PyCryptodome AES报错:PKCS#7填充不正确,求排查解决
排查AES解密时PKCS#7填充错误的原因与解决方案
首先,这个ValueError("PKCS#7填充不正确。")本质上是解密后的数据不符合PKCS#7填充规则,通常是加密解密流程中某个环节出错,导致解密出的明文乱码,无法通过填充校验。结合你给出的代码片段,咱们逐个排查可能的问题:
1. 未正确使用PyCryptodome的填充/去填充工具
你的加密代码里用到了Padding.pad,但PyCryptodome官方的PKCS#7填充工具是Crypto.Util.Padding下的pad和unpad函数。如果解密时没有调用unpad,或者用了错误的填充工具,就会直接触发这个错误。
解决办法:
- 先导入正确的模块:
from Crypto.Util.Padding import pad, unpad
- 修改加密和解密函数,确保加密时对字节数据填充,解密后去除填充:
加密时要把字符串转成字节(因为pad只接受字节类型),解密后用unpad处理再转回字符串:
# 修正后的加密函数片段 plaintext_bytes = plaintext.encode('utf-8') # 字符串转字节 plaintext_padded = pad(plaintext_bytes, AES.block_size) # 修正后的解密函数完整逻辑 def decryptString(ciphertextHex, key): ciphertext = binascii.unhexlify(ciphertextHex) iv = ciphertext[:AES.block_size] ciphertext = ciphertext[AES.block_size:] cipher = AES.new(key, AES.MODE_CBC, iv) plaintext_bytes = cipher.decrypt(ciphertext) try: # 去除PKCS#7填充并转回字符串 plaintext = unpad(plaintext_bytes, AES.block_size).decode('utf-8') return plaintext except ValueError as e: print(f"填充校验失败:{e},大概率是密钥错误或密文损坏") raise
2. 密钥长度不符合AES要求
AES对密钥长度有严格要求:必须是16(AES-128)、24(AES-192)或32(AES-256)字节。如果你的key是字符串直接传入,或者通过哈希生成后长度不对,会导致加密解密用的密钥无效,解密出的明文完全乱码,进而触发填充错误。
解决办法:
- 把字符串密钥转换成符合长度的字节密钥,比如用SHA-256生成32字节的AES-256密钥:
def get_valid_aes_key(key_str): # 将字符串密钥哈希为32字节的AES-256密钥 return hashlib.sha256(key_str.encode('utf-8')).digest()
- 加密和解密时都使用这个处理后的密钥,确保长度正确:
# 加密调用示例 key = get_valid_aes_key("your_secret_key") encrypted = encryptString("要加密的内容", key) # 解密调用示例 decrypted = decryptString(encrypted, key)
3. 加密和解密的密钥不一致
这是最常见的原因:加密用的密钥和解密用的密钥不是同一个(比如编码方式不同,一个用UTF-8,一个用GBK;或者密钥字符串输入错误)。密钥不一致会导致解密出的内容完全是乱码,根本无法通过PKCS#7填充的校验。
解决办法:
- 确保加密和解密使用完全相同的密钥:
- 统一用UTF-8编码字符串密钥;
- 可以在加密前打印密钥的十六进制值,解密时也打印对比,确认两者一致:
print("加密密钥十六进制:", key.hex())
4. 密文在传输/存储中损坏
你加密后返回的是IV+密文的十六进制字符串,如果这个字符串在传输或存储时被修改(比如丢失字符、混入无效字符),解密时得到的密文就是错误的,自然无法通过填充校验。
解决办法:
- 确保密文的完整性:可以在加密后计算密文的哈希值(比如SHA-256),解密前先校验哈希,确认密文没有被修改;
- 避免手动编辑密文字符串,传输时优先用二进制方式,或者确保文本传输不会破坏十六进制格式。
完整可运行的修正代码
这里给你整理了完整的修正代码,包含所有正确的流程:
from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes import hashlib import binascii def get_valid_aes_key(key_str): # 生成符合AES-256要求的32字节密钥 return hashlib.sha256(key_str.encode('utf-8')).digest() def encryptString(plaintext, key): # 字符串转字节并填充 plaintext_bytes = plaintext.encode('utf-8') plaintext_padded = pad(plaintext_bytes, AES.block_size) # 生成随机IV(CBC模式必须用随机IV) iv = get_random_bytes(AES.block_size) # 创建AES CBC加密器 cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(plaintext_padded) # 返回IV+密文的十六进制字符串 return (iv + ciphertext).hex() def decryptString(ciphertextHex, key): # 解析十六进制字符串为字节 ciphertext = binascii.unhexlify(ciphertextHex) # 分离IV和密文 iv = ciphertext[:AES.block_size] ciphertext = ciphertext[AES.block_size:] # 创建AES CBC解密器 cipher = AES.new(key, AES.MODE_CBC, iv) # 解密得到带填充的字节数据 plaintext_bytes = cipher.decrypt(ciphertext) try: # 去除填充并转回字符串 plaintext = unpad(plaintext_bytes, AES.block_size).decode('utf-8') return plaintext except ValueError as e: print(f"填充错误:{e},可能原因:密钥不匹配、密文损坏、填充方式错误") raise # 测试示例 if __name__ == "__main__": secret_key_str = "my_secure_password_12345" aes_key = get_valid_aes_key(secret_key_str) original_text = "Hello, AES CBC Encryption with PKCS#7 Padding!" encrypted_text = encryptString(original_text, aes_key) print(f"加密结果:{encrypted_text}") decrypted_text = decryptString(encrypted_text, aes_key) print(f"解密结果:{decrypted_text}")
内容的提问来源于stack exchange,提问作者Ryper




