密钥与填充配置正确但解密失败问题排查
解决RijndaelManaged解密时的“Padding is invalid and cannot be removed”错误
问题出在这几个地方:
- IV长度解析错误:加密时你用
BitConverter.GetBytes(iv.Length)把IV长度(int类型,占4字节)写入文件,但解密时却用BitConverter.ToUInt16(buffer, 0)解析——这会把4字节的整数截断成2字节,虽然当前你的IV长度刚好在UInt16范围内,逻辑上完全不匹配,一旦块大小调整就会直接出错。 - 未确保完整读取数据:
FileStream.Read()不会保证一次性读完你要的字节数,直接调用一次可能只读到部分数据,导致IV长度或IV本身不完整,用错误的IV解密肯定会触发填充错误。 - 参数设置顺序不合理:构造函数里先设
Key再设KeySize和BlockSize,虽然当前密钥长度刚好符合要求,但不符合最佳实践,容易引发潜在的参数不匹配问题。
修正后的完整代码
//Targeting .Net 4.5.2 //Key is hard coded, ONLY while working through this error in a minimum repo. byte[] testKey = Convert.FromBase64String("KN1df3fOkLmSPyOP4r+grlVFDC/JVlWuew1u/hDGvUU="); //Called to Encrypt ("D:\\Assets\\", "Test.txt") public void DoWork(string filePath, string fileName) { CryptographyUtil crypto = new CryptographyUtil(testKey); crypto.EncryptFile(filePath, fileName); } //Called to Decrypt ("D:\\Assets\\", "Test.txt.dat") public void UnDoWork(string filePath, string fileName) { CryptographyUtil crypto = new CryptographyUtil(testKey); crypto.DecryptFile(filePath, fileName); } public class CryptographyUtil { RijndaelManaged rjndl; RNGCryptoServiceProvider cRng; public CryptographyUtil(byte[] key, int keySize = 256, int blockSize = 256) { cRng = new RNGCryptoServiceProvider(); rjndl = new RijndaelManaged(); // 先设置密钥长度和块大小,再赋值密钥,避免参数不匹配 rjndl.KeySize = keySize; rjndl.BlockSize = blockSize; rjndl.Key = key; rjndl.Mode = CipherMode.CBC; rjndl.Padding = PaddingMode.PKCS7; } public void EncryptFile(string filePath, string fileName) { string inputFilePath = Path.Combine(filePath, fileName); if(!File.Exists(inputFilePath)) { throw new FileLoadException("Unable to locate or open file.", inputFilePath); } string outputDirectory = Path.Combine(filePath, "Encrypted"); if(!Directory.Exists(outputDirectory)) { Directory.CreateDirectory(outputDirectory); } string outputPath = Path.Combine(outputDirectory, fileName + ".dat"); //Create a unique IV each time byte[] iv = new byte[rjndl.BlockSize / 8]; cRng.GetBytes(iv); byte[] ivSize = BitConverter.GetBytes(iv.Length); ICryptoTransform encryptor = rjndl.CreateEncryptor(rjndl.Key, iv); // using语句会自动释放资源,不用手动Close using(FileStream readStream = File.OpenRead(inputFilePath)) using(FileStream writeStream = new FileStream(outputPath, FileMode.Create)) using(CryptoStream encryptStream = new CryptoStream(writeStream, encryptor, CryptoStreamMode.Write)) { // 先写IV长度和IV明文,再写加密数据 writeStream.Write(ivSize, 0, ivSize.Length); writeStream.Write(iv, 0, iv.Length); readStream.CopyTo(encryptStream); encryptStream.FlushFinalBlock(); } } public void DecryptFile(string filePath, string fileName) { string outputDirectory = Path.Combine(filePath, "Decrypted"); if(!Directory.Exists(outputDirectory)) { Directory.CreateDirectory(outputDirectory); } //Remove the ".dat" from the end of the file string outputFilePath = Path.Combine(outputDirectory, fileName.Substring(0, fileName.LastIndexOf('.'))); if(File.Exists(outputFilePath)) { File.Delete(outputFilePath); } using(FileStream readStream = File.OpenRead(Path.Combine(filePath, fileName))) { byte[] buffer = new byte[4]; int bytesRead = 0; // 循环读取,确保拿到完整的4字节IV长度 while(bytesRead < buffer.Length) { int read = readStream.Read(buffer, bytesRead, buffer.Length - bytesRead); if(read == 0) throw new IOException("Unexpected end of file while reading IV length."); bytesRead += read; } // 用正确的Int32解析IV长度 int ivLength = BitConverter.ToInt32(buffer, 0); buffer = new byte[ivLength]; bytesRead = 0; // 同样循环读取,确保拿到完整的IV while(bytesRead < ivLength) { int read = readStream.Read(buffer, bytesRead, ivLength - bytesRead); if(read == 0) throw new IOException("Unexpected end of file while reading IV."); bytesRead += read; } ICryptoTransform decryptor = rjndl.CreateDecryptor(rjndl.Key, buffer); buffer = new byte[1024]; using(FileStream writeStream = new FileStream(outputFilePath, FileMode.Create)) using(CryptoStream decryptStream = new CryptoStream(readStream, decryptor, CryptoStreamMode.Read)) { int readIdx = decryptStream.Read(buffer, 0, buffer.Length); while(readIdx > 0) { writeStream.Write(buffer, 0, readIdx); readIdx = decryptStream.Read(buffer, 0, buffer.Length); } } } } }
顺便提几个优化点
- 删掉了冗余的
Flush()和Close():using语句会自动处理流的释放,手动调用纯属多余。 - 加了读取完整性校验:避免因流读取不完整导致的IV错误,这是文件操作的基本规范。
- 调整了Rijndael参数顺序:先设长度再设密钥,确保参数匹配,减少潜在bug。
内容的提问来源于stack exchange,提问作者Reahreic
相关产品推荐
相关产品推荐

