PHP调用第三方API怎么加密_协商密钥并验证签名【说明】

1次阅读

正规第三方api不需应用层密钥协商,只需https+预置密钥签名;php用hash_hmac生成hmac-sha256签名时须严格按文档拼参、urlencode并注意参数顺序与编码格式。

PHP调用第三方API怎么加密_协商密钥并验证签名【说明】

PHP调用第三方API时密钥协商通常不走TLS层外的自定义流程

绝大多数正规第三方API(如微信支付、支付宝、Stripe、AWS)**不让你自己实现密钥协商**,而是直接要求你使用 HTTPS + 预置的 app_idsecret_key(或 access_token),签名基于已知密钥生成。所谓“加密协商密钥”是 TLS 握手的事,PHP 的 curlfile_get_contents 走 HTTPS 时自动完成,你不需要、也不应该在应用层再做一次 DH/ECDH 协商。

真正要你做的,是:拿到平台分配的密钥对(client_id + client_secretapi_key + api_secret),按文档要求对请求参数排序、拼接、哈希(常见 sha256hmac_sha256),再把签名塞进 Header 或 Query。

  • 别试图用 openssl_pkey_new() 手动协商密钥——API 服务端根本不会配合
  • 别把 client_secret 当成 AES 密钥去加密整个 body——99% 的 API 不接受密文 body,只校验签名
  • 时间戳、随机字符串nonce)、签名算法标识(sign_method)往往是签名必需字段,漏掉就 401 Unauthorized

PHP生成HMAC-SHA256签名的典型写法和易错点

签名本质是「用密钥对标准化后的请求数据做不可逆摘要」,不是加密。最常用的是 hash_hmac(),但要注意参数顺序和编码:

  • hash_hmac('sha256', $message, $secret_key, true) 第三个参数是密钥,第四个 true 表示返回原始二进制,需 base64_encode()bin2hex() 转成字符串(看 API 要求)
  • $message 必须严格按文档拼接:通常是参数按字典序排序后,用 & 连接,= 左右不加空格,且所有值必须 urlencode()(注意:不是 rawurlencode(),有些 API 明确要求空格转 +
  • 常见错误:file_get_contents() 发送 POST 时没设 Content-Type: application/x-www-form-urlencoded,导致服务端解析参数失败,签名原文跟服务端算的不一致

示例(微信公众号获取 access_token 后的签名):

立即学习PHP免费学习笔记(深入)”;

```php $params = [     'appid' => 'wx123',     'secret' => 'abc456',     'timestamp' => time(),     'noncestr' => bin2hex(random_bytes(8)), ]; ksort($params); $message = http_build_query($params, '', '&', PHP_QUERY_RFC3986); // 注意 RFC3986 编码风格 $signature = hash_hmac('sha256', $message, $api_secret, true); $sign = base64_encode($signature); ```

cURL发送带签名请求时Header和Body的匹配陷阱

签名是否生效,取决于你签名时用的「原始请求数据」和服务端收到并复现的数据是否完全一致。cURL 默认行为容易破坏一致性:

  • 如果 API 要求签名覆盖 Content-Type,你必须把它写进签名原文,并在 curl_setopt($ch, CURLOPT_HTTPHEADER, [...]) 中显式设置,不能依赖 cURL 自动推断
  • json_encode() 构造 body 后直接发,但签名时却按 application/x-www-form-urlencoded 规则拼参数 → 签名无效
  • curl_setopt($ch, CURLOPT_POSTFIELDS, $data)$data 是数组时,cURL 会自动转成 form-data;是字符串时才原样发送。务必确认你签名用的 $message 和实际发出的 body 字符串完全等价
  • 某些 API(如阿里云 OpenAPI)要求签名中包含 HostX-date 等 header 字段,漏掉任一 header 就拒绝

验证第三方响应签名时要注意公钥格式和填充方式

少数 API(如苹果 App Store Server Notifications、部分银行接口)会返回带签名的 JSON 响应,要求你用其公钥验签。这时 PHP 的 openssl_verify() 是主力,但坑很多:

  • 公钥必须是 PEM 格式(以 -----BEGIN public KEY----- 开头),如果给的是 JWK 或 DER,得先转换:openssl_pkey_get_public("file://path/to/pubkey.pem") 才能用
  • 验签前必须从响应里完整提取出原始 payload(不含 signature 字段)和 base64url 解码后的 signature,且 payload 必须跟签名时服务端拼的**一字不差**(包括换行、空格、JSON 键序)
  • 算法要匹配:服务端若用 RS256(RSA + SHA256),PHP 得用 OPENSSL_ALGO_SHA256;若用 ES256(ECDSA),则需 openssl_pkey_get_public() 支持 EC key,PHP ≥ 7.1
  • 别用 hash_equals() 对比签名结果——那是给 HMAC 设计的,RSA/ECDSA 验签必须用 openssl_verify()

验签失败,90% 的原因是 payload 预处理不一致,而不是密钥或算法错了。

text=ZqhQzanResources