替换PasswordDerivedBytes为Rfc2898DerivedBytes时Unicode编码结果异常求助
问题诊断与修复方案
我帮你定位到了两个核心问题,正是它们导致了Rfc2898DerivedBytes模式下解密出乱码的情况:
1. 密钥与IV的派生逻辑完全不同
PasswordDeriveBytes和Rfc2898DeriveBytes的GetBytes方法行为有本质区别:
- PasswordDeriveBytes:多次调用
GetBytes是从派生的字节流里连续取数。比如先拿32字节当密钥,再拿接下来的16字节当IV,是连贯的。 - Rfc2898DeriveBytes:默认每次调用
GetBytes都会从头生成完整序列。你第二次调用GetBytes(16)拿到的是序列的前16字节,而不是密钥之后的16字节——这直接导致IV完全错误,解密出来的内容自然乱码。
2. 填充模式不匹配
原来的加密逻辑用的是PaddingMode.PKCS7,但你在非legacy模式下改成了PaddingMode.Zeros。这会引发两个问题:
- 如果密钥/IV正确,解密时会因为填充验证失败抛出异常;
- 改成Zeros后不报错,只是因为错误的密钥解密出的字节恰好符合Zeros填充的格式,但实际内容已经完全错乱。
修复后的代码
下面是修正后的Decrypt方法,解决了上述两个问题:
private string Decrypt(string encrypted, string key, string salt, bool legacy = false) { UnicodeEncoding encoding = new UnicodeEncoding(); byte[] encryptedDataBytes = Convert.FromBase64String(encrypted); byte[] saltBytes = encoding.GetBytes(salt); RijndaelManaged encryption = new RijndaelManaged(); byte[] aesKey = null; byte[] aesIV = null; if (legacy) { var secretKey = new PasswordDeriveBytes(key, saltBytes) { IterationCount = 100 }; aesKey = secretKey.GetBytes(32); aesIV = secretKey.GetBytes(16); encryption.Padding = PaddingMode.PKCS7; } else { // 关键:一次性生成足够的字节(32位密钥+16位IV=48字节),再拆分 var secretKey = new Rfc2898DeriveBytes(key, saltBytes, 100); byte[] fullKeyData = secretKey.GetBytes(48); aesKey = fullKeyData.Take(32).ToArray(); aesIV = fullKeyData.Skip(32).Take(16).ToArray(); // 保持填充模式与legacy一致,不能改Zeros encryption.Padding = PaddingMode.PKCS7; } using (var decryptor = encryption.CreateDecryptor(aesKey, aesIV)) using (var memoryStream = new MemoryStream(encryptedDataBytes)) using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) using (var reader = new StreamReader(cryptoStream, encoding)) { // 用StreamReader读取更可靠,避免手动读取时的截断问题 return reader.ReadToEnd(); } }
额外注意点
- 我把密钥和IV的提取逻辑统一了,不管legacy与否都先拿到完整的密钥和IV,代码更清晰;
- 改用
StreamReader.ReadToEnd()读取解密后的内容,比手动读取字节数组更可靠——因为CryptoStream的读取不一定能一次性拿到所有字节; - 确保加密时使用的编码、填充模式、迭代次数和这里完全一致,否则还是会出问题。
内容的提问来源于stack exchange,提问作者mhapps
相关产品推荐
相关产品推荐

