PBKDF2 密钥派生
RFC 2898 / RFC 8018 / PKCS#5 v2.1 标准 ·
默认 OWASP 2023 推荐迭代 600,000 次 ·
优先 Web Crypto,回退 CryptoJS · 纯前端本地计算
典型场景模板
代码示例(PBKDF2-HMAC-SHA256)
// Node.js 内置 (推荐)
const crypto = require('crypto');
const dk = crypto.pbkdf2Sync(
'mypassword', // 密码
Buffer.from('1234567890abcdef', 'hex'), // 16 字节 Salt
600000, // 迭代次数
32, // DkLen
'sha256'); // PRF
console.log(dk.toString('hex'));
// 浏览器 (Web Crypto API, 高性能)
async function pbkdf2(password, salt, iterations, dkLenBytes) {
const enc = new TextEncoder();
const baseKey = await crypto.subtle.importKey(
'raw', enc.encode(password),
{ name: 'PBKDF2' }, false, ['deriveBits']);
const bits = await crypto.subtle.deriveBits(
{ name: 'PBKDF2', hash: 'SHA-256', salt: salt, iterations: iterations },
baseKey, dkLenBytes * 8);
return new Uint8Array(bits);
}
import hashlib, secrets
password = b'mypassword'
salt = secrets.token_bytes(16) # 16 字节随机 Salt
iterations = 600_000
dk_len = 32 # 256 位
dk = hashlib.pbkdf2_hmac('sha256', password, salt, iterations, dk_len)
print(dk.hex())
# 校验 (时序安全比较)
import hmac
hmac.compare_digest(dk_a, dk_b)
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);
PBEKeySpec spec = new PBEKeySpec(
"mypassword".toCharArray(),
salt,
600_000, // 迭代
256); // DkLen 位 (256 / 8 = 32 字节)
byte[] dk = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
.generateSecret(spec).getEncoded();
// dk -> Hex / Base64
package main
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"golang.org/x/crypto/pbkdf2"
)
func main() {
salt := make([]byte, 16)
_, _ = rand.Read(salt)
dk := pbkdf2.Key(
[]byte("mypassword"),
salt,
600000, // iterations
32, // DkLen
sha256.New)
fmt.Println(hex.EncodeToString(dk))
}
<?php
$password = 'mypassword';
$salt = random_bytes(16);
$dk = hash_pbkdf2('sha256', $password, $salt, 600000, 32, true); // raw output 32 字节
echo bin2hex($dk), PHP_EOL;
// 时序安全比较
hash_equals($dk_a, $dk_b);
# OpenSSL 1.1.1+
openssl kdf -keylen 32 -kdfopt digest:SHA256 \
-kdfopt pass:mypassword \
-kdfopt hexsalt:1234567890abcdef \
-kdfopt iter:600000 PBKDF2
# 旧版 OpenSSL (用于 enc 派生 IV)
openssl enc -aes-256-cbc -P -pbkdf2 -iter 600000 \
-pass pass:mypassword -S 1234567890abcdef
PBKDF2 算法说明 / OWASP 2023 推荐参数
| PRF | 输出/块 | OWASP 2023 迭代 | 典型应用 |
|---|---|---|---|
| HMAC-SHA1 | 20 / 64 字节 | 1,300,000 次 | WPA2-PSK(4096 固定)、iOS Data Protection、老旧系统 |
| HMAC-SHA224 | 28 / 64 字节 | — | 过渡用途 |
| HMAC-SHA256 | 32 / 64 字节 | 600,000 次(推荐) | 密码存储、JWT 密钥派生、AES 密钥派生、KeePass |
| HMAC-SHA384 | 48 / 128 字节 | 210,000 次 | 高安全级 JWT |
| HMAC-SHA512 | 64 / 128 字节 | 210,000 次 | BIP39(2048 固定)、1Password、64 位机器更快 |
| HMAC-SHA3-256 | 32 / 136 字节 | — | 新协议、抗量子潜力 |
| HMAC-SHA3-512 | 64 / 72 字节 | — | 新协议、最高安全级 |
| HMAC-MD5 | 16 / 64 字节 | — | 仅供调试与遗留对接,新项目避免使用 |
RFC 6070 标准测试向量(PBKDF2-HMAC-SHA1)
| P (密码) | S (盐值) | c | dkLen | DK (Hex) |
|---|---|---|---|---|
| "password" | "salt" | 1 | 20 | 0c60c80f961f0e71f3a9b524af6012062fe037a6 |
| "password" | "salt" | 2 | 20 | ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957 |
| "password" | "salt" | 4096 | 20 | 4b007901b765489abead49d926f721d065a429c1 |
| "passwordPASSWORDpassword" | "saltSALTsaltSALTsaltSALTsaltSALTsalt" | 4096 | 25 | 3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038 |
安全使用清单
- ✅ Salt 必须使用 CSPRNG 生成,每用户/每次密码变更唯一,长度 ≥ 16 字节;本工具使用
crypto.getRandomValues - ✅ 迭代次数随硬件升级,建议每 2 年翻倍;存储时连同 PRF / 迭代 / Salt 一起保存(如 PHC 字符串格式)
- ✅ 密码哈希比较使用时序安全 compare_digest / hash_equals / 本工具自动使用恒定时间比较
- ✅ 派生 AES + IV:DkLen 设为 32+16=48 字节(AES-256),前 32 作为密钥、后 16 作为 IV;或分别使用不同 Salt 派生
- ⚠️ 严禁固定 Salt(如 "salt12345"),等于没有加盐,攻击者可建立专用彩虹表
- ⚠️ 新项目优先 Argon2id / scrypt,PBKDF2 是 FIPS 合规与最广泛兼容的选择,但 GPU/ASIC 抗性较弱
常见问题 FAQ
PBKDF2 与 bcrypt / scrypt / Argon2 该如何选择?
PBKDF2 CPU 密集,FIPS 140-2 合规,被 NIST、PKCS#5 等标准认可,跨平台支持极好;缺点是 GPU/FPGA/ASIC 抗性较弱。bcrypt 限制 72 字节输入;scrypt 内存密集,抗 GPU 强;Argon2id 是 2015 年密码哈希竞赛冠军,OWASP 首推。
选型:政府/金融合规 → PBKDF2-SHA512;新业务首选 → Argon2id;老 PHP/Rails → bcrypt。
迭代次数(iterations)应该设多少?
原则:在用户可接受时间内(100~500ms)尽可能多。OWASP 2023 推荐:SHA256 = 600,000;SHA512 = 210,000;SHA1 = 1,300,000。建议每 2 年翻倍以匹配硬件性能提升;保存哈希时连同迭代次数一起存储,便于平滑升级。
Salt 该怎么生成?为什么必须随机?
Salt 必须使用密码学安全随机数生成器(CSPRNG):JS 用 crypto.getRandomValues、Go 用 crypto/rand、Python 用 secrets。长度 ≥ 16 字节;每用户、每次密码变更唯一;不需保密可与哈希一起存储。固定 Salt = 无 Salt,攻击者可建立专用彩虹表。
怎么从派生的密钥得到 AES 密钥 + IV?
DkLen 设为「AES 密钥长度 + IV 长度」,例如 AES-256-CBC:DkLen = 32 + 16 = 48 字节,前 32 作为密钥、后 16 作为 IV;或者分别用不同 Salt 派生。文件加密格式头通常存储:Magic + Salt + 迭代次数 + IV + 密文 + HMAC。
为什么浏览器计算这么快?iOS/手机会很慢吗?
本工具优先使用 Web Crypto API(浏览器原生 C 实现,调用底层 OpenSSL/BoringSSL),比纯 JavaScript 实现快 10~100 倍;不支持 SHA3/MD5 时回退 CryptoJS 4.2。手机性能比桌面慢 3~5 倍,60 万次 SHA256 桌面约 200ms、iPhone 约 800ms、低端 Android 约 1.5s,仍在用户可接受范围。
