C# AES加密解密本地正常,VM加密内容无法被Azure应用解密
加密解密跨环境不兼容问题排查
问题场景
本地部署加密服务时功能正常,但将服务部署到虚拟机(VM)后,其加密内容无法被Azure上的应用解密。已确认加密密码、.NET版本一致。
加密代码
public static string EncryptToBase64(string content, byte[] password) { if (string.IsNullOrEmpty(content)) return content; byte[] inputBytes = Encoding.UTF8.GetBytes(content); string base64Encoded; using (Aes encryptor = Aes.Create("AES")) { encryptor.KeySize = 256; // 32 * 8 == AES 256 encryptor.BlockSize = 128; // 16 * 8 encryptor.Mode = CipherMode.CBC; encryptor.Padding = PaddingMode.Zeros; encryptor.GenerateIV(); var key = new Rfc2898DeriveBytes(password, saltBytes, 100); encryptor.Key = key.GetBytes(encryptor.KeySize / 8); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(inputBytes, 0, inputBytes.Length); cs.FlushFinalBlock(); } byte[] bt = ms.ToArray(); // here we prepend the IV (16 bytes) to the cypher text var mixed = encryptor.IV.Concat(bt).ToArray(); base64Encoded = Convert.ToBase64String(mixed); } return base64Encoded; } }
解密代码
public static string DecryptBase64(string base64content, byte[] password) { if (string.IsNullOrEmpty(base64content)) return base64content; using (Aes encryptor = Aes.Create("AES")) { encryptor.KeySize = 256; // 32 * 8 == AES 256 encryptor.BlockSize = 128; // 16 * 8 encryptor.Mode = CipherMode.CBC; encryptor.Padding = PaddingMode.Zeros; byte[] encyptedBytes = Convert.FromBase64String(base64content); var key = new Rfc2898DeriveBytes(password, saltBytes, 100); encryptor.Key = key.GetBytes(encryptor.KeySize / 8); encryptor.IV = encyptedBytes.Take(16).ToArray(); // pull first 16 bytes string plaintext = string.Empty; //int byteCount = 0; byte[] cipherBytes = encyptedBytes.Skip(16).ToArray(); // take remaining byte[] plainTextBytes = new byte[cipherBytes.Length]; using (MemoryStream ms = new MemoryStream(cipherBytes)) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Read)) { using (StreamReader srDecrypt = new StreamReader(cs)) { plaintext = srDecrypt.ReadToEnd(); } } } return plaintext.TrimEnd('\0'); // zero padded so chuck out the extra \0 values } }
可能的问题及排查方向
1. saltBytes 一致性问题
代码中使用了未定义的saltBytes变量,若VM与Azure环境的saltBytes值不同,Rfc2898DeriveBytes生成的加密密钥会完全不一致,直接导致解密失败。
- 排查:在VM和Azure环境分别输出
saltBytes的Base64编码,确认两者完全相同;若saltBytes是动态生成的,需将其与IV一起附加到密文头部,解密时先读取salt再生成密钥。
2. 密钥生成结果不一致
即使密码和salt一致,跨环境下Rfc2898DeriveBytes的实现可能存在隐性差异(虽.NET跨平台标准统一,但需验证)。
- 排查:在VM和Azure环境中,用相同的
password和saltBytes生成密钥,输出密钥的Base64编码,对比是否完全一致。
3. 编码与流读取问题
解密时使用StreamReader.ReadToEnd(),其默认编码可能因环境差异导致解码错误;而加密时明确使用Encoding.UTF8,两者不匹配会引发乱码或解密失败。
- 排查:替换解密的流读取方式,直接读取字节数组再转字符串:
using (MemoryStream ms = new MemoryStream(cipherBytes)) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Read)) { cs.Read(plainTextBytes, 0, plainTextBytes.Length); } } plaintext = Encoding.UTF8.GetString(plainTextBytes).TrimEnd('\0');
4. 填充模式的跨环境处理差异
PaddingMode.Zeros的实现虽为标准,但部分环境可能对末尾零字节的处理存在差异,导致解密后内容无法正确截断。
- 排查:临时替换为
PaddingMode.PKCS7(更安全的标准填充方式),测试跨环境解密是否正常;若正常,说明零填充的处理存在环境差异。
5. IV与密文的拼接/拆分错误
加密时将IV(16字节)与密文拼接,解密时通过Take(16)和Skip(16)拆分,若环境中字节数组的长度计算或处理存在异常,会导致IV或密文截取错误。
- 排查:在VM中加密测试内容,输出完整加密字节数组的Base64、IV的Base64、密文的Base64;在Azure环境中手动设置IV和密文进行解密,验证是否能得到正确内容。
6. 异常信息定位
在Azure的解密代码中添加异常捕获,明确错误类型(如CryptographicException提示填充错误、密钥不匹配等),直接定位问题根源。
代码优化建议
- 固定
saltBytes或随密文传输,避免环境差异导致密钥不一致; - 将
PaddingMode.Zeros替换为PaddingMode.PKCS7,提升安全性与兼容性; - 提高
Rfc2898DeriveBytes的迭代次数(建议≥10000),增强密钥生成的抗暴力破解能力。
内容的提问来源于stack exchange,提问作者Juliyanage Silva
相关产品推荐
相关产品推荐

