如何让C#加密结果可被Node.js的CryptoJS.AES.decrypt解密?
C#实现与CryptoJS AES加密等效的问题及解决
问题描述
需要在C#中实现与Node.js以下代码完全等效的AES加密功能:
let encrypted = CryptoJS.AES.encrypt(JSON.stringify(text), passPhrase).toString()
确保C#加密后的内容能通过Node.js的以下代码解密:
var decrypted = CryptoJS.AES.decrypt(encrypted, passPhrase);
已编写的C#密钥派生方法:
public static byte[] DeriveKeyAndIVFromPassword(string password, int derivedBytesLength) { using (Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, derivedBytesLength)) { return rfc2898.GetBytes(derivedBytesLength); } }
主加密方法:
public static string EncryptAES(string plainText, string passphrase) { try { using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider()) { aesAlg.Mode = CipherMode.CBC; // 使用CBC模式 aesAlg.Padding = PaddingMode.PKCS7; // 使用PKCS7填充 aesAlg.KeySize = 256; // 使用AES-256 byte[] derivedKeyAndIV = DeriveKeyAndIVFromPassword(passphrase, aesAlg.KeySize / 8 + aesAlg.BlockSize / 8); byte[] key = derivedKeyAndIV.Take(aesAlg.KeySize / 8).ToArray(); byte[] iv = derivedKeyAndIV.Skip(aesAlg.KeySize / 8).Take(aesAlg.BlockSize / 8).ToArray(); aesAlg.Key = key; using (var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, iv)) { using (MemoryStream msEncrypt = new MemoryStream()) { msEncrypt.Write(iv, 0, iv.Length); using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (var swEncrypt = new StreamWriter(csEncrypt)) { swEncrypt.Write(plainText); } } return Convert.ToBase64String(msEncrypt.ToArray()); } } } } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); return null; } }
虽然设置了CBC模式、PKCS7填充和256位密钥,但C#生成的加密内容无法被CryptoJS解密,请问问题出在哪里?
问题分析及修正
你的代码主要有3个核心问题,导致和CryptoJS的默认行为不匹配:
1. 密钥派生算法不匹配
CryptoJS的AES.encrypt默认使用OpenSSL的EVP_BytesToKey算法生成密钥和IV,而非你用的PBKDF2(Rfc2898DeriveBytes)。只有手动指定key参数时,CryptoJS才会跳过这个派生过程。
2. 加密结果的格式不匹配
CryptoJS生成的加密字符串是特定的OpenSSL格式:Salted__ + 8字节随机salt + 密文,而你的C#代码直接把IV写在密文前面,格式完全不同。
3. 文本编码不一致
C#的StreamWriter默认使用UTF-16编码写入明文,而CryptoJS默认用UTF-8处理明文,这会导致明文内容在加密前就不一致。
修正后的C#代码
第一步:实现EVP_BytesToKey算法(匹配CryptoJS默认行为)
public static (byte[] Key, byte[] IV) EvpBytesToKey(string password, int keySize, int blockSize) { var passwordBytes = Encoding.UTF8.GetBytes(password); var key = new byte[keySize / 8]; var iv = new byte[blockSize / 8]; var temp = new byte[0]; var hash = new byte[0]; using (var md5 = MD5.Create()) { while (key.Length + iv.Length > temp.Length) { hash = md5.ComputeHash(temp.Concat(passwordBytes).ToArray()); temp = temp.Concat(hash).ToArray(); } Array.Copy(temp, key, key.Length); Array.Copy(temp, key.Length, iv, 0, iv.Length); } return (key, iv); }
第二步:修正加密方法,匹配CryptoJS的输出格式
public static string EncryptAES(string plainText, string passphrase) { try { using (var aes = Aes.Create()) { aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; aes.KeySize = 256; int blockSize = aes.BlockSize; // 生成随机8字节salt(CryptoJS默认行为) var salt = new byte[8]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(salt); } // 使用EVP_BytesToKey派生密钥和IV var (key, iv) = EvpBytesToKey(passphrase + Encoding.UTF8.GetString(salt), aes.KeySize, blockSize); aes.Key = key; aes.IV = iv; var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (var msEncrypt = new MemoryStream()) { // 写入CryptoJS的格式前缀:Salted__ + salt msEncrypt.Write(Encoding.ASCII.GetBytes("Salted__"), 0, 8); msEncrypt.Write(salt, 0, salt.Length); using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { // 指定UTF-8编码写入明文 using (var swEncrypt = new StreamWriter(csEncrypt, Encoding.UTF8)) { swEncrypt.Write(plainText); } } return Convert.ToBase64String(msEncrypt.ToArray()); } } } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); return null; } }
补充说明
- 若想让CryptoJS使用PBKDF2而非默认的EVP_BytesToKey,需要在Node.js代码中手动指定密钥派生参数,但两边必须统一配置,不如直接匹配默认行为更简单。
- 修正后的代码生成的加密字符串,完全可以被你提供的Node.js解密代码正常解密。
内容的提问来源于stack exchange,提问作者maryam mohammadbagheri
相关产品推荐
相关产品推荐

