解密方法抛出"Padding is invalid and cannot be removed"异常的原因排查
解密时出现“Padding is invalid and cannot be removed”的问题排查与修复
我使用Rfc2898DeriveBytes和CryptoStream库对SQL连接字符串中的字符串值进行加解密操作,但在解密加密后的值时,出现如下错误:
Padding is invalid and cannot be removed
我查阅了若干可能的解决方案,多数都提到要指定显式填充值,而我已经设置了该值。相关代码如下:
var t1 = "my_sql_password"; var t2 = Encrypt(t1, encryptionKey); var t3 = Decrypt(t2, encryptionKey); // 抛出"Padding is invalid and cannot be removed."错误 public static string Encrypt(string stringToEncrypt, string encryptionKey) { byte[] clearBytes = Encoding.Unicode.GetBytes(stringToEncrypt); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(encryptionKey, 7, 5, HashAlgorithmName.SHA1); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); encryptor.Padding = PaddingMode.PKCS7; using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(clearBytes, 0, clearBytes.Length); cs.Close(); } stringToEncrypt = Convert.ToBase64String(ms.ToArray()); } } return stringToEncrypt; } public static string Decrypt(string stringToDecrypt, string encryptionKey) { stringToDecrypt = stringToDecrypt.Replace(" ", "+"); byte[] cipherBytes = Convert.FromBase64String(stringToDecrypt); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(encryptionKey, 7, 5, HashAlgorithmName.SHA1); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); encryptor.Padding = PaddingMode.PKCS7; using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(cipherBytes, 0, cipherBytes.Length); cs.Close(); // *** 此处抛出"Padding is invalid and cannot be removed." *** } stringToDecrypt = Encoding.Unicode.GetString(ms.ToArray()); } } return stringToDecrypt; }
问题根源
代码里有两个核心错误导致解密失败:
- 盐参数使用错误:
Rfc2898DeriveBytes的第二个参数是盐(salt)的字节数组,你传的整数7会被隐式转成单字节数组[0x07]——虽然语法合法,但盐必须保证加密和解密完全一致,且硬编码固定盐也不符合安全规范,容易导致密钥推导不一致。 - CryptoStream未正确收尾:直接调用
cs.Close()无法保证加密/解密过程中所有数据(包括填充数据)都被正确处理,必须调用cs.FlushFinalBlock()完成流的收尾操作,否则解密时无法正确验证填充格式。
修复后的代码
正确的做法是:加密时生成随机盐,将盐与密文一起存储;解密时先提取盐,再用相同的盐推导密钥和IV,同时确保CryptoStream正确收尾。
var t1 = "my_sql_password"; var t2 = Encrypt(t1, encryptionKey); var t3 = Decrypt(t2, encryptionKey); // 正常解密 public static string Encrypt(string stringToEncrypt, string encryptionKey) { byte[] clearBytes = Encoding.Unicode.GetBytes(stringToEncrypt); // 生成16字节随机盐,提升加密安全性 byte[] salt = new byte[16]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(salt); } using (Aes encryptor = Aes.Create()) { // 用随机盐推导Key和IV Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(encryptionKey, salt, 5, HashAlgorithmName.SHA1); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); encryptor.Padding = PaddingMode.PKCS7; using (MemoryStream ms = new MemoryStream()) { // 先写入盐,方便解密时提取 ms.Write(salt, 0, salt.Length); using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(clearBytes, 0, clearBytes.Length); cs.FlushFinalBlock(); // 替代Close,确保所有加密数据处理完成 } return Convert.ToBase64String(ms.ToArray()); } } } public static string Decrypt(string stringToDecrypt, string encryptionKey) { stringToDecrypt = stringToDecrypt.Replace(" ", "+"); byte[] allBytes = Convert.FromBase64String(stringToDecrypt); // 提取前16字节作为盐 byte[] salt = new byte[16]; Array.Copy(allBytes, 0, salt, 0, salt.Length); // 提取剩余部分作为密文 byte[] cipherBytes = new byte[allBytes.Length - salt.Length]; Array.Copy(allBytes, salt.Length, cipherBytes, 0, cipherBytes.Length); using (Aes encryptor = Aes.Create()) { // 用相同的盐推导Key和IV Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(encryptionKey, salt, 5, HashAlgorithmName.SHA1); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); encryptor.Padding = PaddingMode.PKCS7; using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(cipherBytes, 0, cipherBytes.Length); cs.FlushFinalBlock(); // 确保解密完成,验证填充格式 } return Encoding.Unicode.GetString(ms.ToArray()); } } }
额外说明
- 盐必须随机生成,每个加密内容使用不同的盐,避免相同明文生成相同密文,提升抗破解能力。
Rfc2898DeriveBytes的迭代次数(当前为5)建议设置为10000以上,增强密钥推导的安全性。- 密钥不要硬编码,应从安全配置存储(如Windows凭据管理器、云密钥管理服务)中读取。
内容的提问来源于stack exchange,提问作者Matt Spinks
相关产品推荐
相关产品推荐

