HMAC 在线生成器
HMAC 生成器是用一把密钥给消息计算 HMAC 签名的免费工具,支持 HMAC-SHA256、SHA1、MD5、SHA224、SHA384、SHA512 与国密 SM3,一次给出 hex、大写 hex、Base64 与 Base64URL。密钥可按文本、Hex 或 Base64 解读;粘贴对方签名即可比对,对不上时自动排查算法、密钥解读、换行或写法差在哪。浏览器本地计算,密钥与消息不上传。
密钥与消息都在本机计算,不上传、不保存(只记住算法与解读方式这几项偏好)。
如何使用HMAC 在线生成器
HMAC 签名怎么算:先选算法(默认 HMAC-SHA256,还有 MD5、SHA1、SHA224、SHA384、SHA512 与国密 SM3),再填密钥与消息;也可以从「填入示例」里选 GitHub Webhook、钉钉机器人加签、飞书机器人签名等固定示例先看效果。密钥与消息都在浏览器本地计算,不上传。
结果卡同时给出小写 hex、大写 hex、Base64、Base64URL 四种写法,每行独立复制,下方公式行会写明密钥和消息各按什么方式取成了多少字节。对方文档说 secret 是十六进制或 Base64 时,把「密钥解读」切到 Hex 或 Base64;中文消息可切 GBK;拿不准算法就点「显示全部 7 种算法」一次对照。
把对方给的签名粘进「期望签名」框:本页会自动剥掉 sha256= 前缀和 URL 编码,按字节比对并告诉你一致的是哪种写法;不一致时点「帮我排查」,会在算法、密钥解读、末尾换行、CRLF、对调与拼接哈希等几百种常见口径里找出能对上的那个,并可一键套用、随时撤销。
关于HMAC 在线生成器的常见问题
- HMAC 在线生成会上传我的密钥吗?
- 不会。HMAC 在线生成全程在你的浏览器本地完成,密钥、消息、期望签名和算出的结果都不会发到任何服务器,打开 F12 的网络面板再算一次就能自证:计算过程不产生请求,断网照样出结果(页面本身会加载一个访问统计脚本,它不读取也不上报你填的内容)。本页只把算法、密钥解读、消息解读、是否显示全部算法这四项偏好写进本机 localStorage,密钥与消息一律不落盘、不进网址。三个输入框都关掉了拼写检查与自动填充——Chrome 的「增强型拼写检查」会把输入框里的文字发到云端,对密钥来说这是真实的泄露面。即便如此,也不建议在公共电脑上粘贴生产环境的密钥。
- HMAC 是加密吗,HMAC 签名能解密还原吗?
- 不是加密,也不能解密。HMAC 是消息认证码:它用密钥给消息算出一个固定长度的签名,签名里既不含消息也不含密钥,是单向的,没有任何办法从签名反推出原文或密钥。「HMAC 加密」只是一个流行的说法,严格讲它做的是认证而不是保密——消息本身照样是明文传输的,HMAC 保证的是「这段消息确实出自持有密钥的人、且一个字节都没被改过」。验证的唯一方法是用同一把密钥对同一段消息重算一遍,再和收到的签名比对,本页的「期望签名」框做的就是这件事。
- HMAC-SHA256 和 SHA256(密钥+消息) 有什么区别?
- 构造不同,结果也完全不同。HMAC 把密钥补齐到分组长度,分别与 0x36、0x5c 异或后做两轮哈希;而 SHA256(密钥+消息) 只是把两段拼起来做一次普通哈希。以密钥 deertools、消息「小鹿tools」为例,HMAC-SHA256 是 7739aee5 开头,SHA256(deertools小鹿tools) 是 61d034f3 开头,毫无关系。更要紧的是,直接拼接存在长度扩展攻击:拿到一组合法的消息与签名,攻击者不需要知道密钥就能伪造「消息 + 填充 + 任意追加内容」的合法签名,HMAC 的两轮构造正是为了堵住它。如果对方的接口其实用的是拼接哈希,本页「帮我排查」会直接指出来;单纯算拼接后的哈希请用文本 SHA 那页。
- 为什么我算的 HMAC 签名和服务端对不上?
- 绝大多数是六类口径差异:一是写法,同一个签名可以写成 hex、大写 hex、Base64、Base64URL,钉钉还要再做一次 URL 编码;二是密钥解读,secret 是当文本用,还是先按 Hex 或 Base64 解码成字节再用;三是换行与空格,消息末尾多一个换行、密钥首尾多一个空格,签名就整串不同,网页输入框还会把 CRLF 换行归一成 LF;四是中文编码,UTF-8 与 GBK 取出的字节不同;五是算法,SHA256 与 SM3 长度一样容易认错;六是签名原串本身不同,比如参数顺序、JSON 空格、时间戳单位。前五类本页的「帮我排查」会自动枚举试出来,第六类只能把服务端日志里的原串原样粘进来核对。
- HMAC 的密钥是 Hex 字符串时该怎么填?
- 看文档怎么说。如果文档说 secret 是一串十六进制表示的字节(常见于银行、支付和部分物联网平台),就把「密钥解读」切到 Hex,本页会先把它解码成字节再算;如果文档只是给了一串字符、没提编码,那它就是普通文本,保持默认的「文本」。两种解读结果完全不同:同一串 00112233 对消息 hello 做 HMAC-SHA256,当文本是 65dd8e623b68ce94…,当 Hex(4 个字节)是 1e686202515f1091…。Hex 解读允许中间夹空格、冒号或 0x 前缀,大小写不限;位数为奇数或含非十六进制字符时会指出是第几个字符出错。Base64 编码的 secret 同理,切到 Base64 即可。
- 钉钉机器人加签的 HMAC 签名怎么算?
- 钉钉自定义机器人的加签规则是:把「毫秒时间戳 + 换行 + secret」作为消息,secret 本身作为密钥,做 HMAC-SHA256,结果先 Base64 再 URL 编码,拼到 webhook 地址的 timestamp 与 sign 参数里。用本页的固定示例验证:时间戳 1700000000000、secret 为 SECexample,得到 Base64 uroalHrWHORz59talE1o26b8HuX2kOp8WlAN91F9Pj0=,URL 编码后末尾的等号变成 %3D。把 URL 编码后的串粘进期望签名框,本页会自动先做 URL 解码再比对。真实调用时时间戳必须用当前时间,且与钉钉服务器相差不能超过一小时;具体口径以钉钉开放平台官方文档为准。
- 飞书机器人签名的 HMAC 消息为什么是空的?
- 因为飞书把「秒级时间戳 + 换行 + secret」整串当成 HMAC 的密钥,消息反而是空的——和钉钉恰好相反。官方 Python 示例就是 hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256),没有传消息。用固定示例验证:时间戳 1700000000、secret 为 SECexample,密钥填「1700000000 换行 SECexample」、消息留空,HMAC-SHA256 的 Base64 是 giaCP81WKcKmxS0uSLlrqVVwRIDJGES6GpFgEr4g4C4=。很多人照着钉钉的写法把串当消息、secret 当密钥,结果自然对不上;这种「密钥与消息对调」正是本页「帮我排查」能直接识别的一类。口径以飞书开放平台官方文档为准。
- HMAC-SM3 和 HMAC-SHA256 的签名怎么区分?
- 只看签名本身分不出来。HMAC-SM3 与 HMAC-SHA256 的输出都是 256 位,也就是 32 字节、64 个十六进制字符或 44 个 Base64 字符,长度和字符集完全一样。所以拿到一个 64 位 hex 签名时,本页的期望值识别会同时提示「HMAC-SHA256 或 HMAC-SM3」。要确定只能问对方用的是哪个算法,或者两种都算一遍:打开「显示全部 7 种算法」,同一份密钥与消息的 SM3 和 SHA256 结果并排给出。政务、银行、支付类接口越来越多要求国密 HMAC-SM3,而浏览器自带的 Web Crypto 并不支持 SM3,本页用 WASM 实现,与 OpenSSL 结果一致。
- HMAC-MD5 和 HMAC-SHA1 现在还安全吗?
- MD5 与 SHA-1 的碰撞攻击并不能直接用来攻破 HMAC:HMAC 的安全性依赖的是哈希函数的伪随机性,而不是抗碰撞性,截至目前 HMAC-MD5 与 HMAC-SHA1 都没有公开的实用攻击,老接口照常使用没有问题。RFC 6151 的结论是 HMAC-MD5 作为消息认证码尚无实际漏洞,但不建议在新协议里使用;NIST 也仍然允许 HMAC-SHA1。新设计的接口请直接选 HMAC-SHA256,这是当前的默认选择。本页选 MD5 或 SHA1 时只给一条中性提示,不会把它们标成不安全。
- HMAC-SHA256 的密钥应该设多长?
- 至少与摘要等长:HMAC-SHA256 用 32 字节(256 位)随机密钥就够了,HMAC-SHA512 用 64 字节。比这短会降低安全余量;但也不是越长越好——密钥一旦超过哈希的分组长度(SHA-256 是 64 字节,SHA-512 是 128 字节),HMAC 会先把它哈希成摘要再用,再长也没有收益,本页的计量行会在这种情况下提示「已先哈希」。密钥一定要随机生成,不要用单词、手机号或项目名,可以用本站的随机密码生成器生成后,再按文档要求以文本、Hex 或 Base64 的形式交给对方。
- JWT 的 HS256 签名能用 HMAC 生成器算吗?
- 可以手工核对。HS256 就是 HMAC-SHA256:把 JWT 的前两段原样连起来(header 段 + 点 + payload 段,都是 Base64URL 字符串,不要解码)当消息,签名密钥当密钥,结果看本页的 Base64URL 那一行,应当与 JWT 的第三段逐字相同。HS384、HS512 分别对应 HMAC-SHA384、HMAC-SHA512。如果要整段解析 token、查看过期时间并校验签名,用本站的 JWT 解码器更方便;本页不做 JWT 签发,也不会替你拼 header 和 payload。
- Webhook 签名验证怎么用 HMAC-SHA256 做?
- 以 GitHub 为例:配置 webhook 时设置一个 secret,GitHub 每次推送都会用它对请求体原文做 HMAC-SHA256,放进 X-Hub-Signature-256 请求头,形如 sha256= 加 64 位小写 hex。服务端拿到请求后用同一 secret 对收到的原始请求体重算一遍,再用常数时间比较函数(Python 的 hmac.compare_digest、Node 的 crypto.timingSafeEqual)和请求头比对。用 GitHub 官方文档的示例验证:secret 为 It's a Secret to Everybody、请求体为 Hello, World!,签名是 sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17,本页「填入示例」里选 GitHub Webhook 会直接显示一致。最常见的坑是先把 JSON 解析再序列化,字段顺序与空格一变签名就对不上,必须用原始字节。
HMAC 是什么:和普通哈希、加盐、加密的区别
HMAC(Hash-based Message Authentication Code,基于哈希的消息认证码,RFC 2104)是用一把密钥给一段消息算出固定长度签名的标准构造:同样的密钥加同样的消息永远得到同样的签名, 换了密钥或者改了消息里的任何一个字节,签名就完全不同。开放平台接口的 sign 参数、Webhook 签名验证、钉钉机器人加签、 飞书机器人签名、JWT 的 HS256、谷歌验证器的 6 位动态码,背后都是同一块积木。
它的构造是 HMAC(K, m) = H((K′ ⊕ opad) ‖ H((K′ ⊕ ipad) ‖ m)):K′ 是把密钥补零到哈希分组长度(太长则先哈希), ipad 是 0x36 重复、opad 是 0x5c 重复,一共做两轮哈希。这和三样东西都不一样:普通哈希没有密钥,谁都能算; 加盐哈希只是把盐和原文拼起来算一次(本站文本 SHA 与 文本 MD5两页的加盐就是这种),存在长度扩展攻击;而加密是可逆的,HMAC 不可逆。所以常说的「HMAC 加密」其实是认证而不是加密, 本页也不提供任何解密入口。
| 常见叫法 | 通常指什么 | 在本页怎么做 |
|---|---|---|
| HMAC 在线生成 / HMAC 在线计算 / HMAC Generator | 给定密钥与消息算出 HMAC 签名 | 选算法,填密钥和消息即出结果 |
| HMAC-SHA256 在线 / HMAC SHA256 Online / HMAC-SHA256 签名 | 最常用的 HMAC-SHA256 | 默认算法就是它 |
| HMAC 加密 | 其实是 HMAC 签名,不可逆、不能解密 | 同上;验证靠重算比对 |
| HMAC 签名验证 / Webhook 签名验证 | 用同一密钥重算,再与收到的签名比对 | 粘进「期望签名」框 |
| API 签名计算 / 接口签名生成 | 按平台规则拼出签名原串后做 HMAC | 原串粘进消息框,见下文平台口径 |
| HMAC Base64 / HMAC hex | 同一签名的两种写法 | 四种写法一次给全 |
| 国密 HMAC / HMAC-SM3 | 用国密 SM3 做底层哈希的 HMAC | 算法里选 SM3 |
HMAC-SHA256、HMAC-SHA1、HMAC-MD5、HMAC-SHA512、HMAC-SM3 的长度对照
HMAC 的输出长度等于底层哈希的摘要长度。拿到一个不知出处的签名,数一下长度基本就能定位算法—— 本页的期望值识别用的也是这张表:
| 算法 | 位数 | 字节 | 分组长度 | hex 长度 | Base64 长度 | Base64URL 长度 |
|---|---|---|---|---|---|---|
| HMAC-MD5 | 128 位 | 16 字节 | 64 字节 | 32 个字符 | 24 个字符 | 22 个字符 |
| HMAC-SHA1 | 160 位 | 20 字节 | 64 字节 | 40 个字符 | 28 个字符 | 27 个字符 |
| HMAC-SHA224 | 224 位 | 28 字节 | 64 字节 | 56 个字符 | 40 个字符 | 38 个字符 |
| HMAC-SHA256 | 256 位 | 32 字节 | 64 字节 | 64 个字符 | 44 个字符 | 43 个字符 |
| HMAC-SHA384 | 384 位 | 48 字节 | 128 字节 | 96 个字符 | 64 个字符 | 64 个字符 |
| HMAC-SHA512 | 512 位 | 64 字节 | 128 字节 | 128 个字符 | 88 个字符 | 86 个字符 |
| HMAC-SM3 | 256 位 | 32 字节 | 64 字节 | 64 个字符 | 44 个字符 | 43 个字符 |
HMAC Base64、hex 与 Base64URL:同一个签名的四种写法
签名本身是一串字节,写成文本才需要选写法。签名对不上的第一大原因就是写法不同,所以本页四种一起给。 以密钥 deertools、消息「小鹿tools」的 HMAC-SHA256 为例(示例特意选了含加号和斜杠的值,能看清三处差异):
| 写法 | 值 | 谁在用 |
|---|---|---|
| 小写 hex | 7739aee535dedcb000391efba74406ba5fd3b12b05f893ac4141fe34c075c2c6 | GitHub Webhook、多数开放平台的 sign |
| 大写 hex | 7739AEE535DEDCB000391EFBA74406BA5FD3B12B05F893AC4141FE34C075C2C6 | 部分支付与银行接口要求大写 |
| Base64 | dzmu5TXe3LAAOR77p0QGul/TsSsF+JOsQUH+NMB1wsY= | 钉钉、飞书机器人加签(钉钉还要再 URL 编码) |
| Base64URL | dzmu5TXe3LAAOR77p0QGul_TsSsF-JOsQUH-NMB1wsY | JWT 签名段:加号换减号、斜杠换下划线、去掉等号 |
注意 Base64 编的是签名的原始 32 个字节,不是那串 64 位 hex 文本;把 hex 当字符串再 Base64 会得到一个 88 字符的串,那是错的。比对时 hex 大小写无关,Base64 大小写敏感——本页把期望值先解码成字节再比,两种情况都不会误判。 钉钉要求的 URL 编码可以用本站 URL 编码 工具做,Base64 与原文互转用 Base64 编码解码。
HMAC 密钥是文本还是字节:Hex、Base64 密钥的两种解读
HMAC 吃的是字节。很多平台给的 secret 是一串 hex 或 Base64,到底是「当文本用」还是「先解码成字节再用」, 是签名对不上的第二大原因。两种用法都真实存在,所以本页不替你自动切换,只在密钥看起来像编码过的字节时给一条提示:
| 密钥串 | 解读方式 | 取出的字节 | HMAC-SHA256 结果 |
|---|---|---|---|
| 00112233 | 文本 | 8 字节(字符 0、0、1、1…) | 对 hello:65dd8e623b68ce94… |
| 00112233 | Hex | 4 字节 00 11 22 33 | 对 hello:1e686202515f1091… |
| ZGVlcnRvb2xz | 文本 | 12 字节 | 对「小鹿tools」:62bf82f73095e042… |
| ZGVlcnRvb2xz | Base64 | 9 字节,即 deertools | 对「小鹿tools」:7739aee535dedcb0…(与文本密钥 deertools 相同) |
Hex 解读允许空格、冒号和 0x 前缀;Base64 解读兼容 URL 安全字符集(减号、下划线)和缺失的等号补齐。 非法字符会指出是第几个,不会悄悄忽略。密钥可以为空(RFC 有定义),但空密钥意味着任何人都能算出同样的签名。
常见平台签名口径:GitHub Webhook 签名验证、钉钉机器人加签、飞书机器人签名
下面每条都带一个写死的示例值(与今天的日期无关),在「填入示例」里选同名示例即可逐位对照:
| 平台 | 密钥 | 消息 | 算法与写法 | 示例结果 |
|---|---|---|---|---|
| GitHub Webhook 签名验证 | webhook 的 secret(示例 It's a Secret to Everybody) | 原始请求体(示例 Hello, World!) | HMAC-SHA256,小写 hex,前缀 sha256= | sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17 |
| 钉钉机器人加签 | secret(示例 SECexample) | 毫秒时间戳 + 换行 + secret(示例 1700000000000) | HMAC-SHA256,Base64 后再 URL 编码 | uroalHrWHORz59talE1o26b8HuX2kOp8WlAN91F9Pj0%3D |
| 飞书机器人签名 | 秒级时间戳 + 换行 + secret(示例 1700000000) | 空 | HMAC-SHA256,Base64 | giaCP81WKcKmxS0uSLlrqVVwRIDJGES6GpFgEr4g4C4= |
API 签名计算的通用套路:多数开放平台的接口签名生成规则是「把除 sign 以外的参数按参数名字典序排序 → 拼成 key=value&key=value → 有的再拼上时间戳或 nonce → 用 app secret 做 HMAC-SHA256 → 转 hex 或 Base64」。 各家在排序、是否 URL 编码、空值是否参与、时间戳是秒还是毫秒上各不相同,所以本页不做原串的自动拼装, 而是让你把服务端日志里的签名原串原样粘进消息框,再和对方的签名逐字节比对。本页也不读取当前时间, 示例时间戳都是固定值;生产环境的签名逻辑请写在服务端,不要把生产密钥写进前端代码。
HMAC 签名对不上的排查清单
「帮我排查」做的事情,就是把下面这张清单自动化:在算法 × 密钥解读 × 密钥首尾空白 × 消息编码 × 消息换行变体 × 密钥消息对调这几个维度里,按「离你当前设置差几处」从近到远逐个试(最多 600 种组合),外加两种「直接拼接后普通哈希」的冒充写法, 找到与期望值字节一致的那个就给出结论和一键套用。都没命中时它会如实说没找到,而不是编一个结论。
| 检查项 | 典型表现 | 本页实测 |
|---|---|---|
| 写法 | 一边是 hex 一边是 Base64,或 Base64 被 URL 编码过 | 期望值按字节比,写法差异自动消除 |
| 消息末尾换行 | echo 或读文件时多了 \n | 「小鹿tools」7739aee5… 与带换行的 cad73564… |
| CRLF 与 LF | 网页输入框会把粘进来的 \r\n 统一成 \n | line1↵line2 是 5016ca7d…,CRLF 版是 a90c6fdd… |
| 密钥首尾空格 | 复制 secret 时带上了空格 | 密钥「deertools 」(尾部空格)得 daced483… |
| 密钥解读 | secret 是 hex 或 Base64 编码的字节 | 见上一节对照表 |
| 中文编码 | 对方按 GBK 取字节 | GBK 下 HMAC-SHA256 是 5c508025… |
| 密钥与消息对调 | 把飞书的串当成了消息 | 对调后得 96fee38d… |
| 根本不是 HMAC | 对方用的是 SHA256(密钥+消息) | 拼接哈希得 61d034f3… |
以上都排除了,剩下几乎总是签名原串本身不同:JSON 的字段顺序和空格、参数是否排序、时间戳单位、 是否把 sign 自己也算了进去。这时请拿服务端日志里打印出的原串来比,而不是自己重新拼一次。
各语言 HMAC-SHA256 签名的等价写法
下面几段代码对密钥 deertools、消息「小鹿tools」算出的结果都与本页一致:
import hmac, hashlib, base64
sig = hmac.new(b"deertools", "小鹿tools".encode("utf-8"), hashlib.sha256)
print(sig.hexdigest()) # 7739aee5…c075c2c6
print(base64.b64encode(sig.digest()).decode()) # dzmu5TXe…NMB1wsY=
# 服务端比对用常数时间比较,不要用 ==
hmac.compare_digest(sig.hexdigest(), received)const crypto = require("crypto");
const h = crypto.createHmac("sha256", "deertools").update("小鹿tools", "utf8");
console.log(h.digest("hex")); // 7739aee5…c075c2c6
// 比对:crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)),两边长度要先相等Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec("deertools".getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] sig = mac.doFinal("小鹿tools".getBytes(StandardCharsets.UTF_8));
String b64 = Base64.getEncoder().encodeToString(sig); // dzmu5TXe…NMB1wsY=# PHP
hash_hmac('sha256', '小鹿tools', 'deertools'); // 注意 PHP 是「算法, 消息, 密钥」的顺序
# Go
mac := hmac.New(sha256.New, []byte("deertools")); mac.Write([]byte("小鹿tools"))
# openssl:printf 不带换行;换成 echo 会多签一个 \n,结果就成了 cad73564… 那一串
printf '小鹿tools' | openssl dgst -sha256 -hmac 'deertools'两个常见坑:一是 PHP 的 hash_hmac 参数顺序是「算法、消息、密钥」,和多数语言相反,写反了就等于密钥消息对调; 二是服务端比对签名要用常数时间比较(hmac.compare_digest、crypto.timingSafeEqual、MessageDigest.isEqual), 普通的字符串相等会因为提前返回而泄露时间信息。
HMAC 的两个冷知识(带实测值)
一、超长密钥 K 与它的哈希 H(K) 是等价的。RFC 2104 规定密钥超过分组长度时先哈希再用, 于是 100 个字母 a 作密钥,和这 100 个 a 的 SHA-256 摘要(32 字节)作密钥,对 hello 的 HMAC-SHA256 都是 f9dd31832e4f13f0e693ebd296f52e1692d46472462aecf3853715be5eaf110c。这也说明密钥超过 64 字节不会更安全。
二、密钥末尾补 0x00 不改变签名。密钥不足分组长度时会右补零,所以 key 与 key 后面多一个零字节 (Hex 写作 6b657900)对 hello 的 HMAC-SHA256 同为 9307b3b915efb5171ff14d8cb55fbcc798c6c0ef1456d66ded1a6aa723a58b7b。用 Hex 解读切过去就能当场验证。 这两条都不构成实际漏洞(前提是密钥本身随机且保密),但能解释一些「换了密钥签名却没变」的怪现象。 顺带一提,TOTP 动态口令的 6 位码就是 HMAC-SHA1(密钥, 时间片) 再截断得到的。