使用Fernet和base64在Python实现安全密码管理时遇InvalidToken错误
解决Fernet InvalidToken错误的方案
问题根源
你直接将load_key()获取的密钥与主密码拼接后作为Fernet密钥,这违反了Fernet的核心规则:Fernet要求密钥必须是URL-safe的base64编码32字节数据。直接拼接主密码会导致密钥格式完全失效,加密和解密使用的密钥不匹配,最终触发InvalidToken错误。
修复步骤
- 用主密码+随机盐的方式,通过哈希算法派生合法的Fernet密钥
- 保存盐值(解密时需要用相同盐派生密钥)
- 修正加密解密的逻辑,确保前后密钥一致
修正后的代码
from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import base64 import os def generate_and_save_key(master_pwd): # 生成16字节随机盐 salt = os.urandom(16) # 初始化密钥派生函数 kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480000, ) # 派生符合Fernet要求的密钥 key = base64.urlsafe_b64encode(kdf.derive(master_pwd.encode())) # 盐+密钥写入文件(前16字节是盐,后面是密钥) with open("key.key", "wb") as key_file: key_file.write(salt + key) def load_key(master_pwd): with open("key.key", "rb") as key_file: data = key_file.read() # 拆分盐和密钥 salt = data[:16] stored_key = data[16:] # 用相同盐重新派生密钥并验证主密码 kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480000, ) try: kdf.verify(master_pwd.encode(), stored_key) except Exception: raise ValueError("主密码错误") return stored_key master_pwd = input("Enter master password: ") # 第一次运行时取消注释生成密钥,之后注释该行避免覆盖 # generate_and_save_key(master_pwd) try: key = load_key(master_pwd) except FileNotFoundError: print("未找到密钥文件,请先生成密钥") exit() fer = Fernet(key) def view(): try: with open("passwords.txt", 'r') as f: for line in f.readlines(): data = line.rstrip() if not data: continue user, passw = data.split("|") decrypted_pwd = fer.decrypt(passw.encode()).decode() print(f"Username: {user} | Password: {decrypted_pwd}") except FileNotFoundError: print("暂无保存的密码") def create(): name = input("username: ") pwd = input("password: ") encrypted_pwd = fer.encrypt(pwd.encode()).decode() with open("passwords.txt", 'a') as f: f.write(f"{name}|{encrypted_pwd}\n") print("密码已保存") while True: mode = input("Create a new password or view existing ones (view, create), press x to quit? ").lower() if mode == "x": break if mode == "view": view() elif mode == "create": create() else: print("无效指令,请重新输入") continue
额外提示
- 首次运行必须取消注释
generate_and_save_key(master_pwd)生成密钥文件,后续运行要注释该行,防止密钥被覆盖导致旧密码无法解密 - 新增了主密码验证逻辑,输入错误密码会直接报错,避免后续解密失败
- 处理了文件不存在的情况,提升程序容错性
内容的提问来源于stack exchange,提问作者victor
相关产品推荐
相关产品推荐

