PHP 怎么PHP Server 签名

wen PHP项目 2

本文目录导读:

PHP 怎么PHP Server 签名

  1. 基于 HMAC(对称密钥)的签名
  2. 基于 RSA(非对称公私钥)的签名
  3. 常见问题与最佳实践
  4. 完整示例:一个带签名的 API 请求流程

在 PHP 中实现“服务端签名”(Server Signing),通常指的是对 API 请求或敏感数据进行数字签名,以验证数据的完整性和来源的真实性(防止篡改或伪造)。

常见的场景包括:

  1. 微信/支付宝支付回调验证(需按平台提供的签名算法验签)。
  2. 对外暴露的 API 接口(双方约定公私钥或密钥,生成签名)。
  3. 文件/消息完整性校验(防止传输过程中内容被修改)。

下面我会给出一个通用的、基于 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)
时间戳/随机数防重放 在签名参数中加入 timestampnonce,服务端验签时检查时间差和是否使用过

完整示例:一个带签名的 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(公钥验签,私钥签名)。
  • 注意:务必按照双方约定的排序规则、编码方式和算法保持一致。

如果你需要具体某个平台(如支付宝、微信)的签名代码,请告诉我,我可以给出针对性的实现。

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