多个 JWT 的匹配散列 JWT
Multiple JWT's match hashed JWT
我正在使用 bcryptjs 对用户的 refresh_token 进行哈希处理,然后再将其存储到我的数据库中。
似乎在将散列字符串与 JWT 进行比较时,以下内容的计算结果总是为真,我在 https://bcrypt-generator.com/
上也得到了相同的行为
例如散列 a$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW
与以下两个 JWT
匹配
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY
您也可以在网站上验证它们是否都会导致 'match'
转到 https://bcrypt-generator.com/ 并打开浏览器控制台。
在控制台中输入这些行:
> var jwt1 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU"
< undefined
> var jwt2 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY"
< undefined
> var h = "a$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW"
< undefined
然后在控制台中输入这些行,观察它们如何 return 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');
}
为什么会这样?字符串明显不同(虽然差别不大)。
散列冲突是因为 bcrypt only hashes the first 72 bytes of input(在大多数实现中)。
这在 bcryptjs
和 bcrypt
npm 包的自述文件中都有记录:
The maximum input length is 72 bytes (note that UTF8 encoded characters use up to 4 bytes) and the length of generated hashes is 60 characters.
Per bcrypt implementation, only the first 72 bytes of a string are used. Any extra bytes are ignored when matching passwords. Note that this is not the first 72 characters. It is possible for a string to contain less than 72 characters, while taking up more than 72 bytes (e.g. a UTF-8 encoded string containing emojis).
(考虑到这是为了用户安全,这是一个客观上糟糕的设计......如果输入超过 72 字节 IMO,bcryptjs 库真的应该总是抛出异常)
我注意到 bcrypt 是为人类提供的(即非随机)密码设计的,而不是作为通用的消息摘要算法。鉴于您不需要向随机生成的密码(例如您的 refresh_token
值)添加盐,您可能应该使用类似 SHA-2 系列算法(例如 SHA-256,但不是 SHA-1)的东西这个。
我正在使用 bcryptjs 对用户的 refresh_token 进行哈希处理,然后再将其存储到我的数据库中。
似乎在将散列字符串与 JWT 进行比较时,以下内容的计算结果总是为真,我在 https://bcrypt-generator.com/
上也得到了相同的行为例如散列 a$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW
与以下两个 JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY
您也可以在网站上验证它们是否都会导致 'match'
转到 https://bcrypt-generator.com/ 并打开浏览器控制台。
在控制台中输入这些行:
> var jwt1 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk1OTg2MDIsImV4cCI6MTY0MjE5MDYwMn0.aJlzFHhBMGO4J7vlOudqOrOFnL1P-yEGrREgdaCXlxU" < undefined > var jwt2 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTZlODdkNi1jMmVkLTRmN2ItOTU2Zi00NDFhMWU1NjA2MmQiLCJpYXQiOjE2Mzk2MDY4ODgsImV4cCI6MTY0MjE5ODg4OH0.vo4HKLXuQbT0Yb0j21M4xl-rakxyE5wINjuGdkPuSJY" < undefined > var h = "a$z4rwnyg.cVtP2SHt3lYj7.aGeAzonmmzbxqCzi2UW3SQj6famGaqW" < undefined
然后在控制台中输入这些行,观察它们如何 return
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');
}
为什么会这样?字符串明显不同(虽然差别不大)。
散列冲突是因为 bcrypt only hashes the first 72 bytes of input(在大多数实现中)。
这在 bcryptjs
和 bcrypt
npm 包的自述文件中都有记录:
The maximum input length is 72 bytes (note that UTF8 encoded characters use up to 4 bytes) and the length of generated hashes is 60 characters.
Per bcrypt implementation, only the first 72 bytes of a string are used. Any extra bytes are ignored when matching passwords. Note that this is not the first 72 characters. It is possible for a string to contain less than 72 characters, while taking up more than 72 bytes (e.g. a UTF-8 encoded string containing emojis).
(考虑到这是为了用户安全,这是一个客观上糟糕的设计......如果输入超过 72 字节 IMO,bcryptjs 库真的应该总是抛出异常)
我注意到 bcrypt 是为人类提供的(即非随机)密码设计的,而不是作为通用的消息摘要算法。鉴于您不需要向随机生成的密码(例如您的 refresh_token
值)添加盐,您可能应该使用类似 SHA-2 系列算法(例如 SHA-256,但不是 SHA-1)的东西这个。