0

我正在将一个较旧的应用程序移植到.Net 6,并且遇到了加密/解密方法的绊脚石,现在失败了。它在 .Net 4.xx 下仍然可以正常工作

抛出的错误是,

"Padding is invalid and cannot be removed."

代码: - 更新为实际的原始代码。此代码在针对 .Net 4.7.2 时运行良好,但是在将代码移动到 .Net 6.0 RC2 后,它开始丢失大于 32 个字符的解密字符串,这会导致其他地方出现错误,因为字符串不完整。

对于上下文。这是在虚拟主机和桌面客户端上运行的,用于加密传输中的消息。webhost 已更新并验证发送正确的加密值(使用 .Net 4 客户端解密消息是成功的)。但是,.Net 6 桌面客户端没有正确解密它,并且在解密的字符串中丢失了字符。

#region Encrypt method(s)

    private const int Keysize = 256;
    private const int Blocksize = 128;
    private const int DerivationIterations = 1000;

    public async Task<string> EncryptStringWithValidatedPadding(string plainText, string passPhrase)
    {
        string encrypted = null;
        
        bool valid = false;

        while (!valid)
        {
            encrypted = await Encrypt(plainText, passPhrase);
            if (!string.IsNullOrEmpty(await Decrypt(encrypted, passPhrase)))
            {
                valid = true;
            }
        }
        return encrypted;
    }

    private async Task<string> Encrypt(string plainText, string passPhrase)
    {
        var saltStringBytes = GenerateRandomEntropy(32); // 256 bits
        var ivStringBytes = GenerateRandomEntropy(16); // 128 bits
        byte[] plainTextBytes = Convert.FromBase64String(plainText);
        using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
        {
            var keyBytes = password.GetBytes(Keysize / 8);
            using (var symmetricKey = new AesManaged())
            {
                symmetricKey.KeySize = Keysize;
                symmetricKey.BlockSize = Blocksize;
                symmetricKey.Mode = CipherMode.CBC;
                symmetricKey.Padding = PaddingMode.PKCS7;
                using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                        {
                            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                            cryptoStream.FlushFinalBlock();
                            var cipherTextBytes = saltStringBytes;
                            cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                            cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                            memoryStream.Close();
                            cryptoStream.Close();
                            var encrypted64String = Convert.ToBase64String(cipherTextBytes);
                            return encrypted64String;
                        }
                    }
                }
            }
        }
    }

    private static byte[] GenerateRandomEntropy(int byteSize)
    {
        var randomBytes = new byte[byteSize];
        using (var rngCsp = new RNGCryptoServiceProvider())
        {
            rngCsp.GetBytes(randomBytes);
        }
        return randomBytes;
    }

    #endregion

    #region Decrypt method


    public static async Task<string> Decrypt(string cipherText, string passPhrase)
    {
        try
        {
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Blocksize / 8).ToArray();
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) + Blocksize / 8).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) + Blocksize / 8)).ToArray();
            using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new AesManaged())
                {
                    symmetricKey.KeySize = 256;
                    symmetricKey.BlockSize = Blocksize;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        return null;
    }

    #endregion

这被称为,

encryptedString = await new EncryptDecrypt().EncryptStringWithValidatedPadding(b64String, Convert.ToBase64String(Encoding.UTF8.GetBytes(passPhrase)));

我假设保存 IV 应该可以解决这个问题,但我想知道这里是否有我没有看到的明显缺陷。

谁能解释一下?

更新:正如建议的那样,我已将代码重构为以下内容。我还立即将其剥离,以确保底层算法的工作。

参考:https ://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=net-6.0

namespace Encryption_Helper
{
    public class EncryptDecrypt
    {
        #region Encrypt method(s)

        private static byte[] bytes = new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 };

        private const int Keysize = 256;
        private const int Blocksize = 128;
        private const int DerivationIterations = 1000;

        public static async Task<string> EncryptStringWithValidatedPadding(string plainText, string passPhrase)
        {
            string encrypted = null;

            bool valid = false;

            while (!valid)
            {
                encrypted = await Encrypt(plainText, passPhrase);
                if (!string.IsNullOrEmpty(await Decrypt(encrypted, passPhrase)))
                {
                    valid = true;
                }
            }
            return encrypted;
        }

        private static async Task<string> Encrypt(string plainText, string passPhrase)
        {
            using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), bytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                var ivBytes = password.GetBytes(Blocksize / 8);
                using (var aes = Aes.Create())
                {
                    aes.Key = keyBytes;
                    aes.IV = ivBytes;

                    ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                    using (MemoryStream msEncrypt = new MemoryStream())
                    {
                        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                        {
                            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                            {
                                swEncrypt.Write(plainText);
                            }
                            plainText = Convert.ToBase64String(msEncrypt.ToArray());
                        }
                    }
                }
                return plainText;
            }
        }

        #endregion

        #region Decrypt method


        public static async Task<string> Decrypt(string cipherText, string passPhrase)
        {
            try
            {
                using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), bytes, DerivationIterations))
                {
                    var keyBytes = password.GetBytes(Keysize / 8);
                    var ivBytes = password.GetBytes(Blocksize / 8);
                    using (var aes = Aes.Create())
                    {
                        aes.Key = keyBytes;
                        aes.IV = ivBytes;
                     
                        ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);

                        using (var memoryStream = new MemoryStream(Convert.FromBase64String(cipherText)))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                using (StreamReader srDecrypt = new StreamReader(cryptoStream))
                                { 
                                    cipherText = srDecrypt.ReadToEnd();
                                }
                            }
                        }
                        return cipherText;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        #endregion
    }
}

它仍然抛出填充错误!

4

1 回答 1

2

解决了!

public class EncryptDecrypt
    {
        #region Encrypt method(s)

        private const int Keysize = 256;
        private const int Blocksize = 128;
        private const int DerivationIterations = 1000;

        public static async Task<string> EncryptStringWithValidatedPadding(string plainText, string passPhrase)
        {
            string encrypted = null;

            bool valid = false;

            while (!valid)
            {
                encrypted = await Encrypt(plainText, passPhrase);
                if (!string.IsNullOrEmpty(await Decrypt(encrypted, passPhrase)))
                {
                    valid = true;
                }
            }
            return encrypted;
        }

        private static async Task<string> Encrypt(string plainText, string passPhrase)
        {
            var saltStringBytes = GenerateRandomEntropy(Keysize / 8); // 256 bits
            var ivStringBytes = GenerateRandomEntropy(Blocksize / 8); // 128 bits
            byte[] plainTextBytes = Convert.FromBase64String(plainText);
            using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var aes = Aes.Create())
                {
                    aes.KeySize = Keysize;
                    aes.BlockSize = Blocksize;
                    aes.Mode = CipherMode.CBC;
                    aes.Padding = PaddingMode.PKCS7;

                    using (var memoryStream = new MemoryStream())
                    {
                        using (var cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(keyBytes, ivStringBytes), CryptoStreamMode.Write))
                        {
                            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                            cryptoStream.FlushFinalBlock();
                            var cipherTextBytes = saltStringBytes;
                            cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                            cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                            memoryStream.Close();
                            cryptoStream.Close();
                            var encrypted64String = Convert.ToBase64String(cipherTextBytes);
                            return encrypted64String;
                        }
                    }
                }
            }
        }

        private static byte[] GenerateRandomEntropy(int byteSize)
        {
            var randomBytes = new byte[byteSize];
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }

        #endregion

        #region Decrypt method


        public static async Task<string> Decrypt(string cipherText, string passPhrase)
        {
            try
            {
                var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
                var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
                var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Blocksize / 8).ToArray();
                var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) + Blocksize / 8).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) + Blocksize / 8)).ToArray();
                using (var password = new Rfc2898DeriveBytes(Convert.FromBase64String(passPhrase), saltStringBytes, DerivationIterations))
                {
                    var keyBytes = password.GetBytes(Keysize / 8);
                    using (var aes = Aes.Create())
                    {
                        aes.KeySize = Keysize;
                        aes.BlockSize = Blocksize;
                        aes.Mode = CipherMode.CBC;
                        aes.Padding = PaddingMode.PKCS7;

                        using (var ms = new MemoryStream(cipherTextBytes))
                        {
                            using (var cs = new CryptoStream(ms, aes.CreateDecryptor(keyBytes, ivStringBytes), CryptoStreamMode.Read))
                            {
                                using (StreamReader srDecrypt = new StreamReader(cs))
                                {

                                    // Read the decrypted bytes from the decrypting stream
                                    // and place them in a string.
                                    cipherText = srDecrypt.ReadToEnd();
                                }
                            }
                        }
                        return cipherText;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return null;
        }

        #endregion

更新解密方法后,一切都好起来了。

在我看来,.Net 6 打破了嵌套using循环,在返回值完全设置之前关闭了流。

于 2021-10-22T19:36:32.927 回答