JWT 簽名算法 HS256、RS256 及 ES256 及密鑰生成
個人筆記,不保證正確。
獨立博客閱讀: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.
目前應該所有 jwt 相關的庫都支持 HS256/RS256/ES256 這三種算法。
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

浙公網安備 33010602011771號