PHP 怎么PHP 密钥派生

wen PHP项目 3

本文目录导读:

PHP 怎么PHP 密钥派生

  1. 使用 hash_pbkdf2() 函数(推荐)
  2. 使用 Argon2(更安全、现代)
  3. 使用 OpenSSL 的 EVP_BytesToKey
  4. 最佳实践建议
  5. 选择建议

在 PHP 中实现密钥派生,最常用、最安全的方法是使用 PBKDF2(基于密码的密钥派生函数 2)或 Argon2(更现代、推荐用于密码哈希,也可用于派生密钥),以下是几种常见实现方式:

使用 hash_pbkdf2() 函数(推荐)

这是最直接的 PBKDF2 实现,PHP 内置支持:

<?php
function deriveKey($password, $salt, $iterations = 100000, $keyLength = 32) {
    $derivedKey = hash_pbkdf2(
        'sha256',           // 哈希算法
        $password,          // 原始密码
        $salt,              // 盐值
        $iterations,        // 迭代次数
        $keyLength,         // 输出密钥长度(字节数)
        true                // 原始二进制输出
    );
    return $derivedKey;
}
// 使用示例
$password = "my_secure_password";
$salt = random_bytes(16);  // 生成安全的随机盐值
$iterations = 100000;      // 根据性能调整
$keyLength = 32;           // 256位密钥
$derivedKey = deriveKey($password, $salt, $iterations, $keyLength);
// 存储时需保存 salt 和 iterations
echo "Salt (hex): " . bin2hex($salt) . "\n";
echo "Derived Key (hex): " . bin2hex($derivedKey) . "\n";
?>

使用 Argon2(更安全、现代)

PHP 7.2+ 支持 Argon2,主要用于密码哈希,但也可用于派生密钥:

<?php
function deriveKeyWithArgon2($password, $salt, $keyLength = 32) {
    // 将盐值编码进选项
    $options = [
        'memory_cost' => 1 << 17,  // 128 MB
        'time_cost'   => 4,
        'threads'     => 2,
        'salt'        => $salt     // 自定义盐值
    ];
    // 使用 PASSWORD_ARGON2ID 算法
    $hash = password_hash($password, PASSWORD_ARGON2ID, $options);
    // 注意:password_hash 输出是包含所有参数的字符串
    // 要提取纯密钥部分可能需要额外处理
    return $hash;
}
// 或者使用 sodium 扩展(更直接)
function deriveKeyWithSodium($password, $salt, $keyLength = 32) {
    // 需要 libsodium 扩展(PHP 7.2+ 默认包含)
    $hash = sodium_crypto_pwhash(
        $keyLength,
        $password,
        $salt,
        SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE,
        SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE
    );
    return $hash;
}

使用 OpenSSL 的 EVP_BytesToKey

适用于与其他系统兼容的场景(如 OpenSSL 加密):

<?php
function deriveKeyOpenSSL($password, $salt, $keyLength = 32, $ivLength = 16) {
    $derived = '';
    $total = $keyLength + $ivLength;
    $hash = '';
    while (strlen($derived) < $total) {
        $hash = md5($hash . $password . $salt, true);
        $derived .= $hash;
    }
    $key = substr($derived, 0, $keyLength);
    $iv = substr($derived, $keyLength, $ivLength);
    return ['key' => $key, 'iv' => $iv];
}
// 使用示例
$result = deriveKeyOpenSSL("password", "salted__" . random_bytes(8));
echo "Key (hex): " . bin2hex($result['key']) . "\n";
echo "IV (hex): " . bin2hex($result['iv']) . "\n";
?>

最佳实践建议

安全性考虑:

  • 始终使用足够长的随机盐值(最少 16 字节)
  • 迭代次数:根据服务器性能调整,至少 100,000 次
  • 密钥长度:根据用途选择(AES-256 需要 32 字节)
  • 避免使用 MD5 等弱哈希算法

完整示例(推荐方案):

<?php
class KeyDerivation {
    const ALGORITHM = 'sha256';
    const ITERATIONS = 100000;
    const SALT_LENGTH = 32;
    const KEY_LENGTH = 32;
    public static function derive($password, $salt = null) {
        if ($salt === null) {
            $salt = random_bytes(self::SALT_LENGTH);
        }
        $key = hash_pbkdf2(
            self::ALGORITHM,
            $password,
            $salt,
            self::ITERATIONS,
            self::KEY_LENGTH,
            true
        );
        return [
            'key' => $key,
            'salt' => $salt,
            'iterations' => self::ITERATIONS
        ];
    }
    public static function verify($password, $derivedKey, $salt, $iterations) {
        $testKey = hash_pbkdf2(
            self::ALGORITHM,
            $password,
            $salt,
            $iterations,
            strlen($derivedKey),
            true
        );
        return hash_equals($derivedKey, $testKey);
    }
}
// 使用示例
$password = "secure_password_123";
// 派生密钥
$result = KeyDerivation::derive($password);
echo "Salt: " . bin2hex($result['salt']) . "\n";
echo "Key: " . bin2hex($result['key']) . "\n";
// 验证
$isValid = KeyDerivation::verify($password, $result['key'], $result['salt'], $result['iterations']);
echo "验证结果: " . ($isValid ? "成功" : "失败") . "\n";
?>

选择建议

场景 推荐方法
一般密钥派生 hash_pbkdf2()
密码存储 password_hash() + Argon2
与现代系统兼容 Argon2 或 PBKDF2
OpenSSL 互操作 EVP_BytesToKey

永远不要使用简单的哈希函数(如 MD5、SHA1)直接派生密钥,始终使用专门的密钥派生函数(KDF)。

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