PHP 签名工具类

wen PHP项目 3

本文目录导读:

PHP 签名工具类

  1. 主要功能
  2. 使用场景
  3. 扩展建议

我来为您创建一个完整的PHP签名工具类,包含常见的签名算法实现:

<?php
/**
 * 签名工具类
 * 支持 MD5、SHA1、SHA256、HMAC 等常见签名算法
 */
class SignUtil
{
    /**
     * 签名算法常量
     */
    const ALG_MD5 = 'md5';
    const ALG_SHA1 = 'sha1';
    const ALG_SHA256 = 'sha256';
    const ALG_HMAC_MD5 = 'hmac_md5';
    const ALG_HMAC_SHA1 = 'hmac_sha1';
    const ALG_HMAC_SHA256 = 'hmac_sha256';
    const ALG_RSA = 'rsa';
    const ALG_ECDSA = 'ecdsa';
    /**
     * 生成签名
     *
     * @param array $params 待签名参数
     * @param string $secret 密钥
     * @param string $algorithm 签名算法
     * @param array $options 额外选项
     * @return string 签名结果
     */
    public static function sign(array $params, string $secret, string $algorithm = self::ALG_MD5, array $options = []): string
    {
        // 1. 过滤空值和非签名参数
        $params = self::filterParams($params, $options['exclude_keys'] ?? []);
        // 2. 排序参数
        ksort($params);
        // 3. 拼接字符串
        $str = self::buildSignStr($params, $options['separator'] ?? '&');
        // 4. 根据算法生成签名
        switch ($algorithm) {
            case self::ALG_MD5:
                return self::md5Sign($str, $secret);
            case self::ALG_SHA1:
                return self::sha1Sign($str, $secret);
            case self::ALG_SHA256:
                return self::sha256Sign($str, $secret);
            case self::ALG_HMAC_MD5:
                return self::hmacSign($str, $secret, 'md5');
            case self::ALG_HMAC_SHA1:
                return self::hmacSign($str, $secret, 'sha1');
            case self::ALG_HMAC_SHA256:
                return self::hmacSign($str, $secret, 'sha256');
            case self::ALG_RSA:
                return self::rsaSign($str, $secret, $options['private_key'] ?? null);
            case self::ALG_ECDSA:
                return self::ecdsaSign($str, $secret, $options['private_key'] ?? null);
            default:
                throw new InvalidArgumentException("不支持的签名算法: {$algorithm}");
        }
    }
    /**
     * 验证签名
     *
     * @param array $params 请求参数(包含签名)
     * @param string $secret 密钥
     * @param string $algorithm 签名算法
     * @param string $signName 签名参数名
     * @param array $options 额外选项
     * @return bool
     */
    public static function verify(array $params, string $secret, string $algorithm = self::ALG_MD5, string $signName = 'sign', array $options = []): bool
    {
        if (!isset($params[$signName])) {
            throw new InvalidArgumentException("缺少签名参数: {$signName}");
        }
        $clientSign = $params[$signName];
        // 复制参数并移除签名字段
        $params = $params;
        unset($params[$signName]);
        // 生成服务端签名
        $serverSign = self::sign($params, $secret, $algorithm, $options);
        // 使用 hash_equals 防止时序攻击
        return hash_equals($clientSign, $serverSign);
    }
    /**
     * MD5 签名(微信支付风格)
     *
     * @param array $params 参数
     * @param string $key 密钥
     * @return string
     */
    public static function wechatSign(array $params, string $key): string
    {
        // 过滤空值和sign
        $params = self::filterParams($params, ['sign', 'sign_type']);
        // 排序
        ksort($params);
        // 拼接
        $str = self::buildSignStr($params);
        $str .= '&key=' . $key;
        return strtoupper(md5($str));
    }
    /**
     * 阿里云签名(RPC风格)
     *
     * @param array $params 参数
     * @param string $secret 密钥
     * @return string
     */
    public static function aliyunRpcSign(array $params, string $secret): string
    {
        // 排序并过滤
        ksort($params);
        $params = self::filterParams($params, ['Signature', 'signature']);
        // 构造规范请求
        $str = '';
        foreach ($params as $key => $value) {
            $str .= '&' . self::percentEncode($key) . '=' . self::percentEncode($value);
        }
        $str = substr($str, 1);
        $stringToSign = 'GET&%2F&' . self::percentEncode($str);
        return base64_encode(hash_hmac('sha1', $stringToSign, $secret . '&', true));
    }
    /**
     * 腾讯云签名(TC3-HMAC-SHA256)
     *
     * @param array $params 参数
     * @param string $secretId 密钥ID
     * @param string $secretKey 密钥
     * @param array $options 选项
     * @return string
     */
    public static function tencentCloudSign(array $params, string $secretId, string $secretKey, array $options = []): string
    {
        $timestamp = $options['timestamp'] ?? time();
        $date = gmdate('Y-m-d', $timestamp);
        $service = $options['service'] ?? '';
        $host = $options['host'] ?? '';
        $action = $options['action'] ?? '';
        $version = $options['version'] ?? '';
        $region = $options['region'] ?? '';
        $method = $options['method'] ?? 'POST';
        $payload = $options['payload'] ?? json_encode($params);
        // 步骤1:拼接规范请求串
        $payloadHash = hash('sha256', $payload);
        $canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" . $host . "\n";
        $signedHeaders = "content-type;host";
        $canonicalRequest = $method . "\n/\n\n" . $canonicalHeaders . "\n" . $signedHeaders . "\n" . $payloadHash;
        // 步骤2:拼接待签名字符串
        $credentialScope = $date . "/" . $service . "/tc3_request";
        $hashedCanonicalRequest = hash('sha256', $canonicalRequest);
        $stringToSign = "TC3-HMAC-SHA256\n" . $timestamp . "\n" . $credentialScope . "\n" . $hashedCanonicalRequest;
        // 步骤3:计算签名
        $secretDate = hash_hmac('sha256', $date, "TC3" . $secretKey, true);
        $secretService = hash_hmac('sha256', $service, $secretDate, true);
        $secretSigning = hash_hmac('sha256', "tc3_request", $secretService, true);
        $signature = hash_hmac('sha256', $stringToSign, $secretSigning);
        // 步骤4:拼接Authorization
        $authorization = "TC3-HMAC-SHA256 Credential=" . $secretId . "/" . $credentialScope . ", SignedHeaders=" . $signedHeaders . ", Signature=" . $signature;
        return $authorization;
    }
    /**
     * 过滤参数
     *
     * @param array $params 原参数
     * @param array $excludeKeys 需要排除的键
     * @return array
     */
    protected static function filterParams(array $params, array $excludeKeys = []): array
    {
        $excludeKeys = array_merge(['sign', 'signature', 'sign_type'], $excludeKeys);
        $result = [];
        foreach ($params as $key => $value) {
            // 排除指定的键
            if (in_array($key, $excludeKeys)) {
                continue;
            }
            // 过滤空值(保留0和'0')
            if ($value === '' || $value === null || $value === false) {
                continue;
            }
            // 数组转为JSON
            if (is_array($value)) {
                $value = json_encode($value);
            }
            $result[$key] = $value;
        }
        return $result;
    }
    /**
     * 构建签名字符串
     *
     * @param array $params 参数
     * @param string $separator 分隔符
     * @return string
     */
    protected static function buildSignStr(array $params, string $separator = '&'): string
    {
        $str = '';
        foreach ($params as $key => $value) {
            if ($str !== '') {
                $str .= $separator;
            }
            $str .= $key . '=' . $value;
        }
        return $str;
    }
    /**
     * MD5 签名
     */
    protected static function md5Sign(string $str, string $key): string
    {
        return md5($str . $key);
    }
    /**
     * SHA1 签名
     */
    protected static function sha1Sign(string $str, string $key): string
    {
        return sha1($str . $key);
    }
    /**
     * SHA256 签名
     */
    protected static function sha256Sign(string $str, string $key): string
    {
        return hash('sha256', $str . $key);
    }
    /**
     * HMAC 签名
     */
    protected static function hmacSign(string $str, string $key, string $algo): string
    {
        return hash_hmac($algo, $str, $key);
    }
    /**
     * RSA 签名
     */
    protected static function rsaSign(string $str, string $key, ?string $privateKey): string
    {
        if (!$privateKey) {
            throw new InvalidArgumentException('RSA签名需要提供私钥');
        }
        $privateKey = openssl_pkey_get_private($privateKey);
        if (!$privateKey) {
            throw new RuntimeException('无效的RSA私钥');
        }
        $signature = '';
        openssl_sign($str, $signature, $privateKey, OPENSSL_ALGO_SHA256);
        openssl_free_key($privateKey);
        return base64_encode($signature);
    }
    /**
     * ECDSA 签名
     */
    protected static function ecdsaSign(string $str, string $key, ?string $privateKey): string
    {
        if (!$privateKey) {
            throw new InvalidArgumentException('ECDSA签名需要提供私钥');
        }
        $privateKey = openssl_pkey_get_private($privateKey);
        if (!$privateKey) {
            throw new RuntimeException('无效的ECDSA私钥');
        }
        $signature = '';
        openssl_sign($str, $signature, $privateKey, OPENSSL_ALGO_SHA256);
        openssl_free_key($privateKey);
        return base64_encode($signature);
    }
    /**
     * URL编码(RFC 3986)
     *
     * @param string $str
     * @return string
     */
    protected static function percentEncode(string $str): string
    {
        $res = urlencode($str);
        $res = str_replace(['+', '*', '%7E'], ['%20', '%2A', '~'], $res);
        return $res;
    }
}
// ==================== 使用示例 ====================
class SignUtilExample
{
    public function testBasicSign()
    {
        $params = [
            'name' => '张三',
            'age' => 30,
            'phone' => '13800138000',
        ];
        $secret = 'my_secret_key';
        // MD5签名
        $sign = SignUtil::sign($params, $secret, SignUtil::ALG_MD5);
        echo "MD5签名: " . $sign . PHP_EOL;
        // HMAC-SHA256签名
        $sign = SignUtil::sign($params, $secret, SignUtil::ALG_HMAC_SHA256);
        echo "HMAC-SHA256签名: " . $sign . PHP_EOL;
        // 验证签名
        $params['sign'] = $sign;
        $result = SignUtil::verify($params, $secret, SignUtil::ALG_HMAC_SHA256);
        echo "签名验证: " . var_export($result, true) . PHP_EOL;
    }
    public function testWechatSign()
    {
        $params = [
            'appid' => 'wx123456789',
            'mch_id' => '123456',
            'nonce_str' => 'abc123',
            'body' => '测试商品',
            'out_trade_no' => '2024000001',
            'total_fee' => 100,
        ];
        $apiKey = 'your_wechat_pay_api_key';
        $sign = SignUtil::wechatSign($params, $apiKey);
        echo "微信支付签名: " . $sign . PHP_EOL;
    }
    public function testGenerateRSAKeyPair()
    {
        // 生成RSA密钥对
        $config = [
            'private_key_bits' => 2048,
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
        ];
        $res = openssl_pkey_new($config);
        openssl_pkey_export($res, $privateKey);
        $details = openssl_pkey_get_details($res);
        $publicKey = $details['key'];
        $params = ['order_id' => '123456', 'amount' => 100];
        // 使用私钥签名
        $sign = SignUtil::sign($params, '', SignUtil::ALG_RSA, [
            'private_key' => $privateKey
        ]);
        // 使用公钥验证(需要额外实现)
        echo "RSA私钥: " . $privateKey . PHP_EOL;
        echo "RSA公钥: " . $publicKey . PHP_EOL;
        echo "RSA签名: " . $sign . PHP_EOL;
    }
}
// 执行示例
$example = new SignUtilExample();
$example->testBasicSign();
$example->testWechatSign();
$example->testGenerateRSAKeyPair();

这个签名工具类具有以下特点:

主要功能

  1. 多算法支持:MD5、SHA1、SHA256、HMAC系列和RSA/ECDSA
  2. 参数过滤:自动过滤空值和签名相关字段
  3. 密钥拼接:支持密钥直接拼接方式
  4. 防时序攻击:使用 hash_equals() 验证签名
  5. URL编码:支持RFC 3986标准编码

使用场景

  • API接口安全:防止参数篡改
  • 支付系统:微信支付、支付宝等平台签名
  • 开放平台:类似于阿里云、腾讯云的签名机制
  • JWT令牌:JWT签名的基础

扩展建议

如果需要更多平台的特定签名,可以在类中添加:

// 支付宝签名
public static function alipaySign(array $params, string $privateKey) {}
// 百度云签名
public static function baiduSign(array $params, string $secret) {}
// JWT签名
public static function jwtSign(array $payload, string $secret) {}

这个工具类应该能满足大多数PHP项目的签名需求,如果有特定平台的需求,可以根据具体规范进行定制扩展。

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