使用.NET类的OpenSSL加密

问题描述:

我正在创建一个使用与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

终于想通这一个。如果有人需要在不使用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

怕有一个错误与此最新的代码问题,以及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)

我不知道它这将很难从一台计算机上加密一个文本块,然后将其发送给另一个存储和解密。