我来详细解释 PHP 中处理签名头(通常指 HTTP 请求中的签名验证)的方法。

常见的签名验证场景
HMAC-SHA256 签名验证
<?php
function verifySignature($payload, $signature, $secret) {
// 重建签名
$expectedSignature = hash_hmac('sha256', $payload, $secret);
// 使用安全比较防止时序攻击
return hash_equals($expectedSignature, $signature);
}
// 获取请求头和内容
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$payload = file_get_contents('php://input');
$secret = 'your_secret_key';
if (verifySignature($payload, $signature, $secret)) {
echo "签名验证通过";
} else {
http_response_code(401);
echo "签名验证失败";
}
生成签名
<?php
function generateSignature($payload, $secret) {
return hash_hmac('sha256', $payload, $secret);
}
// 发送请求时生成签名
$data = json_encode(['user_id' => 123, 'action' => 'login']);
$secret = 'your_secret_key';
$signature = generateSignature($data, $secret);
// 设置请求头
$headers = [
'Content-Type: application/json',
'X-Signature: ' . $signature
];
完整的签名验证中间件
<?php
class SignatureMiddleware {
private $secret;
private $timestampTolerance = 300; // 5分钟
public function __construct($secret) {
$this->secret = $secret;
}
public function validate() {
// 获取签名头
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$nonce = $_SERVER['HTTP_X_NONCE'] ?? '';
if (empty($signature) || empty($timestamp) || empty($nonce)) {
throw new \Exception('缺少签名参数');
}
// 检查时间戳是否过期
if (abs(time() - $timestamp) > $this->timestampTolerance) {
throw new \Exception('请求已过期');
}
// 获取请求体
$payload = file_get_contents('php://input');
// 重建签名字符串
$signString = $timestamp . $nonce . $payload;
// 计算期望签名
$expectedSignature = hash_hmac('sha256', $signString, $this->secret);
// 验证签名
if (!hash_equals($expectedSignature, $signature)) {
throw new \Exception('签名验证失败');
}
return true;
}
}
// 使用中间件
try {
$middleware = new SignatureMiddleware('your_secret_key');
$middleware->validate();
echo "验证通过";
} catch (\Exception $e) {
http_response_code(401);
echo $e->getMessage();
}
发送带签名的请求
<?php
class SignatureClient {
private $secret;
private $baseUrl;
public function __construct($baseUrl, $secret) {
$this->baseUrl = $baseUrl;
$this->secret = $secret;
}
public function sendRequest($endpoint, $data = []) {
$timestamp = time();
$nonce = bin2hex(random_bytes(16));
$payload = json_encode($data);
// 构建签名字符串
$signString = $timestamp . $nonce . $payload;
$signature = hash_hmac('sha256', $signString, $this->secret);
// 设置请求头
$headers = [
'Content-Type: application/json',
'X-Timestamp: ' . $timestamp,
'X-Nonce: ' . $nonce,
'X-Signature: ' . $signature
];
// 使用 cURL 发送请求
$ch = curl_init($this->baseUrl . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status' => $httpCode,
'response' => json_decode($response, true)
];
}
}
// 使用示例
$client = new SignatureClient('https://api.example.com', 'your_secret_key');
$result = $client->sendRequest('/api/user', ['user_id' => 123]);
使用 openSSL 签名
<?php
// 生成私钥和公钥
$config = [
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
];
$res = openssl_pkey_new($config);
openssl_pkey_export($res, $privateKey);
$publicKey = openssl_pkey_get_details($res)['key'];
// 使用私钥签名
function signWithPrivateKey($data, $privateKey) {
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
// 使用公钥验证
function verifyWithPublicKey($data, $signature, $publicKey) {
$decodedSignature = base64_decode($signature);
return openssl_verify($data, $decodedSignature, $publicKey, OPENSSL_ALGO_SHA256);
}
// 使用示例
$data = 'request data';
$signature = signWithPrivateKey($data, $privateKey);
$isValid = verifyWithPublicKey($data, $signature, $publicKey);
安全建议
<?php
// 1. 使用安全比较函数
// hash_equals() 防止时序攻击
if (hash_equals($expected, $provided)) {
// 签名匹配
}
// 2. 添加时间戳防止重放攻击
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? 0;
if (abs(time() - $timestamp) > 300) { // 5分钟内有效
die("请求已过期");
}
// 3. 使用随机数防止重复请求
$nonce = $_SERVER['HTTP_X_NONCE'] ?? '';
// 检查 nonce 是否已经使用过(需要存储已使用的 nonce)
// 4. 限制请求频率
// 可以使用 Redis 或数据库记录请求频率
// 5. 使用 HTTPS 传输
// 签名头也应该通过 HTTPS 传输
这些示例展示了 PHP 中处理签名头的常见方法,选择哪种方式取决于你的具体需求和安全要求。