AES之CryptoJS加密与C#解密
曾经以为ChatGpt 应该是无所不知道,无所不能的,但是就这个C# 解密用了两天时间来搞,gpt给的代码一直有各种bug,最后还是要靠搜索引擎Bing的帮助才找到了答案
AES加密之CryptoJS与Java C#互相加密解密_梁金堂的博客-CSDN博客 这个文章提供了我查找的方向
Port crypto-js AES functions to C# - TruongTX Blog 这个文章给了我解决的方案
下面是完整的代码示例
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 }