WPF PasswordBox生成的SecureString与手动构建的不一致引发解密问题
嘿,这个坑我之前踩过好几次!明明密码、盐值都一致,为啥WPF能解密,ASP.NET就不行?咱们从几个最常见的原因入手排查,一步步解决:
1. 密码转字节的编码不一致
这是最容易忽略的点!WPF的PasswordBox.Password是**UTF-16(Unicode)**编码的字符串,如果你在WPF里用Encoding.Unicode.GetBytes(password)生成密码字节,但ASP.NET读取配置里的密码时用了Encoding.UTF8.GetBytes(configPassword),那生成的密钥完全不一样,自然解不开。
解决办法:两边统一编码,比如都用UTF8:
// WPF和ASP.NET都用这行转密码为字节 var passwordBytes = Encoding.UTF8.GetBytes(password);
2. 加密算法的参数未明确指定
如果你的共用代码里没有明确指定CipherMode、PaddingMode,或者密钥派生的哈希算法,不同环境(比如.NET Framework vs .NET Core)的默认值可能有差异。比如:
- 早期.NET Framework的
Rfc2898DeriveBytes默认用SHA1,而如果ASP.NET用了.NET Core 2.0+且指定了SHA256,密钥就会不同 - 部分加密算法的默认
PaddingMode可能因版本变化
解决办法:在加解密代码里明确所有参数,示例:
using System.Security.Cryptography; using System.Text; public static string Encrypt(string value, string password, byte[] salt) { using (var aes = Aes.Create()) { // 明确指定模式和填充方式 aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; // 明确指定哈希算法和迭代次数,两边必须完全一致 var deriveBytes = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256); aes.Key = deriveBytes.GetBytes(32); // 256位密钥 aes.IV = deriveBytes.GetBytes(16); // 128位IV using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV)) using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) using (var sw = new StreamWriter(cs, Encoding.UTF8)) { sw.Write(value); } // 把IV和密文合并,解密时需要用IV var ivAndCipher = new byte[aes.IV.Length + ms.ToArray().Length]; Buffer.BlockCopy(aes.IV, 0, ivAndCipher, 0, aes.IV.Length); Buffer.BlockCopy(ms.ToArray(), 0, ivAndCipher, aes.IV.Length, ms.ToArray().Length); return Convert.ToBase64String(ivAndCipher); } } } public static string Decrypt(string encryptedValue, string password, byte[] salt) { var ivAndCipher = Convert.FromBase64String(encryptedValue); var iv = new byte[16]; var cipherBytes = new byte[ivAndCipher.Length - iv.Length]; // 拆分IV和密文 Buffer.BlockCopy(ivAndCipher, 0, iv, 0, iv.Length); Buffer.BlockCopy(ivAndCipher, iv.Length, cipherBytes, 0, cipherBytes.Length); using (var aes = Aes.Create()) { aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; var deriveBytes = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256); aes.Key = deriveBytes.GetBytes(32); aes.IV = iv; // 用加密时的IV初始化 using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) using (var ms = new MemoryStream(cipherBytes)) using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) using (var sr = new StreamReader(cs, Encoding.UTF8)) { return sr.ReadToEnd(); } } }
3. IV(初始化向量)处理错误
加密时如果没有把IV和密文一起存储,或者解密时没有正确提取IV,肯定解不开。IV是加密的必要参数,每次加密的IV应该随机生成,并和密文绑定在一起(比如前缀到密文前)。
上面的示例代码已经包含了IV的合并和拆分逻辑,确保两边都用相同的方式处理IV。
4. .NET版本差异导致API行为不同
如果WPF用的是.NET Framework,ASP.NET用的是.NET Core/.NET 5+,某些加密API的默认行为可能有变化。比如:
Rfc2898DeriveBytes在旧版本的构造函数默认SHA1,新版本建议显式指定哈希算法- 部分加密类的实现细节有调整
解决办法:两边尽量使用相同的.NET版本,或者在代码中显式指定所有参数(就像上面示例那样),避免依赖默认行为。
最后排查技巧
如果还是不行,可以在两边加日志,输出密码字节数组、派生的密钥、IV的十六进制字符串,对比两边是不是完全一致。只要这些参数一致,加解密结果肯定能匹配。
内容的提问来源于stack exchange,提问作者aoven

