剛好看到網友發文,要解Yahoo的簽章,但公鑰的格式竟然是給ECC公鑰的x, y兩個點,所以稍微研究了一下。
發生了什麼
網友在IT幫幫忙提了一個問題:yahoo 第三方授權 jwt 驗證簽名這一段怎麼做?? – iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 (ithome.com.tw)
我們知道JSON Web Tokens – jwt.io可以幫忙驗證各種形式的JWT,所以首先先把網友貼出來的JWT先丟上去看看。
eyJhbGciOiJFUzI1NiIsImtpZCI6IjM0NjZkNTFmN2RkMGM3ODA1NjU2ODhjMTgzOTIxODE2YzQ1ODg5YWQifQ.eyJhdF9oYXNoIjoiTlN3cDVTTmZWb2NRVllaNkgyb2NrQSIsInN1YiI6Ik01WlFJS0dONVlSTVBWR1FHRTVFRkVXSFBFIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImJpcnRoZGF0ZSI6IjE5ODEiLCJnZW5kZXIiOiJvdGhlciIsImlzcyI6Imh0dHBzOi8vYXBpLmxvZ2luLnlhaG9vLmNvbSIsInByb2ZpbGVfaW1hZ2VzIjp7ImltYWdlNjQiOiJodHRwczovL3MueWltZy5jb20vYWcvaW1hZ2VzL2RlZmF1bHRfdXNlcl9wcm9maWxlX3BpY182NHNxLmpwZyIsImltYWdlMTkyIjoiaHR0cHM6Ly9zLnlpbWcuY29tL2FnL2ltYWdlcy9kZWZhdWx0X3VzZXJfcHJvZmlsZV9waWNfMTkyc3EuanBnIiwiaW1hZ2UxMjgiOiJodHRwczovL3MueWltZy5jb20vYWcvaW1hZ2VzL2RlZmF1bHRfdXNlcl9wcm9maWxlX3BpY18xMjhzcS5qcGciLCJpbWFnZTMyIjoiaHR0cHM6Ly9zLnlpbWcuY29tL2FnL2ltYWdlcy9kZWZhdWx0X3VzZXJfcHJvZmlsZV9waWNfMzJzcS5qcGcifSwiZ2l2ZW5fbmFtZSI6IuWLneWPsCIsIm1pZGRsZV9uYW1lIjoiIiwibG9jYWxlIjoiemgtSGFudC1UVyIsIm5vbmNlIjoiWWloc0Z3R0tndDNLSlVoNnRQczIiLCJwaWN0dXJlIjoiaHR0cHM6Ly9zLnlpbWcuY29tL2FnL2ltYWdlcy9kZWZhdWx0X3VzZXJfcHJvZmlsZV9waWNfMTkyc3EuanBnIiwic2lkIjoiNFJaYlVrOWZOZlNHIiwiYXVkIjoiZGoweUptazlWMHhOUWtST1ZHTmxlWGsxSm1ROVdWZHJPVlJIWkRKaWVtUlhUa1JKYldOSGJ6bE5RVDA5Sm5NOVkyOXVjM1Z0WlhKelpXTnlaWFFtYzNZOU1DWjRQV1UzIiwiYXV0aF90aW1lIjoxNjQ5OTI2MzA3LCJuaWNrbmFtZSI6IuWLneWPsCIsIm5hbWUiOiLmiLTli53lj7AiLCJzZXNzaW9uX2V4cCI6MTY1MTEzNTkwNywiZXhwIjoxNjUwNDM2Nzg0LCJpYXQiOjE2NTA0MzMxODQsImFwcF9pZCI6ImRqMHlKbWs5VjB4TlFrUk9WR05sZVhrMUptUTlXVmRyT1ZSSFpESmllbVJYVGtSSmJXTkhiemxOUVQwOUpuTTlZMjl1YzNWdFpYSnpaV055WlhRbWMzWTlNQ1o0UFdVMyIsImZhbWlseV9uYW1lIjoi5oi0IiwiZW1haWwiOiJqYnVkdW9vMTIzQHlhaG9vLmNvbSJ9.LhmytVBJ-BWrmeZMbDWJh-sAFoOjdraRDkpxl0yA44eLSGLMbyliFMTlEgcPISWhif-Hg98dkySuyzzikhSaSw
看起來很順利的從裡面拿到了簽章方法
{
"alg": "ES256",
"kid": "3466d51f7dd0c780565688c183921816c45889ad"
}
是ES256呢,根據文件與網友的說明,Yahoo使用一個網頁來發布JWT的公鑰。
{
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "01640f903a24e1c1f097ebda2d09b196c9de3ed9",
"n": "APXkXeLRieppySH9dilwRSJjDW1MYOjSfqX8ifdXClRJiy6jsTlYcd2zDL6FPDeH6s2n_paJpnyEzzUTiSK-paC1yxdSAYPTT42tZqj5K5ZqBZgapYbPmbjEgCN80kPE66rGtRBkTehCAfAZ_CWFWX6yPDjpAMRkRWCDROdMoYm3",
"e": "AQAB"
},
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "6ff94cdad11e7c3ac08dc9ec3c44844b87e364f7",
"n": "AL1LkSgnGk-sKqFDBrojoqvpqOwmN7tgvz0p6J9g8O_nOzXMAwzMUUs4H_FMgeNWcuE6XzJX3spVwAYBp-rBLwyXXCGbO_chhwcpBDNndlZyqS2zOvwmZYdh4MhrUnIOcA8cdDB1hqoDdKOx9M-EjuoafcgqEPA7rWsZTH6TITMP",
"e": "AQAB"
},
{
"kty": "EC",
"alg": "ES256",
"use": "sig",
"crv": "P-256",
"kid": "3466d51f7dd0c780565688c183921816c45889ad",
"x": "cWZxqH95zGdr8P4XvPd_jgoP5XROlipzYxfC_vWC61I",
"y": "rxX9OCD9rIaheKx6LAs4KWR6Rz1-Lj1phRCmdjUDL_I"
}
]
}
觀察可知,有兩種形式的三把公鑰,分別是RSA和ECC的形式。
RSA的公鑰可以由n和e推導出來,ECC的公鑰也可以由x和y推導出來,這是一個二維平面上的座標,可以標誌出特定的橢圓曲線。
但…無論是xy或是ne都應該是整數才對阿,這是什麼編碼呢?
這是網友提供的文件,上面有範例程式碼,這下好解決了,看來該串亂碼應該是URL_Safe的Base64編碼成二進位的樣子,URL_Safe就是將Base64中的+和/分別替換為- _兩個在網址中沒有特殊意義的符號。
解題
那麼該做的是情應該很簡單了,我這邊使用Python呈現。
- 先把兩個Base 64換回二進制編碼(補
=
使長度變成4的倍數)x_bin = base64.urlsafe_b64decode("cWZxqH95zGdr8P4XvPd_jgoP5XROlipzYxfC_vWC61I=")
y_bin = base64.urlsafe_b64decode("rxX9OCD9rIaheKx6LAs4KWR6Rz1-Lj1phRCmdjUDL_I=") - 二進制編碼轉成整數
int.from_bytes(x_bin , "big")
- 用Python匯入
b = ECC.construct(curve="p256", point_x=int.from_bytes(x_bin , "big"), point_y=int.from_bytes(y_bin , "big"))
- 將證書轉成PEM格式輸出
print(b.export_key(format='PEM'))
經過這些步驟,就可以輸入Jwt.io驗證簽章了
完整程式碼
from Crypto.PublicKey import ECC
import base64
b = ECC.construct(curve="p256", point_x=int.from_bytes(base64.urlsafe_b64decode("cWZxqH95zGdr8P4XvPd_jgoP5XROlipzYxfC_vWC61I="), "big"), point_y=int.from_bytes(base64.urlsafe_b64decode("rxX9OCD9rIaheKx6LAs4KWR6Rz1-Lj1phRCmdjUDL_I="), "big"))
print(b.export_key(format='PEM'))
轉出來的公鑰
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcWZxqH95zGdr8P4XvPd/jgoP5XRO
lipzYxfC/vWC61KvFf04IP2shqF4rHosCzgpZHpHPX4uPWmFEKZ2NQMv8g==
-----END PUBLIC KEY-----
一些好玩的網站
Base64 to binary: Encode and decode bytes online — Cryptii
在 Python 中轉換二進位制為 Int | D棧 – Delft Stack
base64 —— Base16、Base32、Base64、Base85 資料編碼 — Python 3.10.4 說明文件