AES加解密报错:Padding is invalid and cannot be removed问题排查
问题:ASP.NET Core AES解密抛出「Padding无效无法移除」异常
在ASP.NET Core项目中实现AES加解密时,抛出异常:
System.Security.Cryptography.CryptographicException: 'Padding is invalid and cannot be removed.'
已确认前后端均使用PKCS7填充、ECB模式,更换填充模式后仍报错,求问题原因及解决办法。
前端View代码
<form asp-controller="Permit" asp-action="Encryptori" method="post"> <div class="form-group"> <label for="Name">Name:</label> <input type="text" id="Name" name="Name" value="Roberto" class="form-control" /> </div> <div class="form-group"> <label for="Address">Address:</label> <input type="text" id="Address" name="Address" value="Viale Manfredo Fanti, 4 (50137) Firenze" class="form-control" /> </div> <br /> <button type="submit" class="btn btn-primary" onclick="encryptAndSubmit()">Encrypt and Submit</button> <br /><br /> @ViewBag.Display <input type="hidden" id="Key" name="Key" value="@ViewBag.Key" /> <input type="hidden" id="IV" name="IV" value="@ViewBag.IV" /> </form>
前端脚本代码
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.min.js"></script> <script> function encryptAndSubmit() { var name = document.getElementById("Name").value; var address = document.getElementById("Address").value; var key = "@ViewBag.Key"; var iv = "@ViewBag.IV"; var encryptedName = encrypt(name, key, iv); var encryptedAddress = encrypt(address, key, iv); document.getElementById("Name").value = encryptedName; document.getElementById("Address").value = encryptedAddress; } function encrypt(plainText, key, iv) { var keyBytes = CryptoJS.enc.Utf8.parse(key); var ivBytes = CryptoJS.enc.Utf8.parse(iv); var encrypted = CryptoJS.AES.encrypt(plainText, keyBytes, { iv: ivBytes, mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } </script>
后端Controller代码
private readonly byte[] _key; private readonly byte[] _iv; public PermitController() { var rng = new RNGCryptoServiceProvider(); _key = new byte[32]; rng.GetBytes(_key); _iv = new byte[16]; rng.GetBytes(_iv); } public async Task<IActionResult> Encryptori() { ViewBag.Display = ""; ViewBag.Key = Convert.ToBase64String(_key); ViewBag.IV = Convert.ToBase64String(_iv); ApplicationUser user = await _userManager.GetUserAsync(User); return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Encryptori(IFormCollection collection) { string TheKey = collection["Key"]; string TheIv = collection["IV"]; var keyBytes = Convert.FromBase64String(TheKey); var ivBytes = Convert.FromBase64String(TheIv); string encryptedName = collection["Name"]; string encryptedAddress = collection["Address"]; var decryptedName = Decrypt(encryptedName, TheKey, TheIv); var decryptedAddress = Decrypt(encryptedAddress, TheKey, TheIv); // Store the decrypted data in the ViewBag ViewBag.Display = encryptedName + " and " + encryptedAddress; ApplicationUser user = await _userManager.GetUserAsync(User); return View(); } private string Decrypt(string encryptedText, string key, string iv) { try { var keyBytes = Convert.FromBase64String(key); var ivBytes = Convert.FromBase64String(iv); using (var aes = Aes.Create()) { aes.Key = keyBytes; aes.IV = ivBytes; aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write)) { var encryptedBytes = Convert.FromBase64String(encryptedText); cs.Write(encryptedBytes, 0, encryptedBytes.Length); } ms.Position = 0; var decryptedBytes = new byte[ms.Length]; ms.Read(decryptedBytes, 0, decryptedBytes.Length); return Encoding.UTF8.GetString(decryptedBytes); } } } catch (CryptographicException ex) { Console.WriteLine("Decryption failed: " + ex.Message); return null; } }
问题原因
核心问题是前后端密钥的解析方式完全不一致:
- 后端生成的密钥是32字节随机数,通过
Convert.ToBase64String转成Base64字符串传给前端 - 前端却把这个Base64字符串当成UTF-8文本,用
CryptoJS.enc.Utf8.parse解析成字节数组,这和后端用Convert.FromBase64String解析出来的字节数组完全不同 - 密钥不匹配导致解密时无法正确还原明文,最终触发填充验证失败的异常
另外,ECB模式本身不需要IV,代码里前后端都设置了IV属于冗余操作,但不是报错原因。
解决办法
1. 修正前端密钥解析方式
把前端的密钥/IV从UTF-8解析改为Base64解析,同时移除ECB模式下的IV设置(可选,更规范):
function encrypt(plainText, key, iv) { // 改用Base64解析密钥,和后端保持一致 var keyBytes = CryptoJS.enc.Base64.parse(key); // ECB模式不需要IV,直接移除相关配置 var encrypted = CryptoJS.AES.encrypt(plainText, keyBytes, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); }
2. 优化后端解密逻辑
调整解密时的流处理方式,同时移除ECB模式下的IV设置:
private string Decrypt(string encryptedText, string key, string iv) { try { var keyBytes = Convert.FromBase64String(key); using (var aes = Aes.Create()) { aes.Key = keyBytes; aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; // ECB模式不需要IV,传null即可 var decryptor = aes.CreateDecryptor(aes.Key, null); // 改用Read模式的CryptoStream,简化代码 using (var ms = new MemoryStream(Convert.FromBase64String(encryptedText))) using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) using (var sr = new StreamReader(cs, Encoding.UTF8)) { return sr.ReadToEnd(); } } } catch (CryptographicException ex) { Console.WriteLine("Decryption failed: " + ex.Message); return null; } }
3. 额外安全建议
- ECB模式安全性极低,建议改用CBC或GCM模式(GCM自带消息认证,更安全),更换模式后要确保前后端IV处理一致,且IV需随机生成并随密文传输
- 绝对不要通过前端隐藏域传输密钥!这会直接泄露密钥,应该由后端存储密钥,前端仅传输密文
内容的提问来源于stack exchange,提问作者Waller
相关产品推荐
相关产品推荐

