Bouncy Castle/CryptoJS与Pycryptodome:Python AES解密填充差异排查
AES CBC跨库解密填充问题解析
填充实现差异
Bouncy Castle与CryptoJS默认均采用PKCS#7填充(注:PKCS#5是PKCS#7的子集,针对8字节块大小;AES块为16字节,因此两者效果完全一致,部分库会混用这两个名称)。而Python生态的Pycryptodome、Cryptography库默认不会自动处理填充:
- Pycryptodome需手动调用
unpad方法指定填充规则,若忽略则直接返回包含填充字节的原始解密数据,触发后续处理错误。 - Cryptography库默认按无填充模式解密,若未显式配置PKCS#7去填充逻辑,会直接抛出填充验证失败的异常。
少数场景下,若Bouncy Castle被显式配置为Zero填充(不足块大小的部分补0x00),而CryptoJS同步设置了对应填充规则,此时Python端若仍用PKCS#7去填充,必然出现错误。
Python正确解密方案
前置确认
需与加密端完全一致的核心参数:
- 密钥:AES-128对应16字节长度,需确保编码(通常为UTF-8)与加密端一致
- IV:CBC模式必须用16字节随机IV,多数场景下会拼接在密文头部(前16字节)
- 填充方式:确认是PKCS#7还是Zero填充
1. Pycryptodome实现
PKCS#7填充场景
from Crypto.Cipher import AES from Crypto.Util.Padding import unpad # 示例:密文为Base64编码,需先解码;若为原始bytes则直接使用 ciphertext_b64 = "YOUR_CIPHERTEXT_BASE64" ciphertext = bytes.fromhex(ciphertext_b64) # 或用base64.b64decode,根据加密端编码方式调整 key = b"YOUR_16BYTE_KEY" # 字符串转bytes用 key = "密钥字符串".encode("utf-8") iv = ciphertext[:16] # 从密文头部提取IV real_ciphertext = ciphertext[16:] # 解密并去填充 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(real_ciphertext), AES.block_size) print(plaintext.decode("utf-8"))
Zero填充场景
from Crypto.Cipher import AES # 密钥、IV、密文处理同上 cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = cipher.decrypt(real_ciphertext) # 移除末尾的0x00填充 plaintext = plaintext.rstrip(b"\x00") print(plaintext.decode("utf-8"))
2. Cryptography实现
PKCS#7填充场景
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding from cryptography.hazmat.backends import default_backend key = b"YOUR_16BYTE_KEY" ciphertext = bytes.fromhex("YOUR_CIPHERTEXT_BASE64") iv = ciphertext[:16] real_ciphertext = ciphertext[16:] # 解密 cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() decrypted_data = decryptor.update(real_ciphertext) + decryptor.finalize() # 去PKCS#7填充 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() plaintext = unpadder.update(decrypted_data) + unpadder.finalize() print(plaintext.decode("utf-8"))
Zero填充场景
解密后直接调用plaintext.rstrip(b"\x00")移除填充字节即可,逻辑同Pycryptodome。
常见错误排查
- 编码不匹配:加密端用UTF-8编码密钥/明文,Python端误用其他编码(如GBK),会导致密钥长度或内容错误。
- IV未分离:未从密文中提取IV,直接用完整密文解密,会导致解密结果混乱且触发填充错误。
- 填充规则误判:若加密端用Zero填充,Python端强行用PKCS#7去填充,会抛出
ValueError异常,此时需检查加密端的Bouncy Castle配置。
内容的提问来源于stack exchange,提问作者Marek Marczak
相关产品推荐
相关产品推荐

