3. 生成算法选型:对称与非对称签名、JWT标准、HMAC与RSA实现示例
生成的核心是数字签名,它决定了的防篡改能力和分发效率。支付SDK中常见的签名算法分为对称签名(如HMAC)和非对称签名(如RSA、ECDSA),而JWT(JSON Web )则提供了标准化的载荷结构和签名流程。本章从原理、选型对比到具体实现,帮助你在支付场景中做出正确选择。
3.1 对称签名与非对称签名对比
| 维度 | 对称签名 (HMAC) | 非对称签名 (RSA) |
|---|---|---|
| 密钥 | 签名与验证使用同一个密钥(对称密钥) | 私钥签名,公钥验证(密钥对) |
| 性能 | 快,适合高QPS场景 | 慢约10~100倍(取决于密钥长度) |
| 密钥分发 | 需通过安全信道传递到验证方;分发困难 | 公钥可公开,私钥仅签名方持有;分发简单 |
| 适用场景 | 内部系统间、微服务网关(共享密钥) | 跨机构通信、开放平台、第三方SDK集成 |
| 安全风险 | 密钥泄露后所有可被伪造 | 私钥泄露则威胁签名方,公钥泄露不影响验证 |
选型经验:支付SDK对公网开放的API,优先使用非对称签名(如RSA或ECDSA);内部服务间调用,使用HMAC可降低计算延迟。
3.2 JWT标准与结构
JWT(RFC 7519)定义了一种紧凑的、自包含的格式,由三个Base64URL编码的部分组成,以点号分隔:
- Header:声明类型(typ: JWT)和签名算法(alg: HS256 / RS256)。
- Payload:携带声明(claims),如用户ID、过期时间(exp)、权限(scope)等。支付SDK中常用
sub(商户ID)、iat(签发时间)、exp(过期时间)。 - Signature:对前两部分的签名,确保完整性。
标准JWT示例(未编码):
Header: {"alg":"RS256","typ":"JWT"}
Payload: {"sub":"merchant_123","iat":1719800000,"exp":1719803600,"scope":"payment:write"}
Signature:
支付SDK注意:Payload中禁止存放敏感数据(如银行卡号、CVV),JWT仅签名未加密。如需加密,应使用JWE标准。
3.3 HMAC实现示例(对称签名)
HMAC(Hash-based Message Authentication Code)结合哈希函数与密钥生成签名。支付SDK中常用HMAC-SHA256。
生成HMAC JWT
// 示例(伪代码风格,实际语言可映射)
import hmac
import hashlib
import base64
def create_hmac_jwt(payload, secret):
header = b'{"alg":"HS256","typ":"JWT"}'
b64_header = base64url_encode(header)
b64_payload = base64url_encode(payload)
signing_input = b64_header + b'.' + b64_payload
signature = hmac.new(secret, signing_input, hashlib.sha256).digest()
b64_sig = base64url_encode(signature)
return b64_header + b'.' + b64_payload + b'.' + b64_sig
验证HMAC JWT
def verify_hmac_jwt(, secret):
parts = .split('.')
if len(parts) != 3: return False
b64_header, b64_payload, b64_sig = parts
signing_input = b64_header + '.' + b64_payload
expected_sig = hmac.new(secret, signing_input.encode(), hashlib.sha256).digest()
actual_sig = base64url_decode(b64_sig)
return hmac.compare_digest(expected_sig, actual_sig)
3.4 RSA实现示例(非对称签名)
RSA使用私钥签名、公钥验签,支持长度2048位及以上。支付SDK中算法标识为 RS256(RSA + SHA-256)。
生成RSA JWT
from cryptography.hazmat.primitives import hashes, asymmetric
from cryptography.hazmat.primitives.asymmetric import rsa, padding
def create_rsa_jwt(payload, private_key):
header = b'{"alg":"RS256","typ":"JWT"}'
b64_header = base64url_encode(header)
b64_payload = base64url_encode(payload)
signing_input = b64_header + b'.' + b64_payload
signature = private_key.sign(
signing_input,
padding.PKCS1v15(),
hashes.SHA256()
)
b64_sig = base64url_encode(signature)
return b64_header + b'.' + b64_payload + b'.' + b64_sig
验证RSA JWT
def verify_rsa_jwt(, public_key):
parts = .split('.')
b64_header, b64_payload, b64_sig = parts
signing_input = b64_header + '.' + b64_payload
signature = base64url_decode(b64_sig)
try:
public_key.verify(
signature,
signing_input.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
return False
常见错误:
- 密钥格式不统一:HMAC需二进制密钥,RSA需PEM格式,使用前务必正确解析。
- 算法标识与实现不匹配:Header中alg声明“HS256”但实际使用RSA签名,导致验签失败。
- Base64URL填充处理:JWT使用无填充的Base64URL,标准Base64解码前需补等号,否则报错。
- 过期时间未校验:未检查
exp声明导致过期仍被放行。
3.5 算法选型性能对比
| 算法 | 密钥长度 | 签名耗时(微秒) | 验签耗时(微秒) | 长度 |
|---|---|---|---|---|
| HMAC-SHA256 | 256 bit | ~15 | ~15 | 约180字符 |
| RSA-2048 | 2048 bit | ~800 | ~20 | 约400字符 |
| ECDSA P-256 | 256 bit | ~25 | ~25 | 约160字符 |
建议:高吞吐的支付内网服务可使用HMAC;对外API推荐RSA或ECDSA(ECC性能优于RSA,同等安全强度下密钥更短)。
3.6 调试与部署要点
在支付SDK集成阶段,以下步骤可帮助快速定位问题:
- 打印签名前的签名输入(Header + "." + Payload),与验证方对齐字节流。
- 使用已知测试向量:jwt.io或官方工具验证签名正误。
- 检查时区与时间偏差:exp和iat使用UTC unix时间戳,服务器允许设置几秒的偏移容忍度。
- 密钥轮换:部署时提供多版本公钥(kid字段),支持平滑过渡。
3.7 性能优化策略
- 缓存公钥对象:RSA公钥解析较慢,第一次加载后复用,避免每次验签都重新解析PEM。
- 池化签名器:对HMAC使用线程安全的
hmac实例,或使用HmacPool减少对象创建。 - 批量验证:对于MQ或事件驱动的支付回调,可批量读取后并行验签,利用多核。
- 移除不必要的Payload声明:减小体积可降低网络传输与Base64编码开销。
3.8 常见错误排查清单
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
Signature verification failed |
密钥不匹配 / 签名输入不同(如空格、换行差异) | 对比签名前的字节流;检查密钥格式(如HMAC需二进制) |
JWT expired |
exp 时间小于当前时间 |
检查系统时钟同步(NTP);增加 leeway 容忍 |
Invalid algorithm |
Header中的alg与验签实现不一致(如HS256 vs RS256) | 统一算法标识;拒绝非预期alg防止算法混淆攻击 |
| Base64URL解码错误 | 未处理标准Base64与URL安全变体的差异(+ / -、/ / _、=去掉) | 使用专用 base64url 库或手动替换字符 |
生产建议:支付SDK中对JWT的验证应拒绝
alg: none 的,并始终检查 typ 是否为 JWT,防止类型混淆。