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

Python密码管理器解密时出现cryptography.fernet.InvalidToken错误

Python密码管理器解密时触发cryptography.fernet.InvalidToken错误

我基于Fernet实现了一个Python密码管理器,但在检索密码的解密环节遇到了cryptography.fernet.InvalidToken错误,以下是我的代码和报错信息:

from cryptography.fernet import Fernet
import argparse
import os
import secrets
import string

class PasswordManager:
    def __init__(self):
        self.master_key = None

    def set_master_password(self, master_password):
        self.master_key = Fernet(Fernet.generate_key())
        self.master_password = master_password.encode()

    def encrypt_password(self, password):
        return self.master_key.encrypt(password.encode())

    def decrypt_password(self, encrypted_password):
        return self.master_key.decrypt(encrypted_password).decode()

    def generate_password(self, length=12):
        characters = string.ascii_letters + string.digits + string.punctuation
        if length < 8:
            raise ValueError("Password length must be at least 8 characters.")
        return ''.join(secrets.choice(characters) for _ in range(length))

    def store_password(self, service, password):
        encrypted_password = self.encrypt_password(password)
        with open('passwords.txt', 'a') as file:
            file.write(f"{service}: {encrypted_password}\n")

    def retrieve_password(self, service):
        with open('passwords.txt', 'r') as file:
            for line in file:
                entry = line.split(': ')
                if entry[0] == service:
                    encrypted_password = entry[1].encode()
                    return self.decrypt_password(encrypted_password)
            return None

def main():
    parser = argparse.ArgumentParser(description="Password Manager")
    parser.add_argument('--action', choices=['store', 'retrieve', 'generate'], help='Operation to perform', required=True)
    parser.add_argument('--master-password', help='Master password')
    parser.add_argument('--service', help='Service name')
    parser.add_argument('--password', help='Password')
    parser.add_argument('--length', type=int, default=12, help='Length of generated password')

    args = parser.parse_args()
    password_manager = PasswordManager()

    if args.action == 'store':
        if not args.master_password or not args.service or not args.password:
            print("Please provide master password, service, and password to store.")
        else:
            password_manager.set_master_password(args.master_password)
            password_manager.store_password(args.service, args.password)
            print("Password stored successfully.")

    elif args.action == 'retrieve':
        if not args.master_password or not args.service:
            print("Please provide master password and service to retrieve password.")
        else:
            password_manager.set_master_password(args.master_password)
            password = password_manager.retrieve_password(args.service)
            if password:
                print(f"Retrieved password for {args.service}: {password}")
            else:
                print(f"No password found for {args.service}.")

    elif args.action == 'generate':
        if not args.master_password:
            print("Please provide master password to generate a password.")
        else:
            password_manager.set_master_password(args.master_password)
            generated_password = password_manager.generate_password(args.length)
            print(f"Generated Password: {generated_password}")

if __name__ == '__main__':
    main()

报错信息

Traceback (most recent call last):
  File "/home/valan/Desktop/PasswordManager/passmanager_jsv/passwd_manager.py", line 80, in <module>
    main()
  File "/home/valan/Desktop/PasswordManager/passmanager_jsv/passwd_manager.py", line 65, in main
    password = password_manager.retrieve_password(args.service)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/valan/Desktop/PasswordManager/passmanager_jsv/passwd_manager.py", line 38, in retrieve_password
    return self.decrypt_password(encrypted_password)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/valan/Desktop/PasswordManager/passmanager_jsv/passwd_manager.py", line 19, in decrypt_password
    return self.master_key.decrypt(encrypted_password).decode()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/cryptography/fernet.py", line 85, in decrypt
    timestamp, data = Fernet._get_unverified_token_data(token)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/cryptography/fernet.py", line 121, in _get_unverified_token_data
    raise InvalidToken
cryptography.fernet.InvalidToken

核心问题分析

  1. 密钥完全不匹配:每次调用set_master_password都会生成全新的Fernet密钥,完全忽略传入的主密码。存储密码用临时密钥,检索时又生成新密钥,自然无法解密旧内容。
  2. 换行符干扰:从文件读取加密密码时,entry[1]包含末尾的换行符\n,直接编码后会导致解密时token格式无效。

修复方案

1. 基于主密码派生固定密钥

Fernet要求使用URL安全的32字节密钥,我们用PBKDF2HMAC算法从主密码派生密钥,同时保存随机盐(salt)确保相同密码能生成相同密钥。

2. 清理文件读取的换行符

读取文件行时用strip()移除末尾的换行符和空白字符。

修复后的完整代码

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
import base64
import argparse
import os
import secrets
import string

class PasswordManager:
    def __init__(self):
        self.master_key = None
        self.salt_file = "salt.txt"

    def _get_or_generate_salt(self):
        # 获取或生成盐,确保相同主密码能派生相同密钥
        if os.path.exists(self.salt_file):
            with open(self.salt_file, "rb") as f:
                return f.read()
        else:
            salt = secrets.token_bytes(16)
            with open(self.salt_file, "wb") as f:
                f.write(salt)
            return salt

    def set_master_password(self, master_password):
        # 从主密码派生Fernet密钥
        salt = self._get_or_generate_salt()
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100000,
            backend=default_backend()
        )
        key = base64.urlsafe_b64encode(kdf.derive(master_password.encode()))
        self.master_key = Fernet(key)

    def encrypt_password(self, password):
        return self.master_key.encrypt(password.encode())

    def decrypt_password(self, encrypted_password):
        return self.master_key.decrypt(encrypted_password).decode()

    def generate_password(self, length=12):
        characters = string.ascii_letters + string.digits + string.punctuation
        if length < 8:
            raise ValueError("密码长度至少为8位。")
        return ''.join(secrets.choice(characters) for _ in range(length))

    def store_password(self, service, password):
        encrypted_password = self.encrypt_password(password)
        # 存储时解码为字符串,避免读取时引入多余字符
        with open('passwords.txt', 'a') as file:
            file.write(f"{service}: {encrypted_password.decode()}\n")

    def retrieve_password(self, service):
        with open('passwords.txt', 'r') as file:
            for line in file:
                line = line.strip()  # 移除换行符和空白
                if not line:
                    continue
                entry = line.split(': ')
                if len(entry) != 2:
                    continue
                if entry[0] == service:
                    encrypted_password = entry[1].encode()
                    return self.decrypt_password(encrypted_password)
            return None

def main():
    parser = argparse.ArgumentParser(description="密码管理器")
    parser.add_argument('--action', choices=['store', 'retrieve', 'generate'], help='操作类型', required=True)
    parser.add_argument('--master-password', help='主密码')
    parser.add_argument('--service', help='服务名称')
    parser.add_argument('--password', help='密码')
    parser.add_argument('--length', type=int, default=12, help='生成密码的长度')

    args = parser.parse_args()
    password_manager = PasswordManager()

    if args.action == 'store':
        if not args.master_password or not args.service or not args.password:
            print("请提供主密码、服务名称和要存储的密码。")
        else:
            password_manager.set_master_password(args.master_password)
            password_manager.store_password(args.service, args.password)
            print("密码存储成功。")

    elif args.action == 'retrieve':
        if not args.master_password or not args.service:
            print("请提供主密码和服务名称以检索密码。")
        else:
            password_manager.set_master_password(args.master_password)
            password = password_manager.retrieve_password(args.service)
            if password:
                print(f"检索到{args.service}的密码: {password}")
            else:
                print(f"未找到{args.service}的密码。")

    elif args.action == 'generate':
        if not args.master_password:
            print("请提供主密码以生成密码。")
        else:
            password_manager.set_master_password(args.master_password)
            generated_password = password_manager.generate_password(args.length)
            print(f"生成的密码: {generated_password}")

if __name__ == '__main__':
    main()

关键修复点说明

  • 密钥派生逻辑:使用PBKDF2HMAC算法结合主密码和固定盐生成密钥,确保相同主密码始终生成一致的解密密钥。
  • 盐存储机制:首次运行生成随机盐并保存,后续读取该盐保证密钥一致性。
  • 文件读取优化:用strip()清理换行符,存储时将加密bytes解码为字符串,避免读取时引入无效字符。
  • 本地化提示:将操作提示改为中文,提升使用体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 07:39:52