我使用以下方法在 Postman 中创建并签署 JWT (ES512)(前脚本):
var CryptoJS = require("crypto-js");
var navigator = {};
var window = {};
eval(pm.globals.get("jsrsasign-js"));
let issuerId = pm.environment.get("issuerId");
let audienceId = pm.environment.get("audienceId");
let privateKey = pm.environment.get("privateKey");
let body = pm.request.body.raw;
let bodyHash = calculateHash(body);
const pl = payload(issuerId, audienceId, bodyHash)
const jwt = signJwt(pl, privateKey)
pm.collectionVariables.set("bearerToken", jwt);
function calculateHash(message) {
const hash = CryptoJS.SHA256(message??'');
return hash.toString(CryptoJS.enc.Base64);
}
function payload(iss, aud, bodyHash) {
return {
"iss": iss,
"aud": aud,
"sub": "api-request",
"rbh": bodyHash,
"exp": Math.round(Date.now() / 1000) + 59,
};
}
function signJwt(payload, privateKey) {
const alg = 'ES512';
const header = {"alg":alg};
const sHeader = JSON.stringify(header);
const sPayload = JSON.stringify(payload);
return KJUR.jws.JWS.sign('ES512', sHeader, sPayload, privateKey);
}
私钥生成:
openssl ecparam -name secp521r1 -genkey -noout -out private.pem
公钥:
openssl ec -in private.pem -pubin -outform PEM -out public.pem
服务器使用:https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt/9.40来验证JWT:
ECDSAVerifier(parseFromPEMEncodedObjects(publicKey).toECKey())
Jwt验证相当不稳定。有时成功(80%),有时失败(20%)。
原因是有时jws签名长度是130字节(不行),应该是132,有时是132。
好的智威汤逊:
eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhZTA3YmE1My0xOTYwLTRiMDYtYTYyZi01ZjdkYjRkYWM3MGYiLCJhdWQiOiJkMF9ha0IyNWJZaDNtI1ByekVmPHVLMXZRTGp7Q3hweUdIUHRMWkwpIiwic3ViIjoib3BlbmFwaS1yZXF1ZXN0IiwicmJoIjoiY2FyZ3VqY3lINHJiSDl0U3BxVWo4dFpkZmM0eHUyL1dvOGZJbzJYek1Nbz0iLCJleHAiOjE3MjI3NDc4NTV9.AJTc6e_4ymPhtQPSx6XoeYyOScFIYf5axTrSTXz2rtXuH9KZAFZBuoTMD35siR7-MIpRpIk2QslSNvU4A7KQSUVFAeH0Mb7yfwC2bN5ncJhWxg6j5dh6sJfPwLi3buGuw9i_TViF6mnLs8WClzXw8NqFBoNSnHexQPkWba8J-trAxoKo
不好智威汤逊:
eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhZTA3YmE1My0xOTYwLTRiMDYtYTYyZi01ZjdkYjRkYWM3MGYiLCJhdWQiOiJkMF9ha0IyNWJZaDNtI1ByekVmPHVLMXZRTGp7Q3hweUdIUHRMWkwpIiwic3ViIjoib3BlbmFwaS1yZXF1ZXN0IiwicmJoIjoiY2FyZ3VqY3lINHJiSDl0U3BxVWo4dFpkZmM0eHUyL1dvOGZJbzJYek1Nbz0iLCJleHAiOjE3MjI3NDc5MTZ9.gcJl3McLyAML3nfbb-ZXXtvfP-TRm0rjpEYX7iiagiFGSNoMi-8yu7jre_QTB0SW2_POrp4OcGg0QY2EeRCf6JnuApCmFTThF3qiwQ82HLS4OjNcHJkKRjxsDVC3aWhcyd7N9N30pWK5vzPCnaxAy1_3u0mHbByY_HMROuixXF07hw
公钥(PEM格式):
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBm3GiQ0HmPdCyeuy7yWnxjwlQnjn9
dytNIqD/nsuhX1SVuDFH2iD12SM+csYrqLAwZKP4Y3BNIH+5QUFgpbCBwEEBu3w6
FeqM/5rJvxjG2/YJU2p1aUGed8Br/bZVEFVN6ohWgFGrC/hXxYuxVVwIwuzXnd8N
bXCVGZZeVKM5YrbcZQM=
-----END PUBLIC KEY-----
如果我使用java客户端(nimbus-jose-jwt)创建并签署JWT,验证是稳定的。
我是否错误地使用了 jsrsasign 库?
还有另一个 JavaScript 库:https://github.com/panva/jose。
不幸的是,我无法将其导入 Postman。
正如评论中已经怀疑的那样,jsrsasign库在将ECDSA签名转换为P1363格式时似乎有一个错误,至少对于曲线P-521来说是这样。在 P1363 格式中,r 和 s 值表示为无符号、大端和串联。对于 P-521,r 和 s 的大小均为 66 字节。如果值较短,则它会变为 66 字节并带有前导 0x00 值,以便签名具有 132 字节的恒定长度。这是必要的,以便可以清楚地识别 r 和 s 值。
对于发布的 130 字节签名,r 和 s 值都缺少填充,即
r-length/s-length = 65bytes/65bytes
,可以轻松检查签名是否无效(空格仅用于显示目的):
Base64url: -GJ6N27KEqRKHgmdyRIApgN6861cFbAXtksTPv3GVDVV-80Jjm266tiQqh3ORfLt_ucijNoKlvdZ8b90H1nb7SQSj3Mo0JZT8-Ht6fP9DA13LsWdTQuX7TTIUEqPegAI9lCmgZMFpbNeGqVarsOfgSIrpFvp7rFCdo7aFM7OaShGKw
hex: f8627a376eca12a44a1e099dc91200a6037af3ad5c15b017b64b133efdc6543555fbcd098e6dbaead890aa1dce45f2edfee7228cda0a96f759f1bf741f59dbed24 128f7328d09653f3e1ede9f3fd0c0d772ec59d4d0b97ed34c8504a8f7a0008f650a6819305a5b35e1aa55aaec39f81222ba45be9eeb142768eda14cece6928462b
手动修复:
hex: 00f8627a376eca12a44a1e099dc91200a6037af3ad5c15b017b64b133efdc6543555fbcd098e6dbaead890aa1dce45f2edfee7228cda0a96f759f1bf741f59dbed24 00128f7328d09653f3e1ede9f3fd0c0d772ec59d4d0b97ed34c8504a8f7a0008f650a6819305a5b35e1aa55aaec39f81222ba45be9eeb142768eda14cece6928462b
Base64url: APhiejduyhKkSh4JnckSAKYDevOtXBWwF7ZLEz79xlQ1VfvNCY5tuurYkKodzkXy7f7nIozaCpb3WfG_dB9Z2-0kABKPcyjQllPz4e3p8_0MDXcuxZ1NC5ftNMhQSo96AAj2UKaBkwWls14apVquw5-BIiukW-nusUJ2jtoUzs5pKEYr
则固定令牌为:
eyJhbGciOiJFUzUxMiJ9.eyJpc3MiOiJhZTA3YmE1My0xOTYwLTRiMDYtYTYyZi01ZjdkYjRkYWM3MGYiLCJhdWQiOiJkMF9ha0IyNWJZaDNtI1ByekVmPHVLMXZRTGp7Q3hweUdIUHRMWkwpIiwic3ViIjoib3BlbmFwaS1yZXF1ZXN0IiwicmJoIjoiY2FyZ3VqY3lINHJiSDl0U3BxVWo4dFpkZmM0eHUyL1dvOGZJbzJYek1Nbz0iLCJleHAiOjE3MjI4NTAxMzJ9.APhiejduyhKkSh4JnckSAKYDevOtXBWwF7ZLEz79xlQ1VfvNCY5tuurYkKodzkXy7f7nIozaCpb3WfG_dB9Z2-0kABKPcyjQllPz4e3p8_0MDXcuxZ1NC5ftNMhQSo96AAj2UKaBkwWls14apVquw5-BIiukW-nusUJ2jtoUzs5pKEYr
此令牌现在有效,并将由任何 JWS 兼容库验证是否有效,请参阅例如在线访问 jwt.io.
但是,此修复并非 100% 可靠。理论上,不同长度的 r 和 s 值也是可能的(例如
r-length/s-length = 66bytes/64bytes
)。由于缺乏明确性,无法先验地确定哪种情况适用,因此必须进行尝试。然而,r-length/s-length = 65bytes/65bytes
签名的假设是合理的,因为较短签名的概率较小(正如以下估计使得合理的那样:前导 0x00 值的概率:1/256,两个前导 0x00 值的概率:1/256^ 2 ...).这些理论上可能的情况是否真的发生,最终取决于错误,如果不进一步分析代码就无法回答。在我的测试中,我只在 1000 个生成的签名中发现长度为 130 和 132 字节的签名。然而,我在签名时偶尔收到一条错误消息unknown ECDSA sig s length error,这表明该错误更加复杂。
在我看来,最合理的解决方案是切换到另一个库(至少对于P-521)。如果您找不到适合您环境的解决方案,则可以使用上述手动修复方法。