本文目录导读:

在PHP中,HMAC(Hash-based Message Authentication Code)主要通过 hash_hmac() 函数实现,以下是详细的使用指南:
基本语法
string hash_hmac ( string $algo , string $data , string $key [, bool $binary = false ] )
| 参数 | 说明 |
|---|---|
$algo |
哈希算法(如 'md5', 'sha1', 'sha256', 'sha512' 等) |
$data |
要计算的消息内容 |
$key |
密钥 |
$binary |
是否返回原始二进制输出(默认 false 返回十六进制字符串) |
常用示例
基础 HMAC-SHA256 签名
$message = "这是要签名的消息";
$secretKey = "my-secret-key-12345";
// 生成签名(十六进制输出)
$signature = hash_hmac('sha256', $message, $secretKey);
echo "签名: " . $signature . "\n";
// 生成签名(原始二进制输出)
$binarySignature = hash_hmac('sha256', $message, $secretKey, true);
echo "二进制签名 (base64): " . base64_encode($binarySignature);
API 签名验证示例
// 服务器端生成签名
function generateSignature($data, $secretKey) {
return hash_hmac('sha256', json_encode($data), $secretKey);
}
// 客户端验证签名
function verifySignature($data, $signature, $secretKey) {
$expected = generateSignature($data, $secretKey);
return hash_equals($expected, $signature); // 使用 hash_equals 防止时序攻击
}
// 使用示例
$payload = ['user_id' => 123, 'timestamp' => time()];
$key = 'api-secret-key-2024';
$signature = generateSignature($payload, $key);
echo "API 签名: " . $signature . "\n";
// 验证
$isValid = verifySignature($payload, $signature, $key);
echo "签名验证: " . ($isValid ? "通过" : "失败") . "\n";
带时间戳的 HMAC(防止重放攻击)
class HMACAuth {
private $secretKey;
private $timeout = 300; // 5分钟超时
public function __construct($secretKey) {
$this->secretKey = $secretKey;
}
public function generateToken($data) {
$timestamp = time();
$payload = json_encode([
'data' => $data,
'timestamp' => $timestamp
]);
$signature = hash_hmac('sha256', $payload, $this->secretKey);
return base64_encode(json_encode([
'payload' => $payload,
'signature' => $signature
]));
}
public function validateToken($token) {
$decoded = json_decode(base64_decode($token), true);
if (!$decoded || !isset($decoded['payload']) || !isset($decoded['signature'])) {
return false;
}
// 验证签名
$expectedSignature = hash_hmac('sha256', $decoded['payload'], $this->secretKey);
if (!hash_equals($expectedSignature, $decoded['signature'])) {
return false;
}
// 验证时间戳
$payload = json_decode($decoded['payload'], true);
$elapsed = time() - $payload['timestamp'];
if ($elapsed > $this->timeout) {
return false; // Token 过期
}
return $payload['data'];
}
}
// 使用示例
$auth = new HMACAuth('super-secret-key');
$token = $auth->generateToken(['user' => 'john_doe']);
echo "生成的 Token: " . $token . "\n";
$result = $auth->validateToken($token);
echo "验证结果: " . ($result ? "有效 - 用户: " . $result['user'] : "无效") . "\n";
HMAC 与不同的哈希算法
$data = "Hello, World!";
$key = "my-key";
$algorithms = ['md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd160'];
echo "各种 HMAC 算法对比:\n";
foreach ($algorithms as $algo) {
$signature = hash_hmac($algo, $data, $key);
echo str_pad($algo, 12) . ": " . $signature . "\n";
}
HMAC 签名
function signFile($filePath, $secretKey) {
$content = file_get_contents($filePath);
return hash_hmac('sha256', $content, $secretKey);
}
// 使用
$fileSignature = signFile('document.pdf', 'file-signing-key');
echo "文件签名: " . $fileSignature;
安全注意事项
时序攻击防护
永远使用 hash_equals() 进行比较,而不是 或 :
// 安全
if (hash_equals($expected, $provided)) {
// 验证通过
}
// 不安全 - 易受时序攻击
if ($expected === $provided) {
// 不推荐
}
密钥管理
// 推荐:使用环境变量或配置文件
$secretKey = getenv('API_SECRET_KEY');
// 或从安全配置读取
$config = require 'config.php';
$secretKey = $config['hmac_key'];
完整的 HMAC 工具类
class HMACTool {
/**
* 生成 HMAC 签名
*/
public static function sign($data, $key, $algo = 'sha256') {
return hash_hmac($algo, $data, $key);
}
/**
* 验证 HMAC 签名(安全比较)
*/
public static function verify($data, $signature, $key, $algo = 'sha256') {
$expected = self::sign($data, $key, $algo);
return hash_equals($expected, $signature);
}
/**
* 生成带签名的 URL
*/
public static function signUrl($url, $key, $expiry = 3600) {
$expiryTime = time() + $expiry;
$signature = self::sign($url . $expiryTime, $key);
return $url . (strpos($url, '?') === false ? '?' : '&')
. "expires={$expiryTime}&signature={$signature}";
}
/**
* 验证签名 URL
*/
public static function verifySignedUrl($url, $key) {
$parsedUrl = parse_url($url);
parse_str($parsedUrl['query'] ?? '', $params);
if (!isset($params['signature']) || !isset($params['expires'])) {
return false;
}
// 检查是否过期
if (time() > $params['expires']) {
return false;
}
// 重新构建 URL(去除签名参数)
$baseUrl = strtok($url, '?');
$queryParts = [];
foreach ($params as $k => $v) {
if ($k !== 'signature') {
$queryParts[] = "$k=$v";
}
}
$cleanUrl = $baseUrl . '?' . implode('&', $queryParts);
// 验证签名
return self::verify($cleanUrl . $params['expires'], $params['signature'], $key);
}
}
// 使用示例
$key = 'url-signing-key';
$url = 'https://api.example.com/download/file123';
$signedUrl = HMACTool::signUrl($url, $key, 3600);
echo "签名URL: " . $signedUrl . "\n";
// 验证
$isValid = HMACTool::verifySignedUrl($signedUrl, $key);
echo "URL有效: " . ($isValid ? "是" : "否");
- 算法选择:优先使用
sha256或sha512,避免使用 MD5 或 SHA1 - 密钥强度:使用足够长且随机的密钥(至少 32 字节)
- 比较方式:始终使用
hash_equals()进行比较 - 密钥存储:不要在代码中硬编码密钥
- 时效性:为签名添加时间戳以防止重放攻击
HMAC 在 API 认证、JWT 实现、Webhook 验证等场景中非常常用,掌握这些用法对 PHP 开发很有帮助。