You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET8中AES加密改用SHA512后解密长文本被截断问题

AES长文本解密截断问题排查(.NET8 SHA512替换SHA1后)

我在应用中使用基于Stack Overflow的C# AES加密代码,原代码在.NET6下运行正常。升级到.NET8后,因SHA1已废弃,将哈希算法改为SHA512。目前短文本解密正常,但长文本解密仅返回前几个字符。附上完整代码及测试输出,请求排查原因:

using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;

namespace AesEncryption
{
    /// <summary>
    /// Extension class for encryption based on an algorithm described in https://stackoverflow.com/questions/10168240/encrypting-decrypting-a-string-in-c-sharp
    /// Changed the fixed derivationIterations to a random short-number and algorithm to AesCng.
    /// Changed HashAlgorithm to SHA512, as SHA1 became deprecated
    /// </summary>
    public static class GlobalEncryptionAes
    {
        public static string Encrypt(this string plainText)
        {
            return Encrypt(plainText, GeneratePassPhrase());
        }

        public static string Encrypt(this string plainText, string passPhrase)
        {
            // DerivationIterations, Salt and IV are randomly generated each time, but is prepended to encrypted cipher text, so they can be used for decryption
            byte[] derivationIterationsBytes = GenerateBitsOfRandomEntropy(2);
            int derivationIterations = (int)BitConverter.ToUInt16(derivationIterationsBytes);
            if (derivationIterations == 0) derivationIterations++;

            byte[] saltStringBytes = GenerateBitsOfRandomEntropy(32);
            byte[] ivStringBytes = GenerateBitsOfRandomEntropy(16);
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (Rfc2898DeriveBytes password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, derivationIterations, HashAlgorithmName.SHA512))
            {
                var keyBytes = password.GetBytes(32);
                using (AesCng symmetricKey = new AesCng())
                {
                    symmetricKey.KeySize = 256;
                    symmetricKey.BlockSize = 128;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                    {
                        using (MemoryStream memoryStream = new MemoryStream())
                        {
                            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();

                                // Create the final bytes as a concatenation of the random derivation iteration bytes, the random salt bytes, the random iv bytes and the cipher bytes.
                                byte[] cipherTextBytes;
                                cipherTextBytes = derivationIterationsBytes;
                                cipherTextBytes = cipherTextBytes.Concat(saltStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                memoryStream.Close();
                                cryptoStream.Close();

                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// decrypts a string
        /// </summary>
        /// <param name="cipherText">encrypted string</param>
        /// <returns></returns>
        public static string Decrypt(this string cipherText)
        {
            return Decrypt(cipherText, GeneratePassPhrase());
        }

        /// <summary>
        /// decrypts a string
        /// </summary>
        /// <param name="cipherText">encrypted string</param>
        /// <param name="passPhrase">encryption key</param>
        /// <returns></returns>
        public static string Decrypt(this string cipherText, string passPhrase)
        {
            // Get the complete stream of bytes that represent:
            // [2 byte of Derivation Iteration], [32 bytes of Salt] + [16 bytes of IV] + [n bytes of CipherText]
            byte[] cipherTextBytesWithDerivationIterationSaltIv = Convert.FromBase64String(cipherText);

            if (cipherTextBytesWithDerivationIterationSaltIv.Length <= 50)
            {
                throw new ArgumentException("cipherText is not valid");
            }

            // Get the DerivationIterationBytes by extracting the first 2 bytes from the supplied cipherText bytes.
            byte[] derivationIterationsBytes = cipherTextBytesWithDerivationIterationSaltIv.Take(2).ToArray();
            int derivationIterations = (int)BitConverter.ToUInt16(derivationIterationsBytes);

            // Get the saltStringBytes by extracting the next 32 bytes from the supplied cipherText bytes.
            byte[] saltStringBytes = cipherTextBytesWithDerivationIterationSaltIv.Skip(2).Take(32).ToArray();

            // Get the ivStringBytes by extracting the next 24 bytes from the supplied cipherText bytes.
            byte[] ivStringBytes = cipherTextBytesWithDerivationIterationSaltIv.Skip(34).Take(16).ToArray();

            // Get the actual cipher text bytes by removing the first 58 bytes (2 + 32 + 16) from the cipherText string.
            byte[] cipherTextBytes = cipherTextBytesWithDerivationIterationSaltIv.Skip(50).Take(cipherTextBytesWithDerivationIterationSaltIv.Length - 50).ToArray();

            using (Rfc2898DeriveBytes password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, derivationIterations, HashAlgorithmName.SHA512))
            {
                var keyBytes = password.GetBytes(32);
                using (AesCng symmetricKey = new AesCng())
                {
                    symmetricKey.KeySize = 256;
                    symmetricKey.BlockSize = 128;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                byte[] plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();

                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// generates string dependent on machine name and user name and SID of user
        /// </summary>
        /// <returns>passphrase</returns>
        private static string GeneratePassPhrase()
        {
            string machineName;
            string currentUserName;
            SecurityIdentifier? currentUserSecurityIdentifier;
            string currentUserSecurityIdentifierString;
            string passPhrasString;
            byte[] passPhraseBytes;

            machineName = Environment.MachineName;
            currentUserName = WindowsIdentity.GetCurrent().Name;
            currentUserSecurityIdentifier = WindowsIdentity.GetCurrent().User;
            if (currentUserSecurityIdentifier is not null)
            {
                currentUserSecurityIdentifierString = currentUserSecurityIdentifier.Value;
            }
            else
            {
                currentUserSecurityIdentifierString = "";
            }

            passPhrasString = machineName + currentUserName + currentUserSecurityIdentifierString;
            passPhraseBytes = Encoding.UTF8.GetBytes(passPhrasString);

            return Convert.ToBase64String(passPhraseBytes);
        }

        /// <summary>
        /// generates entropy
        /// </summary>
        /// <returns>random entropy of length</returns>
        public static byte[] GenerateBitsOfRandomEntropy(byte numberOfBytes)
        {
            byte[] randomBytes = new byte[numberOfBytes];

            RandomNumberGenerator.Fill(randomBytes);

            return randomBytes;
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            string plainText = "abc";
            Console.WriteLine(plainText);

            string cypherText = plainText.Encrypt();
            Console.WriteLine(cypherText);

            plainText = cypherText.Decrypt();
            Console.WriteLine(plainText);



            plainText = "abcdefghijklmnopqrstuvwxyz";
            Console.WriteLine(plainText);

            cypherText = plainText.Encrypt();
            Console.WriteLine(cypherText);

            plainText = cypherText.Decrypt();
            Console.WriteLine(plainText);
        }
    }
}

测试输出

短文本(正常)

abc
rsWFdjY34byN8xxMt/pJxXc4s0Nvi/HwZEW9g0KFhAS+qqi+qlTlbHg73WN49HNOxSB4jXPqqev7MucKwyLvUepR
abc

长文本(解密截断)

abcdefghijklmnopqrstuvwxyz
xoTpfRH4GIMrB4KrrjWxAy872n3dCAvGZteJCb1W+xfX3jIa9ZFFn94lUhhxwQWaJzh1lAi/B44ZXrYCQK67u3z4BZIelPxEBHfuFeEXKsIJiA==
abcdefghijklmnop

原SHA1版本无此问题,期望长文本解密后完整返回原内容。


问题原因及解决方案

问题根源

解密方法中使用CryptoStream.Read仅读取了一次数据,但Read方法不能保证一次性读取所有可用数据,它返回的是实际读取的字节数。短文本解密时巧合一次读完,但长文本或SHA512生成的密钥导致解密数据流需要多次读取才能完成,只调用一次就会导致数据截断。

修复代码

修改Decrypt方法中读取解密数据的部分,循环读取直到Read返回0:

// 替换原解密方法中CryptoStream内的读取逻辑
using (MemoryStream memoryStream = new MemoryStream(cipherTextBytes))
{
    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
    {
        using (MemoryStream plainStream = new MemoryStream())
        {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                plainStream.Write(buffer, 0, bytesRead);
            }
            return Encoding.UTF8.GetString(plainStream.ToArray());
        }
    }
}

说明

  • 使用循环读取CryptoStream,确保所有解密后的数据都被读取
  • 用临时MemoryStream存储所有解密字节,最后一次性转换为字符串
  • 原代码中依赖cipherTextBytes.Length初始化输出数组的方式不可靠,因为解密后的明文长度可能小于密文长度(PKCS7 padding会被移除)

内容的提问来源于stack exchange,提问作者Holger Kühn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 16:12:06