AES之CryptoJS加密与C#解密

曾经以为ChatGpt 应该是无所不知道,无所不能的,但是就这个C# 解密用了两天时间来搞,gpt给的代码一直有各种bug,最后还是要靠搜索引擎Bing的帮助才找到了答案
AES加密之CryptoJS与Java C#互相加密解密_梁金堂的博客-CSDN博客 这个文章提供了我查找的方向

Port crypto-js AES functions to C# - TruongTX Blog 这个文章给了我解决的方案

下面是完整的代码示例
  /// <summary>
        /// <para>C# 版本的 cryptojs.AES.decrypt(encryptedString, passphrase).toString(cryptojs.enc.Utf8)</para>
        /// <para>使用 AES 加密时,需要传入一个 Key 和一个随机的 IV - 初始化向量(IV 用于为加密过程添加随机性)</para>
        /// <para>在 crypto-js 中,如果你将一个口令传递给 "encrypt" 函数,例如 <code>cryptojs.AES.encrypt(message, passphrase).toString()</code>,Key 和 IV 将会自动生成</para>
        /// <para>为了从口令中派生出 Key,它使用 OpenSSL 兼容的派生函数 EVP_BytesToKey。该模式生成一个新的 8 字节随机 salt,并与口令一起使用来生成 Key 和 IV。更多信息请参见 <see cref="DeriveKeyAndIv"/></para>
        /// <para>使用 <code>cryptojs.AES.encrypt(message, passphrase).toString()</code> 加密的结果是一个 Base64 编码的密文,其中包含字符串 "Salted__",后面跟着 8 字节的 salt 和实际的密文。这意味着,前 8 个字节是 "Salted__" 字符串,接下来的 8 个字节是 salt,剩下的字节是实际的密文。</para>
        /// <para>以下是解密 cryptojs 生成的 Base64 字符串的步骤:</para>
        /// <para>- 将 Base64 字符串转换回一个字节数组</para>
        /// <para>- 忽略前 8 个字节(为字符串 "Salted__")</para>
        /// <para>- 取接下来的 8 个字节作为 salt</para>
        /// <para>- 取剩下的字节作为密文</para>
        /// <para>- 使用实用程序函数 <see cref="DeriveKeyAndIv"/> 生成 Key 和 IV</para>
        /// <para>- 将密文、Key 和 IV 输入 AES 解密器以获得最终的字符串</para>
        /// </summary>
        /// <param name="encryptedString">待解密的 Base64 字符串</param>
        /// <param name="passphrase">口令,用于生成 Key 和 IV</param>
        /// <returns>解密后的字符串</returns>
        public static string Cryptojs_AesDecrypt(string encryptedString, string passphrase)
        {
            // 检查参数是否为 null 或空字符串
            if (string.IsNullOrEmpty(encryptedString))
            {
                throw new ArgumentException("加密字符串不能为空。", nameof(encryptedString));
            }

            if (string.IsNullOrEmpty(passphrase))
            {
                throw new ArgumentException("口令不能为空。", nameof(passphrase));
            }

            // 将 Base64 编码的字符串转换为字节数组
            var encryptedBytes = Convert.FromBase64String(encryptedString);

            // 获取盐值和密文
            var salt = encryptedBytes[8..16];
            var cipherText = encryptedBytes[16..];

            // 从口令和盐值派生密钥和 IV
            const int iterations = 1; // CryptoJS 默认使用 1 次迭代
            var passphraseBytes = Encoding.UTF8.GetBytes(passphrase);
            DeriveKeyAndIv(passphraseBytes, salt, iterations, out var key, out var iv);

            // 创建 AES 解密器
            using var aes = Aes.Create();
            aes.Key = key;
            aes.IV = iv;
            aes.KeySize = 256;
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;

            // 创建解密器
            var decryptor = aes.CreateDecryptor(key, iv);

            // 解密密文
            using var msDecrypt = new MemoryStream(cipherText);
            using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
            using var srDecrypt = new StreamReader(csDecrypt);

            // 从解密流中读取解密后的字节,并将它们放入一个字符串中。
            return srDecrypt.ReadToEnd();
        }

        /// <summary>
        /// <para>使用C#编写等效的OpenSSL EVP_BytesToKey方法</para>
        /// <para>从输入的口令和盐生成密钥和初始化向量(IV)</para>
        /// </summary>
        /// <param name="passphrase">口令字节数组</param>
        /// <param name="salt">盐字节数组</param>
        /// <param name="iterations">哈希迭代次数</param>
        /// <param name="key">输出32字节密钥</param>
        /// <param name="iv">输出16字节初始化向量(IV)</param>
        private static void DeriveKeyAndIv(byte[] passphrase, byte[] salt, int iterations, out byte[] key, out byte[] iv)
        {
            // 用于保存哈希值
            var hashList = new List<byte>();

            // 计算第一个哈希值
            var preHashLength = passphrase.Length + (salt?.Length ?? 0);
            var preHash = new byte[preHashLength];
            // 将口令复制到 preHash 数组
            Buffer.BlockCopy(passphrase, 0, preHash, 0, passphrase.Length);
            if (salt != null)
            {
                // 将盐值复制到 preHash 数组
                Buffer.BlockCopy(salt, 0, preHash, passphrase.Length, salt.Length); 
            }
            // 创建 MD5 哈希对象
            var hash = MD5.Create();
            // 计算哈希值
            var currentHash = hash.ComputeHash(preHash);
            // 迭代计算哈希值
            for (var i = 1; i < iterations; i++)
            {
                currentHash = hash.ComputeHash(currentHash);
            }
            // 将第一个哈希值加入哈希值列表
            hashList.AddRange(currentHash);

            #region  用于32字节密钥和16字节IV
            // 如果哈希值不足48字节,则进行更多的哈希计算
            while (hashList.Count < 48)
            {
                preHashLength = currentHash.Length + passphrase.Length + (salt?.Length ?? 0);
                preHash = new byte[preHashLength];

                // 将上一次的哈希值复制到 preHash 数组
                Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
                // 将口令复制到 preHash 数组
                Buffer.BlockCopy(passphrase, 0, preHash, currentHash.Length, passphrase.Length);
                if (salt != null)
                {
                    // 将盐值复制到 preHash 数组
                    Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + passphrase.Length, salt.Length);
                }
                // 计算哈希值
                currentHash = hash.ComputeHash(preHash);
                // 迭代计算哈希值
                for (var i = 1; i < iterations; i++)
                {
                    currentHash = hash.ComputeHash(currentHash);
                }
                // 将计算得到的哈希值加入哈希值列表
                hashList.AddRange(currentHash);
            }
            #endregion 用于32字节密钥和16字节IV


            #region  从哈希值列表中提取密钥和 IV
            // 清除哈希对象
            hash.Clear();
            // 用于保存密钥的字节数组
            key = new byte[32];
            // 用于保存 IV 的字节数组
            iv = new byte[16];
            // 将前32个字节复制到密钥数组
            hashList.CopyTo(0, key, 0, 32);
            // 将后16个字节复制到 IV 数组
            hashList.CopyTo(32, iv, 0, 16);
            #endregion  从哈希值列表中提取密钥和 IV

        }

 

posted @ 2023-03-26 16:07  LuoCore  阅读(957)  评论(0编辑  收藏  举报