C#使用MS库实现XML加密时AES256解密出现填充无效错误
XML加密解密时出现“Padding is invalid and cannot be removed”错误
我尝试使用微软库执行XML加密操作,但解密时持续报错:
"Padding is invalid and cannot be removed."
加密代码:
public static string Encrypt(string plainText, string key) { try { if (string.IsNullOrEmpty(plainText)) throw new ArgumentNullException("plainText"); if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); // Derive a new Salt and IV from the Key using (var keyDerivationFunction = new Rfc2898DeriveBytes(key, _saltSize)) { var saltBytes = keyDerivationFunction.Salt; var keyBytes = keyDerivationFunction.GetBytes(32); var ivBytes = keyDerivationFunction.GetBytes(16); // Create an encryptor to perform the stream transform. // Create the streams used for encryption. using (var aesManaged = new AesManaged()) { aesManaged.Padding = PaddingMode.PKCS7; aesManaged.Mode = CipherMode.CBC; using (var encryptor = aesManaged.CreateEncryptor(keyBytes, ivBytes)) using (var memoryStream = new MemoryStream()) { using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) using (var streamWriter = new StreamWriter(cryptoStream)) { // Send the data through the StreamWriter, through the CryptoStream, to the underlying MemoryStream streamWriter.Write(plainText); } // Return the encrypted bytes from the memory stream, in Base64 form so we can send it right to a database (if we want). var cipherTextBytes = memoryStream.ToArray(); Array.Resize(ref saltBytes, saltBytes.Length + cipherTextBytes.Length); Array.Copy(cipherTextBytes, 0, saltBytes, _saltSize, cipherTextBytes.Length); return Convert.ToBase64String(saltBytes); } } } } catch (Exception e) { LOGGER.Error(String.Format("Got error : \"{0}\" trying to encrypt \"{1}\"", e.Message, plainText)); return ""; } } /** */ public static void Encrypt(XmlDocument Doc, string ElementName, string Password) { // Check the arguments. if (null == Doc) throw new ArgumentNullException(); if (string.IsNullOrEmpty(ElementName)) throw new ArgumentNullException(); if (string.IsNullOrEmpty(Password)) throw new ArgumentNullException(); //////////////////////////////////////////////// // Find the specified element in the XmlDocument // object and create a new XmlElement object. //////////////////////////////////////////////// XmlElement elementToEncrypt = Doc.GetElementsByTagName(ElementName)[0] as XmlElement; // Throw an XmlException if the element was not found. if (elementToEncrypt == null) { throw new XmlException("The specified element was not found"); } ////////////////////////////////////////////////// // Create a new instance of the EncryptedXml class // and use it to encrypt the XmlElement with the // symmetric key. ////////////////////////////////////////////////// EncryptedXml eXml = new EncryptedXml(); string ciphertext = CryptoUtil.Encrypt(elementToEncrypt.OuterXml, Password); byte[] encryptedElement = System.Text.UTF8Encoding.UTF8.GetBytes(ciphertext); //////////////////////////////////////////////// // Construct an EncryptedData object and populate // it with the desired encryption information. //////////////////////////////////////////////// EncryptedData edElement = new EncryptedData() { Type = EncryptedXml.XmlEncElementUrl }; // Create an EncryptionMethod element so that the // receiver knows which algorithm to use for decryption. // Determine what kind of algorithm is being used and // supply the appropriate URL to the EncryptionMethod element. string encryptionMethod = EncryptedXml.XmlEncAES256Url; edElement.EncryptionMethod = new EncryptionMethod(encryptionMethod); // Add the encrypted element data to the // EncryptedData object. edElement.CipherData.CipherValue = encryptedElement; //////////////////////////////////////////////////// // Replace the element from the original XmlDocument // object with the EncryptedData element. //////////////////////////////////////////////////// EncryptedXml.ReplaceElement(elementToEncrypt, edElement, false); }
解密代码:
/// <summary> /// Decrypts the ciphertext using the Key. /// </summary> /// <param name="ciphertext">The ciphertext to decrypt.</param> /// <param name="key">The plain text encryption key.</param> /// <returns>The decrypted text.</returns> public static String Decrypt(string ciphertext, string key) { if (string.IsNullOrEmpty(ciphertext)) throw new ArgumentNullException("cipherText"); if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); // Extract the salt from our ciphertext var allTheBytes = Convert.FromBase64String(ciphertext); var saltBytes = allTheBytes.Take(_saltSize).ToArray(); var ciphertextBytes = allTheBytes.Skip(_saltSize).Take(allTheBytes.Length - _saltSize).ToArray(); using (var keyDerivationFunction = new Rfc2898DeriveBytes(key, saltBytes)) { // Derive the previous IV from the Key and Salt var keyBytes = keyDerivationFunction.GetBytes(32); var ivBytes = keyDerivationFunction.GetBytes(16); // Create a decrytor to perform the stream transform. // Create the streams used for decryption. // The default Cipher Mode is CBC and the Padding is PKCS7 which are both good using (var aesManaged = new AesManaged()) { aesManaged.Padding = PaddingMode.PKCS7; aesManaged.Mode = CipherMode.CBC; using (var decryptor = aesManaged.CreateDecryptor(keyBytes, ivBytes)) using (var memoryStream = new MemoryStream(ciphertextBytes)) using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) using (var streamReader = new StreamReader(cryptoStream)) { // Return the decrypted bytes from the decrypting stream. cryptoStream.FlushFinalBlock(); return streamReader.ReadToEnd(); } } } } public static void Decrypt(XmlDocument Doc, string Password) { // Check the arguments. if (null == Doc && null == Password) throw new ArgumentNullException(); if (null == Doc) throw new ArgumentNullException(); if (null == Password) throw new ArgumentNullException(); // Find the EncryptedData element in the XmlDocument. XmlElement encryptedElement = Doc.GetElementsByTagName("EncryptedData")[0] as XmlElement; // If the EncryptedData element was not found, quietly return. if (encryptedElement == null) { LOGGER.Warn("The EncryptedData element was not found."); return; } XmlElement cipherText = Doc.GetElementsByTagName("CipherValue")[0] as XmlElement; if (cipherText == null) { throw new ArgumentException($"Missing CipherValue in \n {Doc.InnerXml}"); } // Create an EncryptedData object and populate it. EncryptedData edElement = new EncryptedData(); edElement.LoadXml(encryptedElement); // Create a new EncryptedXml object. EncryptedXml exml = new EncryptedXml(); // Decrypt the element using the symmetric key. string plaintext = CryptoUtil.Decrypt(cipherText.InnerText, Password); byte[] rgbOutput = System.Text.UTF8Encoding.UTF8.GetBytes(plaintext); // Replace the encryptedData element with the plaintext XML element. exml.ReplaceData(encryptedElement, rgbOutput); }
单独使用重载的加密/解密方法处理纯文本Payload时无报错,问题仅出现在XML加密场景中。
问题解决
核心错误点
解密方法Decrypt(string ciphertext, string key)中,在读取模式的CryptoStream上调用了FlushFinalBlock(),这是错误操作:
FlushFinalBlock()仅适用于CryptoStreamMode.Write模式,用于将最后一块加密数据写入并处理填充;- 在读取模式下调用该方法会干扰解密流程,导致填充验证失败,触发"Padding is invalid and cannot be removed"错误。
修复步骤
- 移除解密方法中的错误调用
修改Decrypt(string ciphertext, string key)方法,删除cryptoStream.FlushFinalBlock();这一行:
using (var decryptor = aesManaged.CreateDecryptor(keyBytes, ivBytes)) using (var memoryStream = new MemoryStream(ciphertextBytes)) using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) using (var streamReader = new StreamReader(cryptoStream)) { // 移除 cryptoStream.FlushFinalBlock(); return streamReader.ReadToEnd(); }
- 验证参数一致性
确保_saltSize是固定常量(比如private const int _saltSize = 16;),且加密、解密流程中该值保持一致,避免密钥/IV推导错误。
额外优化建议
- 优先使用
EncryptedXml内置的EncryptData方法处理XML元素加密,减少自定义流程带来的编码、填充类错误; - 加密方法中异常返回空字符串的逻辑可能隐藏问题,建议直接抛出异常或返回
null,便于排查错误。
内容的提问来源于stack exchange,提问作者Andrew Tyson
相关产品推荐
相关产品推荐

