【问题标题】:OpenSSL encryption using .NET classes使用 .NET 类的 OpenSSL 加密
【发布时间】:2011-07-24 01:42:40
【问题描述】:

我希望创建一个使用与 OpenSSL 兼容的 .NET 库的类。我知道有一个 OpenSSL.Net 包装器,但我宁愿避免引用第 3 方\非托管代码。我不是在讨论这是否是正确的选择,但这是有原因的。

目前我有以下内容,我认为它应该与 OpenSSL 兼容 - 它有效地完成了我认为 OpenSSL 在 OpenSSL 文档中所做的事情。然而,即使只是使用这个类来进行加密和解密,我也会收到以下错误:

[CryptographicException] Padding is invalid and cannot be removed.

我已经单步执行了代码,验证了salt\key\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);

【问题讨论】:

  • 如果您尝试实现 AES,则块大小不正确,AES 为 128 位 (BlockSize = 256)。我没有看到任何提及 PKCS#7 填充,我相信这是 AES 的 OpenSSL 默认值。通常的做法是在加密上创建一个随机 iv 并将其添加到加密数据中。解密时拉 iv 并将其用于解密。
  • 我需要使用 3DES 来实现相同的功能。有什么建议吗?我实际上正在尝试实现它?

标签: c# .net encryption openssl aes


【解决方案1】:

10 年后,我提出了一个类似的问题 It is possible to decrypt AES password protected file in C# / dotNet 5 encrypted by openssl enc -k? 并且 Evk 对上述答案做了一些小改动(SHA256 而不是 MD5,处理 Salted__string,使用 byte[]),使其可以替代 openSSL 1.1.1(a 到 l)

【讨论】:

    【解决方案2】:

    恐怕这个最新代码存在问题以及 OpenSSLDecrypt 会导致错误:

    填充无效,无法移除。

    说明:在执行当前 Web 请求期间发生未处理的异常。请查看堆栈跟踪以获取>有关错误及其源自代码的位置的更多信息。

    异常详细信息:System.Security.Cryptography.CryptographicException:填充无效且无法删除。

    它出现在这段代码的结尾处:

    在'静态字符串 DecryptStringFromBytesAes(byte[] cipherText, byte[] key, byte[] iv) 中使用 (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))"

    我不知道从一台计算机加密一团文本然后将其发送到另一台计算机进行存储和解密会如此困难。

    【讨论】:

      【解决方案3】:

      终于想通了。如果有人需要在不使用 openssl 包装器的情况下集成 openssl 和 .NET,我将在这里分享结果。

      1) 我的原始代码的主要问题(如问题所示)是您必须在设置密钥或 IV 之前初始化 RijndaelManaged 实例上的 BlockSize 和 KeySize。

      2) 我也将 BlockSize 设置为 256,而它应该只有 128

      3) 我的问题的其余部分是这样一个事实,即 openssl 在附加加密字符串然后对其进行 base64 编码之前将“Salted__”放在盐的前面并期望它。 (我最初在有关文件加密的 openssl 文档中看到了这一点,但在直接通过命令行执行此操作时并没有想到它 - 显然我错了!还要注意 Salted 中 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;
              }
          }
      

      【讨论】:

      • 嗨,你有等效的 openssl 命令行参数来执行相反的任务吗?例如,我用你的代码加密了一条消息,我应该使用什么命令从 openssl 解密?
      • 用openssl解密的等效命令是openssl enc -d -aes-256-cbc -a -in encrypted_file.txt &gt; decrypted_file.txt
      • 这是一个死链接!这是正确的:openssl.org/docs/manmaster/man3/EVP_BytesToKey.html
      • 另一个建议是更改和重构 MD5 md5 = MD5.Create();到 SHA256 sha256 = SHA256.Create();如果使用仍然使用 MD5 的 open sll 版本,则可以传递“-md SHA256”
      • MD5 可以与 CipherMode.CBC 和 PaddingMode.Zeros 一起使用吗?我在使用特定键时遇到问题 - 我发布了另一个关于它的问题 - stackoverflow.com/questions/59388009/…
      猜你喜欢
      • 2022-12-11
      • 2018-11-29
      • 1970-01-01
      • 1970-01-01
      • 2017-08-24
      • 2015-06-06
      • 1970-01-01
      • 2017-03-06
      • 2015-11-19
      相关资源
      最近更新 更多