原正常运行代码出现Padding相关CryptographicException问题求助
解决AES解密时的"Padding is invalid and cannot be removed"异常及数据恢复建议
我有一个存储加密文本数据的程序,新增数据时会从文件加载加密数据、解密、追加新字符串、重新加密后保存至磁盘。该程序此前运行正常,但近期解密加载的数据时抛出
CryptographicException:Padding is invalid and cannot be removed。以下是我使用的加解密代码:
public static byte[] Encrypt(string text, byte[] key) { byte[] encrypted; byte[] IV; using (var aes = Aes.Create()) { aes.Key = key; aes.GenerateIV(); IV = aes.IV; aes.Mode = CipherMode.CBC; var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (var msEncrypt = new MemoryStream()) using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (var swEncrypt = new StreamWriter(csEncrypt)) { swEncrypt.Write(text); } encrypted = msEncrypt.ToArray(); } } // return plaintext IV and encrypted text payload concatenated return IV.Concat(encrypted).ToArray(); } // Encrypt() public static string Decrypt(byte[] bytes, byte[] key) { string plaintext = null; using (var aes = Aes.Create()) { aes.Key = key; // split iv and encryped cyphertext byte[] IV = new byte[aes.BlockSize / 8]; byte[] cipherText = new byte[bytes.Length - IV.Length]; Array.Copy(bytes, IV, IV.Length); Array.Copy(bytes, IV.Length, cipherText, 0, cipherText.Length); aes.IV = IV; aes.Mode = CipherMode.CBC; var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (var msDecrypt = new MemoryStream(cipherText)) using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) using (var srDecrypt = new StreamReader(csDecrypt)) { plaintext = srDecrypt.ReadToEnd(); // !!! EXCEPTION HAPPENS HERE !!! } } return plaintext; }
问题根源分析
你的代码存在两个核心问题:
- 加密流程未完成收尾操作:AES-CBC模式下,最后一块数据的PKCS7填充是在
CryptoStream释放或调用FlushFinalBlock()时才会完成。你原加密代码中,encrypted = msEncrypt.ToArray()是在csEncrypt的using块内部执行的,此时csEncrypt还未释放,导致加密数据缺失最后一块的填充内容,解密时自然触发填充验证失败。 - 非原子性的文件写入:直接覆盖原文件的保存逻辑,一旦写入过程中出现中断(磁盘空间不足、程序崩溃),就会导致文件数据损坏,这也是你遇到异常的可能诱因。
代码修复方案
1. 修正加密方法
确保CryptoStream完成所有加密操作后再读取内存流数据,有两种实现方式:
方式1:将内存流读取移到CryptoStream的using块外部
当csEncrypt的using块结束时,会自动调用Dispose方法完成填充:
public static byte[] Encrypt(string text, byte[] key) { byte[] encrypted; byte[] IV; using (var aes = Aes.Create()) { aes.Key = key; aes.GenerateIV(); IV = aes.IV; aes.Mode = CipherMode.CBC; var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (var msEncrypt = new MemoryStream()) { using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) using (var swEncrypt = new StreamWriter(csEncrypt)) { swEncrypt.Write(text); } // csEncrypt在此处释放,完成填充操作 encrypted = msEncrypt.ToArray(); } } return IV.Concat(encrypted).ToArray(); }
方式2:手动调用FlushFinalBlock
在StreamWriter释放后,手动触发加密收尾:
public static byte[] Encrypt(string text, byte[] key) { byte[] encrypted; byte[] IV; using (var aes = Aes.Create()) { aes.Key = key; aes.GenerateIV(); IV = aes.IV; aes.Mode = CipherMode.CBC; var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (var msEncrypt = new MemoryStream()) using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (var swEncrypt = new StreamWriter(csEncrypt)) { swEncrypt.Write(text); } csEncrypt.FlushFinalBlock(); // 手动完成填充和加密收尾 encrypted = msEncrypt.ToArray(); } } return IV.Concat(encrypted).ToArray(); }
2. 改进文件保存逻辑(避免数据损坏)
采用原子写入方式,先写入临时文件,确认成功后再替换原文件:
// 示例保存逻辑 var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); File.WriteAllBytes(tempFilePath, encryptedData); // 原子替换原文件,确保写入失败时原文件不受影响 File.Move(tempFilePath, yourOriginalFilePath, overwrite: true);
数据恢复建议
根据你描述的解密结果(前1/3有效,后接乱码和重复16字节),数据大概率是部分损坏,可尝试以下步骤恢复:
- 立即备份损坏文件:复制原始损坏文件到安全位置,避免进一步破坏。
- 验证数据长度:正常加密数据总长度应为
16(IV长度) + 16*N(密文长度,N为正整数),如果当前长度不符合,说明数据被截断或覆盖。 - 尝试截断密文到有效块长度:
- 提取前16字节作为IV。
- 截取后面的密文部分到最近的16字节整数倍长度(比如密文长度为X,取
X - (X%16)字节,确保结果≥16)。 - 用修正后的解密代码尝试解密截断后的密文,大概率能得到完整的前半部分有效文本,之后可手动补全丢失内容(若能回忆)。
- 查找备份:检查系统卷影副本(Windows)或个人备份文件,尝试恢复未损坏的历史版本。
内容的提问来源于stack exchange,提问作者Askaga
相关产品推荐
相关产品推荐

