2011-03-27 71 views
17

我正在創建一個使用與OpenSSL兼容的.NET庫的類。我知道有一個OpenSSL.Net包裝,但我寧願避免引用第三方\非託管代碼。我不是在討論這是否是正確的選擇,但有理由。使用.NET類的OpenSSL加密

目前我有以下內容,我相信它應該與OpenSSL兼容 - 它有效地做到了OpenSSL從OpenSSL文檔中做的事情。只是使用這個類做加密和解密。然而,即使,我發現了以下錯誤:

[CryptographicException] Padding is invalid and cannot be removed. 

我已經通過代碼加強,並驗證了鹽\鍵\ IV都是在同一加密和解密過程。

請參閱下面的示例類和呼叫做加密解密。任何想法或指針都會受到歡迎。

public class Protection 
    { 
     public string OpenSSLEncrypt(string plainText, string passphrase) 
     { 
      // generate salt 
      byte[] key, iv; 
      byte[] salt = new byte[8]; 
      RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); 
      rng.GetNonZeroBytes(salt); 
      DeriveKeyAndIV(passphrase, salt, out key, out iv); 
      // encrypt bytes 
      byte[] encryptedBytes = EncryptStringToBytesAes(plainText, key, iv); 
      // add salt as first 8 bytes 
      byte[] encryptedBytesWithSalt = new byte[salt.Length + encryptedBytes.Length]; 
      Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 0, salt.Length); 
      Buffer.BlockCopy(encryptedBytes, 0, encryptedBytesWithSalt, salt.Length, encryptedBytes.Length); 
      // base64 encode 
      return Convert.ToBase64String(encryptedBytesWithSalt); 
     } 

     public string OpenSSLDecrypt(string encrypted, string passphrase) 
     { 
      // base 64 decode 
      byte[] encryptedBytesWithSalt = Convert.FromBase64String(encrypted); 
      // extract salt (first 8 bytes of encrypted) 
      byte[] salt = new byte[8]; 
      byte[] encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length]; 
      Buffer.BlockCopy(encryptedBytesWithSalt, 0, salt, 0, salt.Length); 
      Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length, encryptedBytes, 0, encryptedBytes.Length); 
      // get key and iv 
      byte[] key, iv; 
      DeriveKeyAndIV(passphrase, salt, out key, out iv); 
      return DecryptStringFromBytesAes(encryptedBytes, key, iv); 
     } 

     private static void DeriveKeyAndIV(string passphrase, byte[] salt, out byte[] key, out byte[] iv) 
     { 
      // generate key and iv 
      List<byte> concatenatedHashes = new List<byte>(48); 

      byte[] password = Encoding.UTF8.GetBytes(passphrase); 
      byte[] currentHash = new byte[0]; 
      MD5 md5 = MD5.Create(); 
      bool enoughBytesForKey = false; 
      // See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM 
      while (!enoughBytesForKey) 
      { 
       int preHashLength = currentHash.Length + password.Length + salt.Length; 
       byte[] preHash = new byte[preHashLength]; 

       Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length); 
       Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length); 
       Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length); 

       currentHash = md5.ComputeHash(preHash); 
       concatenatedHashes.AddRange(currentHash); 

       if (concatenatedHashes.Count >= 48) 
        enoughBytesForKey = true; 
      } 

      key = new byte[32]; 
      iv = new byte[16]; 
      concatenatedHashes.CopyTo(0, key, 0, 32); 
      concatenatedHashes.CopyTo(32, iv, 0, 16); 

      md5.Clear(); 
      md5 = null; 
     } 

     static byte[] EncryptStringToBytesAes(string plainText, byte[] key, byte[] iv) 
     { 
      // Check arguments. 
      if (plainText == null || plainText.Length <= 0) 
       throw new ArgumentNullException("plainText"); 
      if (key == null || key.Length <= 0) 
       throw new ArgumentNullException("key"); 
      if (iv == null || iv.Length <= 0) 
       throw new ArgumentNullException("iv"); 

      // Declare the stream used to encrypt to an in memory 
      // array of bytes. 
      MemoryStream msEncrypt; 

      // Declare the RijndaelManaged object 
      // used to encrypt the data. 
      RijndaelManaged aesAlg = null; 

      try 
      { 
       // Create a RijndaelManaged object 
       // with the specified key and IV. 
       aesAlg = new RijndaelManaged { Key = key, IV = iv, Mode = CipherMode.CBC, KeySize = 256, BlockSize = 256 }; 


       // Create an encryptor to perform the stream transform. 
       ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); 

       // Create the streams used for encryption. 
       msEncrypt = new MemoryStream(); 
       using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) 
       { 
        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) 
        { 

         //Write all data to the stream. 
         swEncrypt.Write(plainText); 
         swEncrypt.Flush(); 
         swEncrypt.Close(); 
        } 
       } 
      } 
      finally 
      { 
       // Clear the RijndaelManaged object. 
       if (aesAlg != null) 
        aesAlg.Clear(); 
      } 

      // Return the encrypted bytes from the memory stream. 
      return msEncrypt.ToArray(); 
     } 

     static string DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv) 
     { 
      // Check arguments. 
      if (cipherText == null || cipherText.Length <= 0) 
       throw new ArgumentNullException("cipherText"); 
      if (key == null || key.Length <= 0) 
       throw new ArgumentNullException("key"); 
      if (iv == null || iv.Length <= 0) 
       throw new ArgumentNullException("iv"); 

      // Declare the RijndaelManaged object 
      // used to decrypt the data. 
      RijndaelManaged aesAlg = null; 

      // Declare the string used to hold 
      // the decrypted text. 
      string plaintext; 

      try 
      { 
       // Create a RijndaelManaged object 
       // with the specified key and IV. 
       aesAlg = new RijndaelManaged { Key = key, IV = iv, Mode = CipherMode.CBC, KeySize = 256, BlockSize = 256}; 

       // Create a decrytor to perform the stream transform. 
       ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); 
       // Create the streams used for decryption. 
       using (MemoryStream msDecrypt = new MemoryStream(cipherText)) 
       { 
        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) 
        { 
         using (StreamReader srDecrypt = new StreamReader(csDecrypt)) 
         { 
          // Read the decrypted bytes from the decrypting stream 
          // and place them in a string. 
          plaintext = srDecrypt.ReadToEnd(); 
          srDecrypt.Close(); 
         } 
        } 
       } 
      } 
      finally 
      { 
       // Clear the RijndaelManaged object. 
       if (aesAlg != null) 
        aesAlg.Clear(); 
      } 

      return plaintext; 
     } 
    } 

我再調用這個來測試它:

Protection protection = new Protection(); 
const string passphrase = "<passphrase>"; 
string encrypted = protection.OpenSSLEncrypt(jobid, passphrase); 
string decrypted = protection.OpenSSLDecrypt(encrypted, passphrase); 
+0

如果您嘗試實現AES,則塊大小不正確,對於AES(塊大小= 256),它是128位。我沒有看到有關PKCS#7填充的提及,我相信這是AES的OpenSSL默認設置。另外通常的做法是創建一個隨機的iv加密並將其加入到加密的數據中。在解密時拉出iv並將其用於解密。 – zaph 2016-07-21 21:38:01

+0

我需要實現相同的功能,但使用3DES。任何建議?我正在嘗試實現它? – Azimuth 2016-09-22 11:15:31

回答

26

終於想通這一個。如果有人需要在不使用openssl包裝的情況下集成openssl和.NET,我會在這裏分享結果。

1)我的原始代碼(如問題中)的主要問題是,您必須在設置密鑰或IV之前初始化RijndaelManaged實例上的BlockSize和KeySize。

2)我也有塊大小設置爲256時,它應該只有128

3)我的問題剩下來的事實,OpenSSL的看跌期權,並預計「Salted__」到鹽的前追加前加密的字符串,然後base64編碼它。 (我最初在openssl文檔中看到了關於文件加密的問題,但是並沒有認爲它直接通過命令行來做到這一點 - 顯然我錯了!!還要注意S中鹽的大小寫!)

隨着所有的想法,這是我的「固定」代碼:

public class Protection 
    { 
     public string OpenSSLEncrypt(string plainText, string passphrase) 
     { 
      // generate salt 
      byte[] key, iv; 
      byte[] salt = new byte[8]; 
      RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); 
      rng.GetNonZeroBytes(salt); 
      DeriveKeyAndIV(passphrase, salt, out key, out iv); 
      // encrypt bytes 
      byte[] encryptedBytes = EncryptStringToBytesAes(plainText, key, iv); 
      // add salt as first 8 bytes 
      byte[] encryptedBytesWithSalt = new byte[salt.Length + encryptedBytes.Length + 8]; 
      Buffer.BlockCopy(Encoding.ASCII.GetBytes("Salted__"), 0, encryptedBytesWithSalt, 0, 8); 
      Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 8, salt.Length); 
      Buffer.BlockCopy(encryptedBytes, 0, encryptedBytesWithSalt, salt.Length + 8, encryptedBytes.Length); 
      // base64 encode 
      return Convert.ToBase64String(encryptedBytesWithSalt); 
     } 

     public string OpenSSLDecrypt(string encrypted, string passphrase) 
     { 
      // base 64 decode 
      byte[] encryptedBytesWithSalt = Convert.FromBase64String(encrypted); 
      // extract salt (first 8 bytes of encrypted) 
      byte[] salt = new byte[8]; 
      byte[] encryptedBytes = new byte[encryptedBytesWithSalt.Length - salt.Length - 8]; 
      Buffer.BlockCopy(encryptedBytesWithSalt, 8, salt, 0, salt.Length); 
      Buffer.BlockCopy(encryptedBytesWithSalt, salt.Length + 8, encryptedBytes, 0, encryptedBytes.Length); 
      // get key and iv 
      byte[] key, iv; 
      DeriveKeyAndIV(passphrase, salt, out key, out iv); 
      return DecryptStringFromBytesAes(encryptedBytes, key, iv); 
     } 

     private static void DeriveKeyAndIV(string passphrase, byte[] salt, out byte[] key, out byte[] iv) 
     { 
      // generate key and iv 
      List<byte> concatenatedHashes = new List<byte>(48); 

      byte[] password = Encoding.UTF8.GetBytes(passphrase); 
      byte[] currentHash = new byte[0]; 
      MD5 md5 = MD5.Create(); 
      bool enoughBytesForKey = false; 
      // See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM 
      while (!enoughBytesForKey) 
      { 
       int preHashLength = currentHash.Length + password.Length + salt.Length; 
       byte[] preHash = new byte[preHashLength]; 

       Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length); 
       Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length); 
       Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length); 

       currentHash = md5.ComputeHash(preHash); 
       concatenatedHashes.AddRange(currentHash); 

       if (concatenatedHashes.Count >= 48) 
        enoughBytesForKey = true; 
      } 

      key = new byte[32]; 
      iv = new byte[16]; 
      concatenatedHashes.CopyTo(0, key, 0, 32); 
      concatenatedHashes.CopyTo(32, iv, 0, 16); 

      md5.Clear(); 
      md5 = null; 
     } 

     static byte[] EncryptStringToBytesAes(string plainText, byte[] key, byte[] iv) 
     { 
      // Check arguments. 
      if (plainText == null || plainText.Length <= 0) 
       throw new ArgumentNullException("plainText"); 
      if (key == null || key.Length <= 0) 
       throw new ArgumentNullException("key"); 
      if (iv == null || iv.Length <= 0) 
       throw new ArgumentNullException("iv"); 

      // Declare the stream used to encrypt to an in memory 
      // array of bytes. 
      MemoryStream msEncrypt; 

      // Declare the RijndaelManaged object 
      // used to encrypt the data. 
      RijndaelManaged aesAlg = null; 

      try 
      { 
       // Create a RijndaelManaged object 
       // with the specified key and IV. 
       aesAlg = new RijndaelManaged { Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv }; 

       // Create an encryptor to perform the stream transform. 
       ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); 

       // Create the streams used for encryption. 
       msEncrypt = new MemoryStream(); 
       using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) 
       { 
        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) 
        { 

         //Write all data to the stream. 
         swEncrypt.Write(plainText); 
         swEncrypt.Flush(); 
         swEncrypt.Close(); 
        } 
       } 
      } 
      finally 
      { 
       // Clear the RijndaelManaged object. 
       if (aesAlg != null) 
        aesAlg.Clear(); 
      } 

      // Return the encrypted bytes from the memory stream. 
      return msEncrypt.ToArray(); 
     } 

     static string DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv) 
     { 
      // Check arguments. 
      if (cipherText == null || cipherText.Length <= 0) 
       throw new ArgumentNullException("cipherText"); 
      if (key == null || key.Length <= 0) 
       throw new ArgumentNullException("key"); 
      if (iv == null || iv.Length <= 0) 
       throw new ArgumentNullException("iv"); 

      // Declare the RijndaelManaged object 
      // used to decrypt the data. 
      RijndaelManaged aesAlg = null; 

      // Declare the string used to hold 
      // the decrypted text. 
      string plaintext; 

      try 
      { 
       // Create a RijndaelManaged object 
       // with the specified key and IV. 
       aesAlg = new RijndaelManaged {Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv}; 

       // Create a decrytor to perform the stream transform. 
       ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); 
       // Create the streams used for decryption. 
       using (MemoryStream msDecrypt = new MemoryStream(cipherText)) 
       { 
        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) 
        { 
         using (StreamReader srDecrypt = new StreamReader(csDecrypt)) 
         { 
          // Read the decrypted bytes from the decrypting stream 
          // and place them in a string. 
          plaintext = srDecrypt.ReadToEnd(); 
          srDecrypt.Close(); 
         } 
        } 
       } 
      } 
      finally 
      { 
       // Clear the RijndaelManaged object. 
       if (aesAlg != null) 
        aesAlg.Clear(); 
      } 

      return plaintext; 
     } 
    } 
+0

嗨,你有等價的openssl命令行參數來做相反的任務嗎?例如,我用你的代碼加密一條消息,我應該用什麼命令來從openssl解密? – hardywang 2012-10-26 20:28:18

+1

用openssl解密的等價命令是'openssl enc -d -aes-256-cbc -a -in encrypted_file.txt> decrypted_file.txt' – 2016-02-11 15:08:42

+0

這是一個死鏈接!這是正確的:https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey。html – balage 2016-11-10 18:16:34

2

怕有一個錯誤與此最新的代碼問題,以及OpenSSLDecrypt結果:

Padding is invalid and cannot be removed.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for >more information about the error and where it originated in the code.

Exception Details: System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.

It occurs at the closen paren of this code:

using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))" in 'static string DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv)

我不知道它這將很難從一臺計算機上加密一個文本塊,然後將其發送給另一個存儲和解密。