JWT签名必须用hash_hmac()而非md5()/sha1(),因其是带密钥的HMAC,可防篡改与重放;需对base64url编码的header.payload拼接串计算,密钥须二进制安全,raw_output必须为true,并严格匹配算法声明。

JWT签名为什么必须用hash_hmac()而不是md5()或sha1()
因为JWT签名不是简单哈希,而是带密钥的消息认证码(HMAC),必须防篡改+防重放。直接用md5($payload . $secret)或sha1()是严重漏洞:攻击者可利用长度扩展攻击或暴力碰撞伪造签名。
-
hash_hmac()内部做了密钥预处理和两次哈希嵌套(ipad/opad),标准且抗侧信道 - PHP的
hash_hmac()默认使用sha256,符合JWT RFC 7519推荐算法 - 别手写拼接——
hash_hmac('sha256', $msg, $key, true)第三个参数$key必须是二进制安全字符串(避免空字节截断)
生成JWT header.payload.signature三段式时,hash_hmac()该喂什么数据
只对base64url_encode(header) . '.' . base64url_encode(payload)这两段做HMAC,**不能包含末尾的signature部分,也不能加额外点号或换行**。这是JWT规范强制要求,错一点签名就无效。
- header和payload必须先JSON编码,再用
base64url_encode()(不是base64_encode())——要替换+为-、/为_、去掉= - 签名前拼接字符串示例:
$input = $encoded_header . '.' . $encoded_payload,然后hash_hmac('sha256', $input, $secret_key, true) - 签名结果必须再
base64url_encode(),不是直接转十六进制——否则验证端无法识别
常见签名验证失败的三个硬坑
90%的JWT验证失败不是算法问题,而是编码/边界细节出错。
- 密钥用了
utf8_encode()或trim()——PHP中$secret含BOM或不可见空格会导致hash_hmac()输入不一致 - header里写了
"alg": "HS256"但代码里用了hash_hmac('sha512', ...)——算法声明和实际计算必须严格匹配 - payload里时间字段用
time()但没四舍五入到整秒,或用了microtime(true)——JWT要求iat/exp是整型Unix时间戳
PHP 7.4+ 可直接用hash_hmac(),但注意raw_output参数
JWT签名要求原始二进制输出,不是十六进制字符串,所以raw_output必须设为true。设成false会返回32位hex字符串,base64url编码后长度翻倍,验证必然失败。
- 正确写法:
hash_hmac('sha256', $input, $secret, true) - 错误写法:
hash_hmac('sha256', $input, $secret)(默认false,返回hex) - 如果用
openssl_sign()等替代方案,反而绕远路——hash_hmac()更轻量、无依赖、零配置
最麻烦的从来不是调哪个函数,而是header和payload编码是否真·base64url,密钥有没有被意外trim或转码,以及签名前那串拼接字符串里有没有多一个空格、少一个点。这些地方一错,hash_hmac()算得再准也没用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











