同一枚 JWT 讀得出內容,卻驗不過簽名
一枚 RS256 權杖貼進解碼頁,標頭與載荷照樣解碼,點「驗證簽名」卻回「還沒有輸入金鑰」。把範本整段 script 丟進 node:vm 跑一遍才明白:這一頁把「讀」和「驗」做成兩套判定,驗簽側窄到只有 13 個演算法名進得了門,前六道閘門還不看你填的金鑰。
被測的是 JWT 解析 那一頁,768 行、38463 位元組,帳目在 ALGS(L199–213)、pemToDer(L604)、doVerify(L648)三處;權杖與金鑰全用 demo- 前綴自簽。
13 格裡只寫了三類欄位
Object.keys 數出 13 個鍵,即圖 1 左欄。hashByte 只在 HS 三檔(32/48/64),salt 只在 PS 三檔(32/48/64),curve 只在 ES 三檔(P-256、P-384、P-521),餘下 10 格三類欄位全無,none 只有 family。
最容易上當的是 ES512:鍵裡 512、曲線卻是 P-521,差 9;座標每段 66 位元組,簽名 132 是 2×66 而非 2×64。
圖 1:欄位決定家族,家族決定面板顯隱。
簽名段位元組跟金鑰跟雜湊,不跟演算法名
- HS 三檔 32、48、64 位元組,跟 hash,與那三格 hashByte 一一對上。
- RS256@2048 與 RS384@2048 同為 256,RS512@4096 才 512,PS 三檔同帳。
- ES256 是 64、ES384 是 96、ES512 是 132,跟曲線。
換成 base64url 字元數就是圖 2 末行十二檔,與 ceil(bytes/3)*4 減補位在 12 行逐行相等。
圖 2:兩根同高柱子來自 2048 位的 RS256 與 RS384。
12 演算法 × 4 入口,只有兩種結果
每行權杖的 kid 寫成 demo-key-1、載荷 lvl 寫成 3,HS 金鑰 demo-shared-key-2026-b22,非對稱現生成 RSA-2048/4096 與三條 P 曲線的 PEM。四個入口只有兩種結果:正確金鑰與匯出成 JWK 各 12/12 通過;lvl 從 3 改成 4 而簽名段原樣、或換一把同參數錯鑰,都是 12/12 不匹配。
金鑰進門先看首字元
importKey 先 trim,再看首字元是不是 {:能解析走 jwk,否則 hmac 落 raw、其餘落 spki。同一段金鑰寫成 raw 與寫成 JWK,重算簽名逐位元組一致,32 位元組、64 字元 hex。坑是 {"a":1}:解析得動、進 jwk 支拋 DataError,message 無中文,回落成「貼上的金鑰內容不完整,或與標頭聲明的演算法不匹配」。
PEM 的三條拒絕加一次救回
BEGIN/END 不匹配報「沒有識別到 PEM 的 BEGIN/END 標記」,裸 base64 也落這條;label 不是 PUBLIC KEY 就回顯識別到的名字(CERTIFICATE、RSA PUBLIC KEY 各一條);BEGIN 與 END 不對稱時反向引用失配,仍落第一條。body 先去空白、再把 - 換 +、_ 換 / 才進 atob,失敗報「Base64 無法還原(長度 N)」,N 是去空白後的字元數。一次救回:RSA-2048 demo 公鑰的 SPKI DER 294 位元組編成 392 字元,19 枚 + 與 / 全換成 base64url 寫法後兩次輸出逐位元組一致;另一把 15 枚。字元表差別見 Base64。
七道閘門,六道走在讀金鑰之前
| 閘門 | 觸發 | 回顯 |
|---|---|---|
| L650 | 沒有權杖 | 只 toast「先貼上一個權杖」 |
| L651 | 5 段 JWE | 「無法驗簽」 |
| L654 | alg 為 none | 「沒有簽名可驗」 |
| L658 | 家族不在四類 | 「不在本機可驗證的範圍內」 |
| L663 | 沒有 WebCrypto | 瀏覽器口徑,未實測 |
| L667 | 缺第 3 段 | 「缺少簽名段」 |
| L675 | 金鑰框空白 | 「還沒有輸入金鑰」 |
七道都在金鑰框留空下跑過,除最後一道外無一回顯金鑰文案,前六道在 L672 讀 keyText 前返回。第四道餵了 6 個輸入:HS224、ES256K、EdDSA、hs256、A128GCM 與標頭不帶 alg,全落這條,查表大小寫敏感。
兩條分支失敗時回吐的東西不一樣
HMAC 是自己重算再比:sign 得到 expect 交給 equalBytes(先比 length,再定長 XOR-OR 摺疊),失敗時兩段各印前 8 位元組,恆為 16 個十六進位字元加一枚省略號。RS、PS、EC 走 verify,只回布林值,「簽名 N 位元組」只在成功文案裡;255 位元組的 RS256 與 63 位元組的 ES256 截短簽名在 Node 側返回 false,進「簽名不匹配」而不報錯。
salt 也不反映在長度上:PS256 的 saltLength 被寫死成 32,同一把 RSA-PSS-2048 下 salt=20 簽發的簽名仍是 256 位元組 342 字元,卻驗不過,面板也不提 salt。
口徑補一句:除表中標註外全部為純 Node 實測;DOM 像素、渲染計時器觸發次數、非對稱庫對截短簽名是否拋錯,本批未開瀏覽器,記未實測。
三個面板開關各自聽 family 的
jwt-hs-box 只在 hmac 開,jwt-pk-box 只在 rsa/pss/ec 開,按鈕取兩者的或。八種輸入:沒有 alg、未知串 XYZALG、none 三種三元素全隱藏;HS256 只開 HMAC 框;RS256、PS384、ES512 只開公鑰框;塞一段長度除以 4 餘 1 的壞簽名,開關不動,只有那一行中段從「N 位元組」換成「無法解碼」。
常見問題
Q:同一個 JWT 貼進去,為什麼這一頁讀得出內容卻驗不過簽名?
兩套判定:解碼只要求段能切開、位元組能還原,驗簽要先過閘門——alg 得在 13 格裡、家族在四類內、真有第 3 段。三段結構與 exp 的讀法見站內那篇 舊文。
Q:簽名段是 256 位元組,就一定是 RS256 嗎?
不一定,RS 與 PS 在 2048 位下都是 256 位元組 342 字元;跟雜湊的只有 HS 三檔,跟曲線的只有 ES 三檔。
Q:金鑰填了,為什麼還說沒有輸入金鑰?
要麼填錯了框——顯隱由 alg 決定,落在四類外或沒有 alg 時連按鈕一起隱藏;要麼那一格只有一串空格。