本文目录导读:

- 使用
hash_pbkdf2()(最通用) - 使用
password_hash()+password_verify()(推荐用于密码存储) - 使用 OpenSSL 的 KDF 功能
- 使用 Argon2(现代且更安全)
- 综合最佳实践函数
- 最佳实践建议
- 安全性比较
在 PHP 中实现密钥派生,最常用和推荐的方法是使用 哈希函数(如 PBKDF2) 或 现代 KDF(如 Argon2),下面是几种主流方法的详细说明和代码示例。
使用 hash_pbkdf2()(最通用)
PBKDF2(Password-Based Key Derivation Function 2)是经典且广泛支持的密钥派生函数。
<?php
// 参数准备
$password = 'user_password';
$salt = random_bytes(16); // 始终使用随机盐
$iterations = 100000; // 建议至少 100,000 次
$keyLength = 32; // 输出密钥长度(字节)
$algorithm = 'sha256';
// 派生密钥
$derivedKey = hash_pbkdf2(
$algorithm,
$password,
$salt,
$iterations,
$keyLength,
true // 返回二进制数据,而非十六进制
);
// 生成十六进制表示(便于存储)
$encodedKey = bin2hex($derivedKey);
echo "盐: " . bin2hex($salt) . "\n";
echo "派生密钥: " . $encodedKey . "\n";
?>
注意事项:
- 盐必须是唯一的、随机的,并且至少 16 字节
- 迭代次数越多,安全性越高,但性能越低
- 输出格式可以是二进制或十六进制
使用 password_hash() + password_verify()(推荐用于密码存储)
虽然这是为密码哈希设计的,但也可以用于密钥派生:
<?php
// 密码哈希(适合存储验证,不适合直接用作加密密钥)
$password = 'user_password';
$hash = password_hash($password, PASSWORD_BCRYPT, [
'cost' => 12, // 计算成本
]);
// 验证
if (password_verify($password, $hash)) {
echo "密码验证成功\n";
}
?>
注意:password_hash 不适合直接用于加密密钥,因为它使用的是单向哈希,但如果你需要存储密码凭证,这是最好的选择。
使用 OpenSSL 的 KDF 功能
<?php
// 使用 openssl_pbkdf2(PHP >= 5.5)
$password = 'user_password';
$salt = random_bytes(16);
$iterations = 100000;
$keyLength = 32;
$algorithm = 'sha256';
$derivedKey = openssl_pbkdf2(
$password,
$salt,
$keyLength,
$iterations,
$algorithm
);
echo "派生密钥: " . bin2hex($derivedKey) . "\n";
?>
使用 Argon2(现代且更安全)
PHP 7.2+ 支持 Argon2,它被认为是目前最安全的 KDF 算法之一:
<?php
// 主要用途:密码哈希(PASSWORD_ARGON2I 或 PASSWORD_ARGON2ID)
$options = [
'memory_cost' => 1024 * 64, // 64 MB
'time_cost' => 4, // 迭代次数
'threads' => 2, // 线程数
];
$hash = password_hash('user_password', PASSWORD_ARGON2ID, $options);
// 验证
if (password_verify('user_password', $hash)) {
echo "密码验证成功\n";
}
?>
注意:Argon2 主要用于密码哈希,不能直接输出为加密密钥,如果需要使用 Argon2 派生密钥,需要修改其输出设置(在 C 扩展层面),通常不建议这样做。
综合最佳实践函数
以下是一个完整的密钥派生函数封装:
<?php
/**
* 使用 PBKDF2 派生加密密钥
*
* @param string $password 密码
* @param string $salt 盐(至少16字节)
* @param int $keyLength 密钥长度(字节)
* @param int $iterations 迭代次数
* @return string 返回二进制密钥
*/
function deriveKey(
string $password,
string $salt,
int $keyLength = 32,
int $iterations = 100000
): string {
if (strlen($salt) < 16) {
throw new InvalidArgumentException('盐长度至少需要16字节');
}
return hash_pbkdf2(
'sha256',
$password,
$salt,
$iterations,
$keyLength,
true
);
}
// 使用示例
$password = 'my_secure_password';
$salt = random_bytes(32); // 推荐32字节
$key = deriveKey($password, $salt);
echo "生成的密钥: " . bin2hex($key) . "\n";
?>
最佳实践建议
- 始终使用随机盐:不要重复使用盐
- 选择适当的迭代次数:根据硬件性能调整,建议 100,000 次以上
- 使用安全的哈希算法:SHA-256 或更强
- 密钥长度:AES-256 建议 32 字节
- 存储格式:使用
bin2hex()或 Base64 编码存储 - 更新策略:定期评估和更新参数
安全性比较
| 方法 | 安全性 | 用途 | 性能 |
|---|---|---|---|
| PBKDF2 | 良好 | 通用密钥派生 | 中等 |
| Argon2 | 优秀 | 密码哈希 | 较慢(但安全) |
| bcrypt | 良好 | 密码存储 | 中等 |
| OpenSSL PBKDF2 | 良好 | 通用密钥派生 | 中等 |
选择哪种方法取决于你的具体需求:
- 加密密钥派生 → 使用 PBKDF2
- 密码存储验证 → 使用 Argon2 或 bcrypt
- 需要最大安全性 → 使用 Argon2id