如何用Crypto-JS解密C#后端AES加密内容?解密空串问题排查
AES解密结果为空的问题排查与修复
问题描述
前端Angular使用Crypto-JS解密后端C# AES加密值时,解密结果始终为空字符串,相关代码如下:
C# 加密代码
// 调用示例:EncryptAES("XEMFkT92UtR1VJI8kU8XQJALk98GGEFM", "random text to encrypt"); public static string EncryptAES(string passPhrase, string plainText) { byte[] iv = Generate256BitsOfRandomEntropy(); byte[] temp; byte[] array; using (Aes aes = Aes.Create()) { byte[] salt = Generate256BitsOfRandomEntropy(); Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(passPhrase, salt, 100); aes.Key = pdb.GetBytes(32); aes.KeySize = 256; aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.CBC; aes.IV = iv; ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream()) { using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write)) { using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream, Encoding.UTF8)) { streamWriter.Write(plainText); } temp = memoryStream.ToArray(); array = salt.Concat(iv).Concat(temp).ToArray(); cryptoStream.Flush(); encryptor.Dispose(); } } } return Convert.ToBase64String(array); } // 随机字节数组生成器 private static byte[] Generate256BitsOfRandomEntropy() { var randomBytes = new byte[16]; using (var rngCsp = new RNGCryptoServiceProvider()) { rngCsp.GetBytes(randomBytes); } return randomBytes; }
前端Angular解密代码(原错误版本)
// 参数key与C#一致:XEMFkT92UtR1VJI8kU8XQJALk98GGEFM // 参数toDecrypt是后端返回的Base64字符串 decryptAES(key: string, toDecrypt: string) { var data = Buffer.from(toDecrypt, 'base64'); var salt = data.slice(0, 16); // 前16字节是salt var iv = data.slice(16, 32); // 接下来16字节是IV const wordArrayIV = CryptoJS.lib.WordArray.create(Array.from(iv)); const wordArraySalt = CryptoJS.lib.WordArray.create(Array.from(salt)) var keyPBKDF2 = CryptoJS.PBKDF2(key, wordArraySalt, { keySize: 256 / 32, iterations: 100 }); var decrypted = CryptoJS.AES.decrypt(toDecrypt, keyPBKDF2, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: wordArrayIV }); // 返回空字符串 return decrypted.toString(); }
问题原因
- 解密传入数据错误:CryptoJS.AES.decrypt需要传入纯密文部分,但原代码传入了包含
salt+iv+密文的完整Base64串,导致无法正确解析密文。 - 字节转WordArray方式错误:用
Array.from(iv)转换字节数组会破坏原始字节结构,CryptoJS无法正确识别IV和salt。 - 字符串转换方式错误:
decrypted.toString()默认返回十六进制字符串,需指定UTF-8编码才能得到明文。
修复后的前端解密代码
decryptAES(key: string, toDecrypt: string) { // 用CryptoJS直接解析Base64,避免Buffer转换问题 const fullData = CryptoJS.enc.Base64.parse(toDecrypt); // 拆分salt(前16字节)、IV(中间16字节)、密文(剩余部分) const salt = CryptoJS.lib.WordArray.create(fullData.words.slice(0, 4)); const iv = CryptoJS.lib.WordArray.create(fullData.words.slice(4, 8)); const ciphertext = CryptoJS.lib.WordArray.create(fullData.words.slice(8)); // 生成密钥,与C#的Rfc2898DeriveBytes对齐 const keyPBKDF2 = CryptoJS.PBKDF2(key, salt, { keySize: 256 / 32, iterations: 100, hasher: CryptoJS.algo.SHA1 // Rfc2898DeriveBytes默认用SHA1,显式指定更清晰 }); // 解密时传入纯密文对象 const decrypted = CryptoJS.AES.decrypt( { ciphertext: ciphertext }, keyPBKDF2, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, iv: iv } ); // 转换为UTF-8明文 return decrypted.toString(CryptoJS.enc.Utf8); }
额外说明
- C#的
Rfc2898DeriveBytes默认使用SHA1哈希算法,CryptoJS的PBKDF2默认也是SHA1,显式指定可避免版本差异问题。 - 加密时C#将
salt+iv+密文拼接后转Base64,解密时必须严格按顺序拆分这三部分,不能混淆。
内容的提问来源于stack exchange,提问作者Andre
相关产品推荐
相关产品推荐

