AES解密Android客户端加密数据时出现填充无效错误
Android加密逻辑
Android端加密代码如下:
object AESHelper { fun encrypt(data: String, sharedSecret: ByteArray): String { val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") val secretKeySpec = SecretKeySpec(sharedSecret, "AES") val iv = ByteArray(16) // Zero IV cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(iv)) val encryptedBytes = cipher.doFinal(Base64.decode(data, Base64.NO_WRAP)) return Base64.encodeToString(encryptedBytes, Base64.NO_WRAP).trim() } }
核心配置:
- AES-CBC模式
- PKCS7填充
- 16字节全零初始化向量(IV)
- IV未附加到密文头部
C#解密逻辑
当前C#端解密代码:
public static string Decrypt(string cipherText, byte[] sharedSecret) { using var aes = new AesCryptoServiceProvider { Key = sharedSecret }; var cipherBytes = Convert.FromBase64String(cipherText); var iv = new byte[aes.BlockSize / 8]; var encryptedBytes = new byte[cipherBytes.Length - iv.Length]; Buffer.BlockCopy(cipherBytes, 0, iv, 0, iv.Length); Buffer.BlockCopy(cipherBytes, iv.Length, encryptedBytes, 0, encryptedBytes.Length); aes.IV = iv; using var decryptor = aes.CreateDecryptor(); var decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length); return Encoding.UTF8.GetString(decryptedBytes); }
问题现象
解密Android端生成的密文时,抛出异常:
System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed.
at Internal.Cryptography.UniversalCryptoDecryptor.DepadBlock(Byte[] block, Int32 offset, Int32 count)
at Internal.Cryptography.UniversalCryptoDecryptor.UncheckedTransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
at Internal.Cryptography.UniversalCryptoTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
at MyNamespace.Decrypt(String cipherText, Byte[] sharedSecret)
已尝试操作
已修改C#逻辑,改用16字节全零IV,不再从密文中提取IV,且确认Android端未将IV附加到密文。
疑问
- 为何使用零IV后仍出现填充无效错误?
- C#解密逻辑还需修改哪些部分以匹配Android客户端的加密逻辑?
- 还有哪些调试步骤可以采取?
问题解答
1. 零IV后仍报错的核心原因
代码存在两个关键不匹配点:
- 加密前的Base64处理逻辑相反:Android端是先把明文
data做Base64解码,再加密;而C#端解密后直接转UTF8,没有做对应的Base64编码还原,导致字节流完全错误,触发填充校验失败。 - 密文截取错误:原C#代码错误地从密文中截取了IV长度的字节,导致实际解密的密文缺失了前16字节,这也是填充错误的直接原因之一。
2. C#解密逻辑的修正点
需要调整两个核心步骤:
- 直接使用全零IV,不再从密文中截取
- 解密后的字节流需要做Base64编码,再转UTF8(逆向匹配Android端的加密前处理)
修正后的C#代码:
public static string Decrypt(string cipherText, byte[] sharedSecret) { using var aes = Aes.Create(); aes.Key = sharedSecret; aes.IV = new byte[16]; // 全零IV,和Android端完全一致 aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; // 显式指定填充模式,避免依赖默认值 var cipherBytes = Convert.FromBase64String(cipherText); using var decryptor = aes.CreateDecryptor(); var decryptedBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); // 关键:Android端加密前是Base64解码明文,解密后需Base64编码还原原始字符串 return Convert.ToBase64String(decryptedBytes); }
3. 调试步骤建议
- 字节流对比校验:
- Android端输出加密前的
Base64.decode(data, Base64.NO_WRAP)字节数组,保存为二进制文件 - C#端解密后输出
decryptedBytes字节数组,保存为二进制文件 - 对比两个文件的字节是否完全一致,验证解密是否正确
- Android端输出加密前的
- 密钥一致性验证:
- 两端分别打印
sharedSecret的Base64字符串,确认密钥完全相同(避免编码或传输过程中出现字节丢失)
- 两端分别打印
- 密文一致性验证:
- Android端直接输出加密后的Base64字符串,C#端用这个字符串测试解密,排除传输过程中的截断、编码转换问题
- 参数显式校验:
- 在C#代码中显式指定所有加密参数(Mode、Padding、IV),避免因环境差异导致默认值变化
内容的提问来源于stack exchange,提问作者sobhan alaeddini

