如何使用在线工具手动验证 JWT 签名

Posted

技术标签:

【中文标题】如何使用在线工具手动验证 JWT 签名【英文标题】:How to manually validate a JWT signature using online tools 【发布时间】:2018-10-11 19:16:39 【问题描述】:

据我所知,验证JWT 签名是一个简单的过程。但是当我使用一些在线工具为我做这件事时,它不匹配。如何在不使用 JWT 库的情况下手动验证 JWT 签名?我需要一个快速的方法(使用可用的在线工具)来演示这是如何完成的。

我使用以下信息在https://jwt.io/#debugger-io 上创建了我的JWT

算法: HS256 秘密: hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6 标题: “alg”:“HS256”, “典型”:“智威汤逊” 有效负载: “子”:“1234567890”, “名称”:“约翰·多伊”, “iat”:1516239022 验证签名(部分): 秘密值更改为以上 “已检查”秘密 base64 编码(无论是否检查,仍然得到不同的值)

智威汤逊:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.wDQ2mU5n89f2HsHm1dluHGNebbXeNr748yJ9kUNDA

手动JWT签名验证尝试:

使用 base64UrlEncode 计算器(http://www.simplycalc.com/base64url-encode.php 或 https://www.base64encode.org/)

如果我: (不是网站的实际价值,已修改以显示工具最终将为我构建的内容)

base64UrlEncode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") + "." + base64UrlEncode("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ")

我明白了:

ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5.ZXlKemRXSWlPaUl4TWpNME5UWTNPRGt3SWl3aWJtRnRaU0k2SWtwdmFHNGdSRzlsSWl3aWFXRjBJam94TlRFMk1qTTVNREl5ZlE=

注意:如果我应该对已经编码的值进行编码,或者按原样使用已经编码的值,我会感到有些困惑。

(即使用base64UrlEncode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") + "." + base64UrlEncode("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ")"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ")。

不管我应该做什么,最终结果仍然与签名不匹配。我倾向于我应该重新编码编码值,无论这是否正确。

然后使用 HMAC 生成器计算器(https://codebeautify.org/hmac-generator 或 https://www.freeformatter.com/hmac-generator.html#ad-output)

(不是网站的实际价值,已修改以显示工具最终将为我构建的内容)

HMACSHA256( "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5.ZXlKemRXSWlPaUl4TWpNME5UWTNPRGt3SWl3aWJtRnRaU0k2SWtwdmFHNGdSRzlsSWl3aWFXRjBJam94TlRFMk1qTTVNREl5ZlE=", “hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6” )

这让我很感动:

a2de322575675ba19ec272e83634755d4c3c2cd74e9e23c8e4c45e1683536e01

这与JWT 的签名部分不匹配:

wDQ2mU5n89f2HsHm1dluHGNebbXeNr748yJ9kUNDNCAM != a2de322575675ba19ec272e83634755d4c3c2cd74e9e23c8e4c45e1683536e01


目的:

我需要确认这一点的原因是为了证明能够验证 JWT 未被篡改,而无需解码 JWT

我的客户 Web 界面不需要解码 JWT,因此他们无需为此安装 jwt 包。他们只需要做一个简单的验证来确认JWT 没有被篡改(尽管不太可能),然后再存储JWT 以供将来的API 调用。

【问题讨论】:

也许你的问题只是你编码了一个已经编码的值:base64UrlEncode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")。字符串“eyJh...”已经是"alg":"HS256","typ":"JWT"编码的结果,所以再编码没有意义。 @jps 这是一个很好的观点,如果我再次对其进行编码,我验证并没有改变任何东西,无论哪种方式签名都不正确。阅读说明听起来好像我需要base64UrlEncode JWT 中已经存在的值,最终对其进行两次编码……尽管我开始倾向于不需要第二次对其进行编码。我将在问题中添加此注释以进行澄清,谢谢。 总是只有一次编码。 base64UrlEncode(header) + "." + base64UrlEncode(payload)中的payload和header指的是可读的JSON。 我会同意你的看法@jps。我希望这能解决这个问题,我在问题中添加了一个注释。 我刚刚尝试了在线 HMAC 工具(我以前不知道),结果也与我在 jwt.io 上得到的结果不同。我也很惊讶,现在不知道出了什么问题。 【参考方案1】:

这都是格式和编码的问题。

在https://jwt.io 上,您会根据您的输入值和密码获得此令牌:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

我们要证明签名:

3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

是正确的。

签名是经过 Base64url 编码的 HMAC-SHA256 哈希。 (如RFC7515 中所述)

当您使用online HMAC generator 计算哈希时

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

秘密

hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6

你得到

de921a2a4b225fd66ff0983e8566eb0f6e1584bdfa84120568da40e1f571dbd3

作为结果,这是一个 HMAC-SHA256 值,但不是 Base64url 编码的。这个哈希是一个大数字的十六进制字符串表示。

要将其与 https://jwt.io 中的值进行比较,您需要将值从它的十六进制字符串表示形式转换回数字并 Base64url 对其进行编码。

以下脚本正在执行此操作,并且还使用 crypto-js 来计算它自己的哈希值。这也可以是您在没有 JWT 库的情况下进行验证的一种方式。

var CryptoJS = require("crypto-js");

// the input values
var base64Header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
var base64Payload = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ";
var secret = "hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6";

// two hashes from different online tools
var signatureJWTIO = "3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M";
var onlineCaluclatedHS256 =  "de921a2a4b225fd66ff0983e8566eb0f6e1584bdfa84120568da40e1f571dbd3";

// hash calculation with Crypto-JS. 
// The two replace expressions convert Base64 to Base64url format by replacing 
// '+' with '-', '/' with '_' and stripping the '=' padding
var base64Signature = CryptoJS.HmacSHA256(base64Header + "." + base64Payload , secret).toString(CryptoJS.enc.Base64).replace(/\+/g,'-').replace(/\//g,'_').replace(/\=+$/m,'');

// converting the online calculated value to Base64 representation
var base64hash = new Buffer.from(onlineCaluclatedHS256, 'hex').toString('base64').replace(/\//g,'_').replace(/\+/g,'-').replace(/\=+$/m,'')


// the results:
console.log("Signature from JWT.IO             : " + signatureJWTIO);
console.log("NodeJS calculated hash            : " + base64Signature);
console.log("online calulated hash (converted) : " + base64hash);

结果是:

Signature from JWT.IO             : 3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

NodeJS calculated hash            : 3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

online calulated hash (converted) : 3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

一模一样!

结论:

不同在线工具计算的值都是正确的,但由于格式和编码不同,无法直接比较。 如上所示的一个小脚本可能是一个更好的解决方案。

【讨论】:

【参考方案2】:

我遇到了同样的问题,直到我发现我使用的是普通的base64 编码而不是base64url。 中间还有一些小细节。 这是分步手册,希望能让整个过程更加清晰。

注意事项

注意 1: 您必须从 JSON 字符串(标头和有效负载)中删除所有空格和换行符。 当您生成 JWT 令牌时,它会在 jwt.io 上隐式完成。

注意 2: 要将 JSON 字符串转换为 cryptii.com 上的 base64url 字符串,请创建以下配置:

First view: Text

Second view: Encode
    Encoding: Base64
    Variant: Standard 'base64url' (RFC 4648 §5)

Third view: Text

注意 3: 要将 HMAC HEX 代码(签名)转换为 cryptii.com 上的 base64url 字符串,请创建以下配置:

First view: Bytes
    Format: Hexadecimal
    Group by: None

Second view: Encode
    Encoding: Base64
    Variant: Standard 'base64url' (RFC 4648 §5)

Third view: Text

手册

您将只需要两个在线工具:

    [工具1]: cryptii.com - 用于base64url 编码, [工具 2]: codebeautify.org - 用于 HMAC 计算。

在cryptii.com 上,您既可以进行base64url 编码/解码,也可以进行HMAC 计算,但对于HMAC,您需要提供与jwt.io 上的输入不同的十六进制密钥,因此我使用了单独的服务HMAC 计算。

输入数据

在本手册中,我使用了以下数据:

标题:

"alg":"HS256","typ":"JWT"

有效载荷:

"sub":"1234567890","name":"John Doe","iat":1516239022

秘密(密钥):

The Earth is flat!

秘密不是base64 编码的。

第1步:转换标题[工具1]

标题(纯文本):

"alg":"HS256","typ":"JWT"

标头(base64url 编码):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

第2步:转换payload [工具1]

有效载荷(纯文本):

"sub":"1234567890","name":"John Doe","iat":1516239022

有效载荷(base64url 编码):

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

步骤 3:计算 HMAC 码(签名)[工具 2]

使用SHA256算法计算HMAC。

输入字符串(base64url 编码的标头和有效负载,用点连接):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

计算代码(十六进制数):

c8a9ae59f3d64564364a864d22490cc666c74c66a3822be04a9a9287a707b352

计算的 HMAC 代码是签名的十六进制表示。 注意:不应将base64url编码为纯文本字符串,而应编码为字节序列。

第四步:将计算得到的HMAC码编码为base64url[工具1]:

签名(字节):

c8a9ae59f3d64564364a864d22490cc666c74c66a3822be04a9a9287a707b352

签名(base64url 编码):

yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I

总结

这是我们的结果(全部为base64url 编码):

标题:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

有效载荷:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

签名:

yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I

来自jwt.io的结果:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I

如您所见,所有三个部分都是相同的。

【讨论】:

+1 表示“计算出的 HMAC 代码是签名的 HEX 表示形式。注意:不应将其编码为 base64url 作为纯文本字符串,而是作为字节序列。”

以上是关于如何使用在线工具手动验证 JWT 签名的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中手动验证 JWT 令牌

如何验证或验证 JWT 签名?

如何使用 pyJWT 验证此 JWT 上的签名?

如何验证在 jwt.io 上使用 Keycloak 身份验证提供程序创建的 HS256 签名 JWT 令牌

JWT 令牌签名验证 javascript

如何使用 npm Jose 创建签名的 JWT,然后验证此令牌?