HMAC 与 RSA 签名场景对比
解读
国内 PHP 面试中,面试官问“HMAC 与 RSA 签名场景对比”并不是想听加密算法原理背诵,而是考察候选人能否在真实业务里“选对、用对、防错”。
常见场景:
- 开放接口防篡改(如电商 ERP 回调、支付通知)。
- 内部微服务之间信任传递(如订单服务 → 库存服务)。
- 第三方 JWT 颁发与校验。
- 合规留痕(国密改造、等保 2.0 审计)。
回答必须体现“性能、密钥管理、国密合规、PHP 实现细节”四个维度,否则会被追问“那你们线上为什么不用 RSA?”或“HMAC 密钥怎么轮换?”
知识点
-
算法本质
HMAC:对称哈希消息认证码,双方共享同一密钥,运算 = hash(key ∥ opad ∥ hash(key ∥ ipad ∥ message))。
RSA:非对称数字签名,私钥签名、公钥验签,运算 = 私钥加密消息摘要。 -
安全假设
HMAC 依赖“密钥绝对保密”,泄露即伪造。
RSA 依赖“私钥绝对保密+公钥可信分发”,公钥被篡改则验签失效。 -
性能(PHP 7.4 + OpenSSL 1.1.1 实测,单核)
HMAC-SHA256 1 KB 报文 ≈ 0.03 ms,内存 0 额外分配。
RSA-2048 签名 ≈ 1.2 ms,验签 ≈ 0.05 ms,内存 2 次 256 B 临时分配;RSA-4096 签名 ≈ 8 ms。 -
国密合规
HMAC 对应 SM3-HMAC,RSA 对应 SM2;SM2 签名性能与 RSA-2048 接近,但证书链需国密 CA,PHP 需安装 gmssl 扩展。 -
PHP 函数
HMAC:hash_hmac('sha256', key, true)。
RSA:openssl_sign(sig, $pkey, OPENSSL_ALGO_SHA256) / openssl_verify。 -
密钥管理
HMAC 密钥 ≤ 64 B,存 Kubernetes Secret、阿里云 KMS 对称密钥,可自动轮换。
RSA 私钥需硬件加密机(HSM)或 KMS 非对称密钥,公钥写死在代码或 JWKS 接口,轮换需双证书并行灰度。 -
典型攻击
HMAC:重放攻击(需 timestamp/nonce)、密钥硬编码泄露。
RSA:ccs 注入(早期 OpenSSL)、低版本填充攻击(PKCS#1 v1.5)、公钥被替换。 -
国内监管红线
等保三级以上系统若使用 RSA,密钥长度 ≥ 2048 位且需双因素保护私钥;若走金融移动客户端,需符合《中国金融移动支付可信环境技术规范》,推荐 SM2。
答案
场景一:内部高并发 RPC,延迟敏感,双方已共享密钥
选 HMAC-SHA256;PHP 端在 Guzzle 中间件里统一加签:
$signature = base64_encode(hash_hmac('sha256', $body, $_ENV['RPC_KEY'], true));
验签端复用相同密钥,整体增加 < 0.1 ms,QPS 可保持 2 万+。
密钥通过 KMS 每 24 h 轮换,老密钥保留 5 min 平滑过渡。
场景二:对外开放回调,调用方不可控、数量大
选 RSA-2048;我方生成私钥存 HSM,公钥通过 HTTPS 静态接口暴露。
PHP 接收回调后:
$pub = openssl_pkey_get_public('file://'.__DIR__.'/rsa_pub.pem');
$ok = openssl_verify($body, base64_decode($_SERVER['HTTP_X_SIGN']), $pub, OPENSSL_ALGO_SHA256);
防止重放再加 timestamp 校验 ±5 min。
若客户为国密改造单位,同时提供 SM2 公钥,代码分支用 gmssl 扩展验签。
场景三:混合场景(JWT 自签发 + 第三方网关)
头部内双算法字段:
alg=HS256 用于内部微服务,alg=RS256 用于外部网关。
PHP 用 lcobucci/jwt 3.x,根据 aud 字段自动切换密钥,实现“一套代码,两套信任域”。
一句话总结:
“内网带宽换时间,用 HMAC;外网不可信通道,用 RSA;国密合规,用 SM2;PHP 里先测性能,再管密钥,最后防重放。”
拓展思考
-
零信任架构下,HMAC 密钥如何做到“每次请求不同”?
答:引入 AWS-Style Signature V4,把密钥派生为 kDate → kService → kSigning,每请求只用一次派生密钥,泄露也无法伪造二次请求。PHP 实现需 30 行代码,但需缓存日期密钥减少派生开销。 -
RSA 签名长度固定 256 B,报文整体增大 10%,移动端弱网如何优化?
答:
a) 使用 RSA-PSS 签名后只传 64 B 的 “签名指纹 + 短随机数”,服务端通过缓存公钥索引还原完整签名。
b) 或者切到 ECDSA with P-256,签名长度 64 B,PHP 8.1 以上 openssl_sign 已支持。 -
国内小程序主体无法上传 RSA 公钥,只能提供 HEX 字符串,如何防止公钥被运营人员误改?
答:把公钥写入小程序 app.json 的 subPackage 里,PHP 验签前先计算公钥文件的 SHA-256,与本地预存哈希比对,一致才验签,实现“公钥完整性自校验”。 -
若公司未来要上科创板,审计要求“所有签名算法可回溯”,PHP 如何低成本做审计链?
答:在签名同时写一条日志到阿里云 SLS,字段包括 algorithm、key_id、signature、timestamp、request_id,日志 Topic 开启不可篡改保存 7 年;配合 KMS 的 key rotation 记录,即可满足券商 IT 审计要求。