個人筆記,不保證正確。
博客已遷移至:https://ryan4yin.space/posts/jwt-algorithm-key-generation/
簽名算法
介紹具體的 JWT 簽名算法前,先解釋一下簽名、摘要/指紋、加密這幾個名詞的含義:
- 數字簽名(Digital Signature):就和我們日常辦理各種手續時需要在文件上簽上自己的名字一樣,數字簽名的主要用途也是防止偽造簽名。
- 數字摘要(digest)/數字指紋(fingerprint): 指的都是數據的 Hash 值。
- 加密算法:這個應該不需要解釋,就是對數據進行加密。。
數字簽名的具體實現,通常是先對數據進行一次 Hash 摘要(SHA1/SHA256/SHA512 等),然后再使用非對稱加密算法(RSA/ECDSA 等)的私鑰對這個摘要進行加密,這樣得到的結果就是原始數據的一個簽名。
用戶在驗證數據時,只需要使用公鑰解密出 Hash 摘要,然后自己再對數據進行一次同樣的摘要,對比兩個摘要是否相同即可。
注意:簽名算法是使用私鑰加密,確保得到的簽名無法被偽造,同時所有人都可以使用公鑰解密來驗證簽名。這和正常的數據加密算法是相反的。
因為數字簽名多了非對稱加密這一步,就能保證只有擁有私鑰的人才能生成出正確的數字簽名,達到了防止偽造簽名的目的。
而數字摘要(Hash)則誰都可以計算出來,通常由可信方公布數據的 Hash 值,用戶下載數據后,可通過 Hash 值對比來判斷數據是否損壞,或者被人調包。
重點在於,Hash 摘要必須由可信方公布出來,否則不能保證安全性。而數字簽名可以隨數據一起提供,不需要擔心被偽造。
JWT 是簽名和數據一起提供的,因此必須使用簽名才能保證安全性。
P.S. 在 Android/IOS 開發中,經常會遇到各類 API 或者 APP 商店要求提供 APP 的簽名,還指明需要的是 MD5/SHA1 值。
這個地方需要填的 MD5/SHA1 值,實際上只是你「簽名證書(=公鑰+證書擁有者信息)」的「數字指紋/摘要」,和 JWT 的簽名不是一回事。
前言
JWT 規范的詳細說明請見「參考」部分的鏈接。這里主要說明一下 JWT 最常見的幾種簽名算法(JWA):HS256(HMAC-SHA256) 、RS256(RSA-SHA256) 還有 ES256(ECDSA-SHA256)。
這三個都是消息簽名算法,得到的都只是一段無法還原的簽名。區別在於消息簽名與簽名驗證需要的 「key」不同。
- HS256 使用同一個「secret_key」進行簽名與驗證(對稱加密)。一旦 secret_key 泄漏,就毫無安全性可言了。
- 因此 HS256 只適合集中式認證,簽名和驗證都必須由可信方進行。
- 傳統的單體應用廣泛使用這種算法,但是請不要在任何分布式的架構中使用它!
- RS256 是使用 RSA 私鑰進行簽名,使用 RSA 公鑰進行驗證。公鑰即使泄漏也毫無影響,只要確保私鑰安全就行。
- RS256 可以將驗證委托給其他應用,只要將公鑰給他們就行。
- ES256 和 RS256 一樣,都使用私鑰簽名,公鑰驗證。算法速度上差距也不大,但是它的簽名長度相對短很多(省流量),並且算法強度和 RS256 差不多。
對於單體應用而言,HS256 和 RS256 的安全性沒有多大差別。
而對於需要進行多方驗證的微服務架構而言,顯然只有 RS256/ES256 才能提供足夠的安全性。
在使用 RS256 時,只有「身份認證的微服務(auth)」需要用 RSA 私鑰生成 JWT,其他微服務使用公開的公鑰即可進行簽名驗證,私鑰得到了更好的保護。
更進一步,「JWT 生成」和「JWT 公鑰分發」都可以直接委托給第三方的通用工具,比如 hydra。
甚至「JWT 驗證」也可以委托給「API 網關」來處理,應用自身可以把認證鑒權完全委托給外部的平台,而應用自身只需要專注於業務。這也是目前的發展趨勢。
RFC 7518 - JSON Web Algorithms (JWA) 中給出的 JWT 算法列表如下:
+--------------+-------------------------------+--------------------+
| "alg" Param | Digital Signature or MAC | Implementation |
| Value | Algorithm | Requirements |
+--------------+-------------------------------+--------------------+
| HS256 | HMAC using SHA-256 | Required |
| HS384 | HMAC using SHA-384 | Optional |
| HS512 | HMAC using SHA-512 | Optional |
| RS256 | RSASSA-PKCS1-v1_5 using | Recommended |
| | SHA-256 | |
| RS384 | RSASSA-PKCS1-v1_5 using | Optional |
| | SHA-384 | |
| RS512 | RSASSA-PKCS1-v1_5 using | Optional |
| | SHA-512 | |
| ES256 | ECDSA using P-256 and SHA-256 | Recommended+ |
| ES384 | ECDSA using P-384 and SHA-384 | Optional |
| ES512 | ECDSA using P-521 and SHA-512 | Optional |
| PS256 | RSASSA-PSS using SHA-256 and | Optional |
| | MGF1 with SHA-256 | |
| PS384 | RSASSA-PSS using SHA-384 and | Optional |
| | MGF1 with SHA-384 | |
| PS512 | RSASSA-PSS using SHA-512 and | Optional |
| | MGF1 with SHA-512 | |
| none | No digital signature or MAC | Optional |
| | performed | |
+--------------+-------------------------------+--------------------+
The use of "+" in the Implementation Requirements column indicates
that the requirement strength is likely to be increased in a future
version of the specification.
可以看到被標記為 Recommended 的只有 RS256 和 ES256(糾正:最后一列只是對 JWA 具體實現的一些要求,和算法推薦沒有任何關系!)。
ES256 使用 ECDSA 進行簽名,它的安全性和運算速度目前和 RS256 差距不大,但是擁有更短的簽名長度。
對於需要頻繁發送的 JWT 而言,更短的長度長期下來可以節約大量流量。
因此更推薦使用 ES256 算法。
使用 OpenSSL 生成 RSA/ECC 公私鑰
RS256 使用 RSA 算法進行簽名,可通過如下命令生成 RSA 密鑰:
# 1. 生成 2048 位(不是 256 位)的 RSA 密鑰
openssl genrsa -out rsa-private-key.pem 2048
# 2. 通過密鑰生成公鑰
openssl rsa -in rsa-private-key.pem -pubout -out rsa-public-key.pem
ES256 使用 ECDSA 算法進行簽名,該算法使用 ECC 密鑰,生成命令如下:
# 1. 生成 ec 算法的私鑰,使用 prime256v1 算法,密鑰長度 256 位。(強度大於 2048 位的 RSA 密鑰)
openssl ecparam -genkey -name prime256v1 -out ecc-private-key.pem
# 2. 通過密鑰生成公鑰
openssl ec -in ecc-private-key.pem -pubout -out ecc-public-key.pem
密鑰的使用應該就不需要介紹了,各類語言都有對應 JWT 庫處理這些,請自行查看文檔。
如果是調試/學習 JWT,需要手動簽名與驗證的話,推薦使用 jwt 工具網站 - jwt.io