Rijndael算法C# WPF加密解密文件保存失败及代码修复咨询
问题分析与修复方案
核心问题总结
- 对话框逻辑错误:加密/解密时先弹出保存对话框再配置过滤规则,且对话框顺序不合理(应先选源文件,再选目标文件)
- Rijndael密钥/IV不规范:直接用用户输入的密码作为Key和IV,不符合Rijndael对Key(16/24/32字节)、IV(16字节)的长度要求,且安全性极低
- CryptoStream未完成写入:加密时未调用
FlushFinalBlock(),导致最后一段加密数据未写入文件 - 资源未正确释放:未使用
using语句自动管理文件流、加密流资源,存在资源泄漏风险 - 异常处理过于模糊:无法定位具体错误原因
修复步骤
1. 修正对话框逻辑
- 先弹出打开文件对话框,确认用户选择源文件后,再弹出保存文件对话框
- 在
ShowDialog()前设置对话框的Filter,确保过滤规则生效
2. 规范Rijndael的Key与IV处理
- 使用密码派生Key(通过
Rfc2898DeriveBytes),确保Key长度符合要求 - 生成随机IV,加密时将IV写入输出文件开头,解密时先读取IV再解密
3. 完善加密流操作
- 加密完成后调用
CryptoStream.FlushFinalBlock()确保所有数据写入 - 使用
using语句自动释放所有流资源,替代手动Close()
4. 优化异常处理
- 捕获具体异常并显示错误信息,便于调试
修改后的完整代码
private void ButtonEnc_Click(object sender, RoutedEventArgs e) { var openFileDlg = new Microsoft.Win32.OpenFileDialog(); if (openFileDlg.ShowDialog() != true) return; var saveFileDlg = new Microsoft.Win32.SaveFileDialog { Filter = "AES加密文件 (.aes)|*.aes" }; if (saveFileDlg.ShowDialog() != true) return; string inputFile = openFileDlg.FileName; string outputFile = saveFileDlg.FileName; string password = TextBox1.Text; try { EncryptFile(inputFile, outputFile, password); MessageBox.Show("加密完成!", "提示"); } catch (Exception ex) { MessageBox.Show($"加密失败:{ex.Message}", "错误"); } } private void ButtonDec_Click_1(object sender, RoutedEventArgs e) { var openFileDlg = new Microsoft.Win32.OpenFileDialog { Filter = "AES加密文件 (.aes)|*.aes", Multiselect = false }; if (openFileDlg.ShowDialog() != true) return; var saveFileDlg = new Microsoft.Win32.SaveFileDialog(); if (saveFileDlg.ShowDialog() != true) return; string inputFile = openFileDlg.FileName; string outputFile = saveFileDlg.FileName; string password = TextBox1.Text; try { DecryptFile(inputFile, outputFile, password); MessageBox.Show("解密完成!", "提示"); } catch (Exception ex) { MessageBox.Show($"解密失败:{ex.Message}", "错误"); } } private void EncryptFile(string inputFile, string outputFile, string password) { // 生成随机盐值和IV byte[] salt = new byte[16]; byte[] iv = new byte[16]; using (var rng = new RNGCryptoServiceProvider()) { rng.GetBytes(salt); rng.GetBytes(iv); } // 从密码派生符合要求的Key var deriveBytes = new Rfc2898DeriveBytes(password, salt, 10000); byte[] key = deriveBytes.GetBytes(32); // 256位Key using (var fsOut = new FileStream(outputFile, FileMode.Create)) { // 先写入盐值和IV到文件开头,解密时需要读取 fsOut.Write(salt, 0, salt.Length); fsOut.Write(iv, 0, iv.Length); using (var rijndael = new RijndaelManaged { Key = key, IV = iv, Mode = CipherMode.CBC, // 使用更安全的CBC模式 Padding = PaddingMode.PKCS7 }) using (var encryptor = rijndael.CreateEncryptor()) using (var cryptoStream = new CryptoStream(fsOut, encryptor, CryptoStreamMode.Write)) using (var fsIn = new FileStream(inputFile, FileMode.Open)) { // 批量复制数据,比逐字节读写效率更高 fsIn.CopyTo(cryptoStream); cryptoStream.FlushFinalBlock(); } } } private void DecryptFile(string inputFile, string outputFile, string password) { using (var fsIn = new FileStream(inputFile, FileMode.Open)) { // 读取文件开头的盐值和IV byte[] salt = new byte[16]; byte[] iv = new byte[16]; fsIn.Read(salt, 0, salt.Length); fsIn.Read(iv, 0, iv.Length); // 派生Key var deriveBytes = new Rfc2898DeriveBytes(password, salt, 10000); byte[] key = deriveBytes.GetBytes(32); using (var rijndael = new RijndaelManaged { Key = key, IV = iv, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 }) using (var decryptor = rijndael.CreateDecryptor()) using (var cryptoStream = new CryptoStream(fsIn, decryptor, CryptoStreamMode.Read)) using (var fsOut = new FileStream(outputFile, FileMode.Create)) { // 批量复制解密后的数据 cryptoStream.CopyTo(fsOut); } } }
额外说明
- 使用
Rfc2898DeriveBytes通过密码和盐值派生Key,比直接用密码转字节更安全,且能保证Key长度符合Rijndael要求 - 随机生成IV并与加密数据一起存储,避免固定IV带来的安全风险
- 采用
CBC模式+PKCS7填充,符合标准加密规范 - 使用
CopyTo批量读写文件,比逐字节操作效率更高
内容的提问来源于stack exchange,提问作者dfhdf
相关产品推荐
相关产品推荐

