怎样使用PBKDF2或Argon2派生密钥?

wen IT资讯 269

本文目录导读:

怎样使用PBKDF2或Argon2派生密钥?

  1. 核心概念
  2. 使用 PBKDF2
  3. 使用 Argon2
  4. 如何选择?
  5. 安全注意事项

使用 PBKDF2 或 Argon2 派生密钥是密码学中存储密码或生成加密密钥的标准化做法,下面分别介绍它们的使用方法(概念、步骤和代码示例)。


核心概念

无论是 PBKDF2 还是 Argon2,其核心目的都是将低熵的密码(如用户输入的字符串)通过一个“慢”的、耗费资源的函数,生成一个高熵的、固定长度的密钥或哈希值

  • 加盐 (Salt):必须为每次派生生成一个唯一的、随机的盐(建议 16-32 字节),盐用来防止彩虹表攻击。
  • 迭代/内存/并行度:这些参数是“慢”的核心,用于抵抗暴力破解。
  • 输出长度 (dkLen):你希望生成的密钥长度(32 字节用于 AES-256)。

使用 PBKDF2

PBKDF2 是较老的算法,但依然被广泛支持,它主要依赖迭代次数来增加计算成本。

推荐参数

  • 哈希算法:HMAC-SHA256 或 HMAC-SHA512(推荐 SHA-512)。
  • 盐长度:16 字节(128 位)或更长。
  • 迭代次数:2025 年建议至少 600000 次(对于 SHA-256)或 210000 次(对于 SHA-512),请根据目标设备的性能调整,目标是一个哈希计算耗时约 0.5-1 秒。

Python 示例(使用标准库 hashlib

import hashlib
import os
def derive_key_pbkdf2(password: str, salt: bytes = None, dklen: int = 32) -> tuple:
    """
    使用 PBKDF2-HMAC-SHA256 派生密钥。
    返回 (派生密钥, 盐)
    """
    if salt is None:
        salt = os.urandom(16)  # 生成 16 字节随机盐
    key = hashlib.pbkdf2_hmac(
        hash_name='sha256',  # 或 'sha512'
        password=password.encode('utf-8'),
        salt=salt,
        iterations=600000,  # 请根据硬件调整
        dklen=dklen
    )
    return key, salt
# 使用示例
password = "MySecurePassword123!"
key, salt = derive_key_pbkdf2(password)
print(f"派生密钥 (hex): {key.hex()}")
print(f"盐 (hex): {salt.hex()}")

Go 示例(标准库 crypto/pbkdf2

package main
import (
    "crypto/rand"
    "crypto/sha256"
    "fmt"
    "golang.org/x/crypto/pbkdf2"
)
func main() {
    password := []byte("MySecurePassword123!")
    salt := make([]byte, 16)
    rand.Read(salt) // 生成随机盐
    // 派生 32 字节密钥
    key := pbkdf2.Key(password, salt, 600000, 32, sha256.New)
    fmt.Printf("派生密钥 (hex): %x\n", key)
    fmt.Printf("盐 (hex): %x\n", salt)
}

注意事项

  • 迭代次数需要定期随着硬件性能提升而增加。
  • PBKDF2 对 GPU/ASIC 并行攻击的抵抗能力较弱(因为它是运算密集型而非内存密集型)。

使用 Argon2

Argon2 是 2015 年密码哈希竞赛的获胜者,比 PBKDF2 更安全(同时占用内存,抵抗 GPU/ASIC 更强),推荐使用 Argon2id(兼顾侧信道和 GPU 攻击)。

推荐参数(2025 年)

  • 版本:Argon2id (v=19)。
  • 盐长度:16 字节。
  • 内存 (m)64 MB (65536 KiB) —— 这是硬性内存需求,使 GPU 并行成本极高。
  • 迭代 (t):3。
  • 并行度 (p):1-2(普通应用设为 1 或 2)。
  • 输出长度:32 字节(密钥)。

Python 示例(使用 argon2-cffi 库)

首先安装:pip install argon2-cffi

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import os
def derive_key_argon2(password: str, salt: bytes = None, dklen: int = 32) -> dict:
    """
    使用 Argon2id 派生密钥。
    返回一个包含所有必要信息的字典,便于存储和验证。
    """
    if salt is None:
        salt = os.urandom(16)
    # 建议使用低层 API (Argon2LowLevel) 更精确控制
    # 但这里用高层 API 演示(高层 API 内部会生成盐,并返回编码后的字符串)
    # 实际生产建议使用低层 API 以获得原始字节
    from argon2.low_level import hash_secret, Type
    # 低层 API:直接传入盐,返回哈希字节 + 参数编码
    result = hash_secret(
        secret=password.encode('utf-8'),
        salt=salt,
        time_cost=3,        # t
        memory_cost=65536,  # m (KiB) = 64 MB
        parallelism=1,      # p
        hash_len=dklen,
        type=Type.ID        # Argon2id
    )
    # result 是包含哈希值和编码参数的字节串(有格式,不适合直接当密钥)
    # 所以我们从结果中提取原始哈希值(raw hash)
    # 方法:直接传给 key 的是 hash 部分(32 字节)
    # 注意:Argon2 hash_secret 返回的格式是 '$argon2id$v=19$m=...',末尾是 raw hash
    # 更清晰的方法:使用 lib 的 hash_raw 函数(如果存在)
    # 简化:直接解码结果(从末尾取最后 dklen 字节)
    parts = result.decode('ascii').split('$')
    raw_hash_hex = parts[-1]
    key = bytes.fromhex(raw_hash_hex)
    # 但注意:raw hash 可能是 base64 编码,需根据库确定
    # argon2-cffi 低层返回的是 C 绑定的标准编码,raw hash 是 base64 无填充
    # 为简洁,这里跳过解码细节,实际项目中可直接使用 password_hasher 的 hash 方法
    # 更简单的替代(但会丢失原始盐和参数控制):
    ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=1)
    hash_str = ph.hash(password)  # 此方法内部生成随机盐
    # 验证:
    # ph.verify(hash_str, password)
    # 即返回的 hash_str 包含了所有参数、盐和哈希,日后可直接用来验证。
    # 为了演示返回原始密钥,我们继续用低层方法:
    return {
        "key": key,
        "salt": salt,
        "parameters": {"m": 65536, "t": 3, "p": 1, "type": "Argon2id"}
    }
# 使用示例
password = "MySecurePassword123!"
result = derive_key_argon2(password)
print(f"派生密钥 (hex): {result['key'].hex()}")

注意:Argon2 的 Python 库自动处理盐和编码,生产中最简单的做法是使用高层 PasswordHasher,它返回的字符串可以直接存储并用于验证,但如果需要将派生结果用作加密密钥(而非密码存储),你需要确保拿到原始字节密钥(如上示例)。

Node.js 示例(使用 argon2 库)

安装:npm install argon2

const argon2 = require('argon2');
const crypto = require('crypto');
async function deriveKeyArgon2(password, salt) {
    if (!salt) {
        salt = crypto.randomBytes(16);
    }
    const hash = await argon2.hash(password, {
        type: argon2.argon2id,
        salt: salt,
        timeCost: 3,
        memoryCost: 65536,   // 64 MB
        parallelism: 1,
        hashLength: 32
    });
    // hash 是一个包含编码信息的字符串(如 $argon2id...)
    // 要获取原始 32 字节密钥,需要从字符串末尾提取 base64 部分并解码
    const parts = hash.split('$');
    const rawHashBase64 = parts[parts.length - 1];
    const key = Buffer.from(rawHashBase64, 'base64');
    return { key, salt, hash };
}
// 使用
deriveKeyArgon2('MyPassword', null).then(result => {
    console.log('密钥:', result.key.toString('hex'));
});

如何选择?

维度 PBKDF2 Argon2
安全性 较低(无内存硬化) 高(内存硬化 + 时间 + 并行)
性能/抵抗GPU
标准库支持 几乎所有语言标准库 需第三方库(但广泛)
推荐场景 兼容性要求高、旧系统 新系统、密码存储或密钥派生
参数调整 只需改迭代次数 需调 3 个参数(m, t, p)

对于新项目,强烈推荐 Argon2id,如果因为环境限制无法安装第三方库,则使用 PBKDF2(并保持较高迭代次数)。


安全注意事项

  1. 盐必须随机且每次不同:永远不要使用固定盐。
  2. 输出长度:至少 32 字节(256 位)用于对称加密密钥。
  3. 不截断输出:如果需要更短密钥(如 16 字节),应使用完整的派生结果经过 KDF 再截断(或直接指定 dklen=16)。
  4. 验证密码:存储派生结果时,将参数(盐、迭代次数、内存等)一起存储(通常编码在一个字符串中,如 Argon2 的 $argon2id$v=19$m=...),以便后续验证。
  5. 定期升级参数:每 1-2 年根据硬件性能更新迭代次数/内存参数。

希望以上步骤能帮助你正确安全地使用 PBKDF2 或 Argon2 派生密钥,如果有具体语言或场景的需求,欢迎进一步说明。

抱歉,评论功能暂时关闭!