三种签名算法实现与验签:MD5 拼串、HMAC-SHA256、RSA2(附七条拼串核对清单)

简介: 签名错误是接口对接里最高频的报错,而「代码照抄文档还是错」通常不是算法问题,是拼串细节没对齐。这篇给出三种主流签名方式的完整实现、验签写法,以及七个必须逐项核对的拼串细节。

一、代码照抄文档,还是报签名错误

对接一个电商开放平台,文档里给了一段示例代码,复制过来改两个变量就能跑。

实际跑起来:sign check failed。

然后开始长达两小时的排查:确认 AppSecret 没抄错、确认时间戳没过期、确认参数名大小写、确认编码是 UTF-8……每一轮都觉得自己找到了,每一轮还是错。

这类问题的根源几乎从不在算法。MD5、HMAC、RSA 都是标准算法,任何语言的库都不会算错。错的是「参与签名的那串原文」——你拼出来的字符串和对方期望的,少了一个空格、多了一个参数、顺序差了一位,摘要就完全不同。

下面把三种签名方式拆开讲,重点放在拼串规则上。

二、三种签名方式的定位

方式 密钥关系 典型场景 强度
MD5 拼串 双方共享同一个 secret 国内老平台的主流方案 中(依赖 secret 保密)
HMAC-SHA256 双方共享同一个 key 较新平台的推荐方案 较高(结构上抗长度扩展)
RSA2 调用方持私钥,平台持公钥 回调通知验签、开放平台授权 高(无需共享密钥)

选型不是「越强越好」。对方给什么你就用什么,能改的只有实现质量。

三、MD5 拼串:七个必须对齐的细节

标准流程是四步:过滤 → 排序 → 拼接 → 加摘要。每一步都藏着坑。

import hashlib
def sign_md5(params: dict, secret: str, upper: bool = True) -> str:
    """key 排序拼串签名。
    规则:剔除空值与签名本身 → 按 key 字典序 → k=v 用 & 连接 → 末尾追加密钥 → MD5
    """
    filtered = {
        k: v for k, v in params.items()
        if k not in ("sign", "sign_type") and v not in (None, "")
    }
    pairs = ["%s=%s" % (k, filtered[k]) for k in sorted(filtered)]
    raw = "&".join(pairs) + "&key=" + secret
    digest = hashlib.md5(raw.encode("utf-8")).hexdigest()
    return digest.upper() if upper else digest

七个别对不齐的地方:

1. 空值到底参不参与。 这是最高频的分歧点。有的平台要求 v="" 也参与排序拼接,有的要求剔除。文档里通常只有一句「去掉空值」,而「空值」指 None、空字符串、还是字符串 "null",各家不同。

2. 排序是字节序还是字典序。 参数名含大小写混合时(如 appKey 与 AppId),Python 的 sorted() 按 ASCII 排,大写字母在小写之前。Java 的 TreeMap 默认也是按字符编码。但有的平台要求忽略大小写排序,那就得显式指定 key。

3. sign 字段自己参不参与。 显然不参与,但代码里忘了剔除是最常见的低级错误——尤其在你把整个请求体直接丢进签名函数的时候。

4. 密钥拼接的形式。 是 &key=secret,还是直接末尾追加 secret?看起来只差 5 个字符,但决定成败。这个必须逐字对照文档示例。

5. URL 编码的时机。 是先对每个 value 做 URL 编码再拼串,还是先拼串再编码?两者结果不同。正确顺序通常是:原始值拼串签名 → 发送时再编码。如果先编码,空格变成 %20,签名原文就变了。

6. 数组与嵌套对象的序列化。 {"ids": [1,2,3]} 拼成 ids=1,2,3 还是 ids=[1,2,3] 还是 ids=1&ids=2&ids=3?JSON body 的键序要不要固定?这一条没有统一规范,只能看对方示例。

7. 摘要大小写。 平台要 32 位大写,库默认输出小写。hexdigest().upper() 一行的事,但漏了就是两小时。

四、HMAC-SHA256:为什么它比拼串更安全

MD5 拼串有个结构性弱点:它把密钥当普通字符串拼进原文。如果拼法是 hash(secret + message),就存在长度扩展攻击——攻击者不需要知道 secret,只要知道 secret 长度和某个消息的哈希值,就能在消息后面追加内容并算出合法的哈希。

HMAC 用两次哈希(ipad / opad)把密钥和消息分离,从结构上避免了这个问题。

import hmac
import hashlib
def sign_hmac_sha256(params: dict, key: str) -> str:
    filtered = {k: v for k, v in params.items() if k != "sign" and v not in (None, "")}
    raw = "&".join("%s=%s" % (k, filtered[k]) for k in sorted(filtered))
    return hmac.new(key.encode("utf-8"), raw.encode("utf-8"), hashlib.sha256).hexdigest()

注意拼串规则和 MD5 版完全一样(排序、过滤、连接符),变的只有最后一步的摘要方式。所以如果你已经把 MD5 版的拼串逻辑写对了,切换 HMAC 只需要改两行。

顺带说一句:MD5 本身在 2004 年就被证明存在实用碰撞。用它做签名的风险不是「密钥被破解」,而是「存在两条不同消息摘要相同」的可能。平台还在用就用,但你自己设计接口时没理由再选它。

五、RSA2:私钥签名,公钥验签

前两种是对称的,双方共享一个密钥。RSA2 是非对称的,解决了「密钥怎么安全地给对方」这个问题。

典型用法是平台回调你的服务时,用它的私钥签名,你用它的公钥验签。你不需要持有任何秘密,公钥可以公开。

import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
def sign_rsa2(raw: str, private_key_pem: bytes) -> str:
    key = serialization.load_pem_private_key(private_key_pem, password=None)
    sig = key.sign(raw.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())
    return base64.b64encode(sig).decode()
def verify_rsa2(raw: str, sign_b64: str, public_key_pem: bytes) -> bool:
    key = serialization.load_pem_public_key(public_key_pem)
    try:
        key.verify(
            base64.b64decode(sign_b64),
            raw.encode("utf-8"),
            padding.PKCS1v15(),
            hashes.SHA256(),
        )
        return True
    except Exception:
        return False

SHA256withRSA 就是上面这两段。两个实战细节:

第一,被签名的原文必须先确定下来。 回调是 JSON body,签名通常是对原始 body 字符串做,不是对解析后的 dict 重新序列化。如果你解析完再 json.dumps,键序和空格都可能变,验签必失败。正确做法是:拿到原始 bytes → 验签 → 再解析。

第二,PKCS1v15 还是 PSS。 绝大多数平台用 PKCS1v15(也叫 RSA2),少数新接口用 PSS。padding 选错,报的还是「验签失败」,同一个错误信息。

六、一张对照表

细节 MD5 HMAC-SHA256 RSA2
参与签名的原文 排序拼串 排序拼串 原始 body 字符串
密钥位置 拼进原文末尾 作为 HMAC key 不在原文中
输出格式 32 位十六进制 64 位十六进制 Base64
大小写要求 多数要求大写 多数要求小写 不适用
验签方 用同一 secret 重算比对 用同一 key 重算比对 用公钥验证

七、检查清单

  • 空值(None / "" / "null")的处理方式与文档示例逐字核对过
  • 排序规则(是否忽略大小写)已确认
  • sign、sign_type 字段已从原文中剔除
  • 密钥拼接形式(&key= 还是直接追加)与示例一致
  • URL 编码发生在签名之后,不是之前
  • 数组、嵌套对象的序列化方式与示例一致
  • 摘要大小写与平台要求一致
  • 回调验签用的是原始 body 字节流,不是重新序列化的 JSON
  • RSA 的 padding 模式(PKCS1v15 / PSS)选对
  • 有一份「签名原文」的 debug 日志,出错时能直接比对

最后一条最有用。把签名前的 raw string 打进日志(脱敏后),下次报错时你只需要把它和文档示例对比一眼,而不是再猜两小时。

八、一句总结

签名这件事,算法是确定的,不确定的只有拼串规则。

所以真正该做的不是找一个「万能签名库」,而是把拼串逻辑单独抽成一个纯函数,输入是参数字典和密钥,输出是原文字符串,再单独做摘要。这样出错时你能一眼看出是原文不对还是摘要不对,而不是在一行链式调用里猜。

相关文章
|
6天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1290 6
|
4天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
|
9天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
29天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9868 33
|
15天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
6天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
694 2
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
4天前
|
人工智能 开发者
Qoder CN 大使计划正式开启|无限创造,等你一起
全年免费积分、优先内测、活动支持与更多共创机会,邀请用 Qoder 把想法变成现实的你。
465 0
|
14天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
992 1