我正在尝试编写一个“纯”NodeJS 函数来验证 JSON Web Token 上的签名,因为我要部署到的环境没有可用的这个包。
该代码适用于非椭圆曲线密钥,但如果我使用 P-256 和 ES256 则无效 - 我只是从 NodeJS 加密 verify.verfify 函数中得到令人讨厌的模糊“false”。
这是函数体:
function verifyToken(token, publicKey) {
const [headerEncoded, payloadEncoded, signatureEncoded] = token.split('.')
const payload = JSON.parse(Buffer.from(payloadEncoded, 'base64').toString())
const verify = crypto.createVerify('sha256')
verify.update(headerEncoded + '.' + payloadEncoded)
const isValidSignature = verify.verify(publicKey, signatureEncoded, 'base64')
if (!isValidSignature) {
throw new Error('Invalid signature')
}
return payload
}
我正在这个有用的站点生成 JWK 格式的私钥和公钥:https://mkjwk.org/
我使用 jsonwebtoken 创建 JWT,然后将我的验证函数与 jsonwebtoken 版本进行比较。我知道我在每种情况下都使用私钥 - 但这只是为了缩短示例。
const jwt = require('jsonwebtoken')
const crypto = require('crypto')
function verifyToken(token, publicKey) {
const [headerEncoded, payloadEncoded, signatureEncoded] = token.split('.')
const payload = JSON.parse(Buffer.from(payloadEncoded, 'base64').toString())
const verify = crypto.createVerify('sha256')
verify.update(headerEncoded + '.' + payloadEncoded)
const isValidSignature = verify.verify(publicKey, signatureEncoded, 'base64')
if (!isValidSignature) {
throw new Error('Invalid signature')
}
return payload
}
// Example RSA style key
const privateKey1 = {
"key": {
"p": "<redacted>",
"kty": "RSA",
"q": "<redacted>",
"d": "<redacted>",
"e": "AQAB",
"use": "sig",
"kid": "<redacted>",
"qi": "<redacted>",
"dp": "<redacted>",
"alg": "RS256",
"dq": "<redacted>",
"n": "<redacted>"
},
"format": "jwk"
}
// Example ES256 - generated from https://mkjwk.org/
const privateKey2 = {
"key": {
"kty": "EC",
"d": "<redacted>",
"use": "sig",
"crv": "P-256",
"kid": "<redacted>",
"x": "<redacted>",
"y": "<redacted>",
"alg": "ES256"
},
"format": "jwk"
}
var token1 = jwt.sign({ 'hello': 'world' }, privateKey1,
{ 'algorithm' : privateKey1.key.alg, expiresIn: 60 * 60, 'header': { 'typ': 'JWT', 'alg' : privateKey1.key.alg } })
console.log('json web token 1 = ', jwt.verify(token1, privateKey1)) // This works
console.log('pure node token 1 = ', verifyToken(token1, privateKey1)) // This works
var token2 = jwt.sign({ 'hello': 'world' }, privateKey2,
{ 'algorithm' : privateKey2.key.alg, expiresIn: 60 * 60, 'header': { 'typ': 'JWT', 'alg' : privateKey2.key.alg } })
console.log('json web token 2 = ', jwt.verify(token2, privateKey2)) // This works
console.log('pure node token 2 = ', verifyToken(token2, privateKey2)) // This fails
我需要提供其他选项来验证.verify吗?
我已经在 https://jwt.io 检查了令牌,那里的签名很好。
问题是由不兼容的 ECDSA 签名格式引起的
ECDSA签名主要指定两种格式,IEEE P1363和ASN.1/DER,见这里:
在 JWT 的上下文中,根据定义使用 P1363,请参见here(步骤 1 到 4 描述了 P1363),即生成的令牌中的(Base64url 编码)签名具有 P1363 格式。
另一方面,NodeJS 的加密模块默认使用 ASN.1/DER,参见here 和here,但可以通过属性
dsaEncoding
切换到 P1363。为此,必须按如下方式扩展密钥:
const privateKey2 = {
"key": {
"kty": "EC",
"d": "v5PriRLFXqqILYFZkX2LWEbUQ_y_NCZXD6il5S6KPus",
"use": "sig",
"crv": "P-256",
"kid": "f8124e12-98c4-4299-b93b-9d9a65f477db",
"x":"r9FyRzL88VKR0wIhiDr8JFOTd5K_hVDb7uw0VDxOM7U",
"y":"aqK5Wmb-UgjDPQ5cB1QGccATrtmgPDRnPzR5JCMPs_M",
"alg": "ES256"
},
"format": "jwk",
"dsaEncoding": "ieee-p1363" // switch to P1363
}
通过此更改,ECDSA 签名也已成功验证。