多个 JWT 的匹配哈希 JWT

Posted

技术标签:

【中文标题】多个 JWT 的匹配哈希 JWT【英文标题】:Multiple JWT's match hashed JWT 【发布时间】:2022-01-19 01:52:30 【问题描述】:

我正在使用 bcryptjs 对用户的 refresh_token 进行哈希处理,然后再将其存储到我的数据库中。

在将散列字符串与 JWT 进行比较时,以下内容似乎总是为真,我在 https://bcrypt-generator.com/ 上也得到了相同的行为

例如,哈希 $2a$10$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW 与以下两个 JWT 匹配

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY

您也可以在网站上验证它们是否都导致“匹配”

    转到https://bcrypt-generator.com/ 并打开您的浏览器控制台。

    在控制台中输入这些行:

    > var jwt1 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU"
    < undefined
    
    > var jwt2 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY"
    < undefined
    
    > var h = "$2a$10$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW"
    < undefined
    

    然后将这些行输入到控制台,观察它们如何返回true

    > bcrypt.compareSync(jwt1, h)
    < true
    > bcrypt.compareSync(jwt2, h)
    < true
    

这是我自己的 JS 代码,它也重现了哈希匹配:

// Login Logic

const refresh_token: string = jwt.sign( userId , authSecrets.refresh_secret,  expiresIn: '30d' );

const hash_refresh = bcrypt.hashSync(refresh_token);

await UserModel.update(
    id: user.id,
    refresh_token: hash_refresh,
);


// Refresh logic
// 'value' is the payload after using joi to validate it 

const claims: any = jwt.verify(value.refresh_token, authSecrets.refresh_secret);

user = await UserModel.get(claims.userId);

if (!bcrypt.compareSync(value.refresh_token, user.refresh_token)) 
    // This never happens with any JWT!
    return response(401, 'Refresh Token is incorrect');


为什么会这样?字符串明显不同(尽管差别不大)。

【问题讨论】:

“以下总是计算为真”-但您实际上并没有发布任何“计算为真”的 JS 表达式-相反,您发布了不 evaluate 任何东西,因为语句不是表达式。我们需要userId 和其他参数的示例值。 bcrypt.compareSync(value.refresh_token, user.refresh_token) 这是计算结果为 true 的表达式,您可以在 bcrypt-generator.com 的“if”语句中找到它,您可以使用我在上面发布的哈希值并使用上面的 JWT 字符串,它们都将评估为 true。 userId 是一个 UUID,不应该影响这里的问题 bcrypt-generator.com 上有数百条 if 语句来自加载到它的所有脚本。请更具体。只需给我一些 JS,我就可以将其复制并粘贴到我的浏览器控制台中。 好的,在 bcrypt-generator.com 上摆弄之后,我现在能够重现您所描述的问题。我同意,这很奇怪。 顺便说一句,the bcryptjs library 已经有 5 年没有更新了,你考虑过the bcrypt package 吗?这仍在积极开发中。 【参考方案1】:

哈希冲突是因为 bcrypt only hashes the first 72 bytes of input(在大多数实现中)。

这在 bcryptjsbcrypt npm 包的 README 中有记录:

bcryptjs:

最大输入长度为 72 个字节(注意 UTF8 编码字符最多使用 4 个字节),生成的哈希长度为 60 个字符。

bcrypt:

每个 bcrypt 实现,仅使用字符串的前 72 个字节。匹配密码时忽略任何额外的字节。请注意,这不是前 72 个字符。字符串可能包含少于 72 个字符,但占用超过 72 个字节(例如,包含表情符号的 UTF-8 编码字符串)。

(考虑到这是为了用户安全,这是一个客观上糟糕的设计......如果输入超过 72 字节 IMO,bcryptjs 库确实应该总是抛出异常)

我注意到 bcrypt 是为人工提供(即非随机)密码而设计的,而不是作为通用消息摘要算法。鉴于您不需要为随机生成的密码添加盐(例如您的 refresh_token 值),您可能应该为此使用 SHA-2 系列算法(例如 SHA-256,但不是 SHA-1)。

【讨论】:

我不敢相信我错过了这个,非常感谢!

以上是关于多个 JWT 的匹配哈希 JWT的主要内容,如果未能解决你的问题,请参考以下文章

JWT 编程语言之间的差异

在 ExpressJs 中使用时 JWT 中的多个问题

如何在 Nest.JS 中使用多个 Secret 实现多个 JWT 策略

为啥 AWS Cognito 对 JWT 使用多个公有密钥?

Quarkus 验证来自多个来源的 JWT

通过多个微服务进行 JWT 授权