You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

密码管理器AES Padding错误排查及加密存储方案咨询

问题:密码管理器二次运行解密时触发Padding错误

首次运行代码可正常生成、加密、解密密码并写入Password.txt,但第二次运行读取文件解密时出现以下错误:

`66lHmLVe~
The password is strong
Encrypted password: b'\xe1\x19\x04\x88</\xf4,\xd9\x10\xe9\xef\x8f4\x00\t'
Decrypted password: `66lHmLVe~
Traceback (most recent call last):
  File "c:\Users\basse\Project_3.py", line 78, in <module>
    decrypted_password = decrypt_data(bytes.fromhex(iv), bytes.fromhex(encrypted_password), key)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\basse\Project_3.py", line 55, in decrypt_data
    decrypted_data = unpad(cipher.decrypt(encrypted_data), block_size)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\basse\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\Crypto\Util\Padding.py", line 92, in unpad
    raise ValueError("Padding is incorrect.")
ValueError: Padding is incorrect.

完整代码

import re
import random
import string

def password_generator(size=10):
    if size <= 8:
        print("Size must be at least 4")
        return None
    password = []
    while len(password) < size:
        password.append(random.choice(string.ascii_lowercase))  # ensure at least one lowercase letter
        if len(password) < size:
            password.append(random.choice(string.ascii_uppercase))  # ensure at least one uppercase letter
        if len(password) < size:
            password.append(random.choice(string.digits))  # ensure at least one digit
        if len(password) < size:
            password.append(random.choice(string.punctuation))  # ensure at least one special character
    random.shuffle(password)  # shuffle to remove the predictability
    return ''.join(password)

def password_checker(password):
    if len(password) >= 8:
        if bool(re.match(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d])', password)):
            print("The password is strong")
        else:
            print("The password is weak")
    else:
        print("You have entered a short or invalid password.")

# Generate a password
generated_password = password_generator()
print(generated_password)

# Check the generated password
password_checker(generated_password)

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import os

# AES requires that plaintexts be a multiple of 16, so we need to pad the data
block_size = 16

# Generate a random 128-bit IV for AES
iv = get_random_bytes(16)

def encrypt_data(iv,data, key):
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    encrypted_data = cipher.encrypt(pad(data.encode('utf-8'), block_size))
    return cipher.iv, encrypted_data

def decrypt_data(iv, encrypted_password, key):
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    decrypted_data = unpad(cipher.decrypt(encrypted_password), block_size)
    return decrypted_data.decode('utf-8')

# Generate a random 256-bit key for AES
key = get_random_bytes(32)

# Encrypt the generated password
iv, encrypted_password = encrypt_data(iv,generated_password, key)
print(f"Encrypted password: {encrypted_password}")

# Decrypt the encrypted password
decrypted_password = decrypt_data(iv, encrypted_password, key)
print(f"Decrypted password: {decrypted_password}")

# Save the encrypted password to a file
with open('C:/Users/foo/Project/Password.txt', 'a', encoding='utf-8') as f:
    f.write(f"{iv.hex()}:{encrypted_password.hex()}\n")
    
# Read the encrypted password from the file
with open('C:/Users/foo/Project/Password.txt', 'r', encoding='utf-8') as f:
    for line in f.readlines():
        line = line.strip()  # Remove the trailing newline character
        iv, encrypted_password = line.split(':')
        decrypted_password = decrypt_data(bytes.fromhex(iv), bytes.fromhex(encrypted_password), key)

问题原因及修复方案

核心问题

每次运行代码时都会重新生成AES密钥,但解密操作必须使用与加密该密码时完全一致的密钥。当前代码中key = get_random_bytes(32)在每次启动时都会生成新密钥,导致第二次运行时用新密钥解密旧密钥加密的数据,最终因解密结果为乱码无法正确解填充,触发Padding错误。

此外,代码每次运行都会生成新密码并追加到文件,读取时尝试解密所有历史记录,但密钥不匹配必然失败。

修复步骤

  1. 固定或安全存储密钥

    • 若要长期使用同一套密钥,需将密钥保存(如加密后存入文件、环境变量),而非每次运行都生成新密钥。示例:
      import pickle
      try:
          # 读取已保存的密钥
          with open('aes_key.bin', 'rb') as f:
              key = pickle.load(f)
      except FileNotFoundError:
          # 首次运行生成并保存密钥
          key = get_random_bytes(32)
          with open('aes_key.bin', 'wb') as f:
              pickle.dump(key, f)
      
    • 注意:明文存储密钥存在安全风险,实际场景建议用用户输入的主密码通过PBKDF2、Argon2等算法派生密钥。
  2. 分离加密解密逻辑

    • 当前代码每次运行都会执行全流程,不符合密码管理器的使用逻辑。建议添加功能分支,比如通过命令行参数选择生成加密新密码,或读取解密已有密码。
  3. 优化文件读写格式

    • 确保写入的每行格式为IV十六进制:密文十六进制,无多余空格或换行。当前的line.strip()已处理换行,需注意不要在分隔符:前后添加空格。

优化后的示例代码(简化版)

import re
import random
import string
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import pickle

def password_generator(size=10):
    if size <= 8:
        print("密码长度至少为8位")
        return None
    password = []
    # 确保包含四类字符
    password.append(random.choice(string.ascii_lowercase))
    password.append(random.choice(string.ascii_uppercase))
    password.append(random.choice(string.digits))
    password.append(random.choice(string.punctuation))
    # 填充剩余长度
    while len(password) < size:
        password.append(random.choice(string.ascii_letters + string.digits + string.punctuation))
    random.shuffle(password)
    return ''.join(password)

def password_checker(password):
    if len(password) < 8:
        print("密码过短,至少需要8位")
        return
    if bool(re.match(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d])', password)):
        print("密码强度:强")
    else:
        print("密码强度:弱,建议包含大小写字母、数字和特殊字符")

def load_or_generate_key():
    """加载已有的AES密钥,没有则生成并保存"""
    key_path = 'aes_key.bin'
    try:
        with open(key_path, 'rb') as f:
            return pickle.load(f)
    except FileNotFoundError:
        key = get_random_bytes(32)
        with open(key_path, 'wb') as f:
            pickle.dump(key, f)
        print("已生成新的AES密钥并保存到aes_key.bin")
        return key

def encrypt_password(plain_password, key):
    iv = get_random_bytes(16)
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    encrypted_data = cipher.encrypt(pad(plain_password.encode('utf-8'), AES.block_size))
    return iv.hex(), encrypted_data.hex()

def decrypt_password(iv_hex, encrypted_hex, key):
    iv = bytes.fromhex(iv_hex)
    encrypted_data = bytes.fromhex(encrypted_hex)
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
    return decrypted_data.decode('utf-8')

def save_encrypted_password(iv_hex, encrypted_hex, file_path='Password.txt'):
    with open(file_path, 'a', encoding='utf-8') as f:
        f.write(f"{iv_hex}:{encrypted_hex}\n")

def load_and_decrypt_all(file_path='Password.txt', key=None):
    if not key:
        key = load_or_generate_key()
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                iv_hex, encrypted_hex = line.split(':')
                try:
                    plain_pwd = decrypt_password(iv_hex, encrypted_hex, key)
                    print(f"解密后的密码:{plain_pwd}")
                except ValueError as e:
                    print(f"解密失败:{e},可能密钥不匹配")
    except FileNotFoundError:
        print("密码文件不存在")

if __name__ == "__main__":
    key = load_or_generate_key()
    # 生成新密码并加密保存
    new_pwd = password_generator()
    if new_pwd:
        print(f"生成的新密码:{new_pwd}")
        password_checker(new_pwd)
        iv_hex, encrypted_hex = encrypt_password(new_pwd, key)
        save_encrypted_password(iv_hex, encrypted_hex)
        print("密码已加密保存")
    # 读取并解密所有已保存的密码
    print("\n读取已保存的密码:")
    load_and_decrypt_all(key=key)

其他存储方案建议

  • 若不想用纯文本文件,可考虑用SQLite数据库存储,每条记录包含IV、密文、备注等信息,比纯文本更易管理。
  • 密钥绝对不能明文存储,建议用用户输入的主密码通过PBKDF2、Argon2等算法派生密钥,即使密钥存储文件泄露,无主密码也无法解密。

内容的提问来源于stack exchange,提问作者Bassel 1000

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 11:48:10