密码管理器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错误。
此外,代码每次运行都会生成新密码并追加到文件,读取时尝试解密所有历史记录,但密钥不匹配必然失败。
修复步骤
固定或安全存储密钥
- 若要长期使用同一套密钥,需将密钥保存(如加密后存入文件、环境变量),而非每次运行都生成新密钥。示例:
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等算法派生密钥。
- 若要长期使用同一套密钥,需将密钥保存(如加密后存入文件、环境变量),而非每次运行都生成新密钥。示例:
分离加密解密逻辑
- 当前代码每次运行都会执行全流程,不符合密码管理器的使用逻辑。建议添加功能分支,比如通过命令行参数选择生成加密新密码,或读取解密已有密码。
优化文件读写格式
- 确保写入的每行格式为
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
相关产品推荐
相关产品推荐

