You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

PyCryptodome AES报错:PKCS#7填充不正确,求排查解决

排查AES解密时PKCS#7填充错误的原因与解决方案

首先,这个ValueError("PKCS#7填充不正确。")本质上是解密后的数据不符合PKCS#7填充规则,通常是加密解密流程中某个环节出错,导致解密出的明文乱码,无法通过填充校验。结合你给出的代码片段,咱们逐个排查可能的问题:

1. 未正确使用PyCryptodome的填充/去填充工具

你的加密代码里用到了Padding.pad,但PyCryptodome官方的PKCS#7填充工具是Crypto.Util.Padding下的padunpad函数。如果解密时没有调用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

火山引擎 最新活动