Python 实现 PBKDF2 + AES-256-GCM 加密存储

PBKDF2 + AES-GCM 是本地加密存储(钱包 vault、配置文件保护)的标准方案:密码经 PBKDF2 派生出密钥,AES-GCM 提供加密和完整性验证。

加密流程

Password

PBKDF2-HMAC-SHA512(iterations=600000, salt=随机16字节)

32 字节 AES Key

AES-256-GCM(nonce=随机16字节)

Ciphertext + Tag

安装依赖

pip install pycryptodome

完整实现

import json
import os
import base64
from hashlib import pbkdf2_hmac
from Crypto.Cipher import AES


PBKDF2_ITERATIONS = 600_000  # NIST 2023 推荐最低值


def derive_key(password: str, salt: bytes) -> bytes:
    return pbkdf2_hmac(
        "sha512",
        password.encode("utf-8"),
        salt,
        PBKDF2_ITERATIONS,
        dklen=32,  # AES-256
    )


def encrypt(password: str, plaintext: str) -> dict:
    salt  = os.urandom(16)
    nonce = os.urandom(16)
    key   = derive_key(password, salt)

    cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
    ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode("utf-8"))

    return {
        "salt":       base64.b64encode(salt).decode(),
        "nonce":      base64.b64encode(nonce).decode(),
        "ciphertext": base64.b64encode(ciphertext).decode(),
        "tag":        base64.b64encode(tag).decode(),
    }


def decrypt(password: str, vault: dict) -> str:
    salt       = base64.b64decode(vault["salt"])
    nonce      = base64.b64decode(vault["nonce"])
    ciphertext = base64.b64decode(vault["ciphertext"])
    tag        = base64.b64decode(vault["tag"])

    key    = derive_key(password, salt)
    cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)

    plaintext = cipher.decrypt_and_verify(ciphertext, tag)
    return plaintext.decode("utf-8")

使用示例

# 加密
secret = '{"privateKey": "0xabcd..."}'
vault = encrypt("my_strong_password", secret)

# 存储为 JSON
with open("vault.json", "w") as f:
    json.dump(vault, f, indent=2)

# 解密
with open("vault.json") as f:
    vault = json.load(f)

plaintext = decrypt("my_strong_password", vault)
print(plaintext)

vault.json 格式:

{
  "salt":       "base64...",
  "nonce":      "base64...",
  "ciphertext": "base64...",
  "tag":        "base64..."
}

为什么选 AES-GCM 而非 CBC

模式加密完整性验证推荐
AES-CBC❌(需额外 HMAC)不推荐新项目
AES-GCM✅(内置 Tag)✅ 推���
AES-CTR需配合 HMAC

GCM 模式同时提供加密和认证,decrypt_and_verify 会在解密前验证 Tag,密文被篡改时抛出 ValueError

关键参数说明

PBKDF2 迭代次数:越高越安全,NIST 2023 建议 SHA-512 至少 210,000 次,MetaMask 等钱包用 600,000 次。代价是派生速度变慢(约 0.5~2 秒),对正常用户不感知,但让暴力破解成本提高数十万倍。

salt 唯一性:每次加密生成新的随机 salt,防止相同密码产生相同密钥(彩虹表攻击)。salt 不需要保密,公开存储即可。

nonce 唯一性:GCM 的 nonce 绝对不能重用于同一密钥,否则 GCM 的安全性完全崩溃。每次加密随机生成是最安全的做法。

密码错误时的行为

try:
    plaintext = decrypt("wrong_password", vault)
except ValueError:
    print("密码错误或数据被篡改")

decrypt_and_verify 验证 Tag 失败时抛出 ValueError,不会泄漏任何明文信息。