本文目录导读:

在 PHP 中实现“服务端签名”(Server Signing),通常指的是对 API 请求或敏感数据进行数字签名,以验证数据的完整性和来源的真实性(防止篡改或伪造)。
常见的场景包括:
- 微信/支付宝支付回调验证(需按平台提供的签名算法验签)。
- 对外暴露的 API 接口(双方约定公私钥或密钥,生成签名)。
- 文件/消息完整性校验(防止传输过程中内容被修改)。
下面我会给出一个通用的、基于 HMAC-SHA256(对称密钥)和 RSA-SHA256(非对称公私钥)的 PHP 签名和验签实现方案,你可以根据实际需求选择使用。
基于 HMAC(对称密钥)的签名
这种方式使用相同的密钥进行签名和验签,速度快,适合内部服务间调用。
签名生成步骤(PHP)
<?php
function generateSignature(array $params, string $secretKey): string {
// 1. 按照参数名 ASCII 码升序排序
ksort($params);
// 2. 拼接成字符串 key1=value1&key2=value2...
$queryString = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
// 3. 使用 HMAC-SHA256 计算签名
$signature = hash_hmac('sha256', $queryString, $secretKey);
return $signature;
}
// 示例使用
$params = [
'name' => 'Alice',
'amount' => 100,
'timestamp' => time()
];
$secret = 'my-secret-key-123';
echo generateSignature($params, $secret);
// 输出类似:c5f3a7b8...
?>
验签步骤(PHP)
<?php
function verifySignature(array $params, string $signature, string $secretKey): bool {
// 1. 注意:如果传过来的参数中包含签名字段,需先排除
$signatureFromRequest = $params['signature'] ?? '';
unset($params['signature']);
// 2. 重新计算签名
$computedSignature = generateSignature($params, $secretKey);
// 3. 比较(防止时序攻击,使用 hash_equals)
return hash_equals($computedSignature, $signatureFromRequest);
}
?>
基于 RSA(非对称公私钥)的签名
适用于需要对外暴露 API,或同时需要签名和加密的场景,客户端用私钥签名,服务端用公钥验签。
准备工作
- 生成 RSA 密钥对(Linux/Mac):
openssl genrsa -out private_key.pem 2048 openssl rsa -in private_key.pem -pubout -out public_key.pem
签名生成(PHP 服务端用私钥签名)
<?php
function rsaSign(array $params, string $privateKeyFilePath): string {
// 1. 将参数排序并拼接成待签字符串
ksort($params);
$content = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
// 2. 读取私钥并签名
$privateKey = file_get_contents($privateKeyFilePath);
$pkeyId = openssl_get_privatekey($privateKey);
openssl_sign($content, $signature, $pkeyId, OPENSSL_ALGO_SHA256);
openssl_free_key($pkeyId);
// 3. 返回 Base64 编码的签名
return base64_encode($signature);
}
// 示例使用
$params = ['order_id' => '12345', 'amount' => 199.9];
$signature = rsaSign($params, '/path/to/private_key.pem');
echo $signature;
?>
验签(PHP 服务端用公钥验签)
<?php
function rsaVerify(array $params, string $signature, string $publicKeyFilePath): bool {
// 1. 排除签名字段,重新拼接
unset($params['signature']);
ksort($params);
$content = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
// 2. 读取公钥
$publicKey = file_get_contents($publicKeyFilePath);
$pubKeyId = openssl_get_publickey($publicKey);
// 3. 验签(注意 base64_decode 还原签名)
$result = openssl_verify($content, base64_decode($signature), $pubKeyId, OPENSSL_ALGO_SHA256);
openssl_free_key($pubKeyId);
return $result; // true 或 false
}
?>
常见问题与最佳实践
| 问题 | 解决方案 |
|---|---|
| 参数中包含中文/特殊字符 | 使用 http_build_query 并指定 PHP_QUERY_RFC3986 保证编码一致 |
| 签名验证总失败 | 检查排序是否一致(包括排除签名字段) 检查参数编码(urlencode)方式 使用 hash_equals 而非 防止时序攻击 |
| 私钥安全存储 | 不要写在代码里;使用环境变量或外部密钥管理服务(AWS KMS / HashiCorp Vault) |
| 时间戳/随机数防重放 | 在签名参数中加入 timestamp 和 nonce,服务端验签时检查时间差和是否使用过 |
完整示例:一个带签名的 API 请求流程
服务端(签名发送方)
// 1. 准备参数
$params = [
'type' => 'transfer',
'to' => 'user_123',
'amount' => 50.00,
'ts' => time()
];
// 2. 生成签名
$params['sign'] = generateSignature($params, $secret);
// 3. 发送请求(curl / Postman)
接收方(验签)
// 1. 获取 POST 数据
$receivedParams = $_POST;
// 2. 验签
$valid = verifySignature($receivedParams, $receivedParams['sign'], $secret);
if (!$valid) {
http_response_code(403);
echo '签名验证失败';
exit;
}
// 3. 继续业务逻辑
echo '通过';
- 内部服务:推荐 HMAC-SHA256(简单快速)。
- 对外 API 或需要非对称能力:推荐 RSA-SHA256(公钥验签,私钥签名)。
- 注意:务必按照双方约定的排序规则、编码方式和算法保持一致。
如果你需要具体某个平台(如支付宝、微信)的签名代码,请告诉我,我可以给出针对性的实现。