JWT:我應(yīng)該使用哪種簽名算法?
本文轉(zhuǎn)載自微信公眾號(hào)「DotNET技術(shù)圈」,作者Scott Brady。轉(zhuǎn)載本文請(qǐng)聯(lián)系DotNET技術(shù)圈公眾號(hào)。
JWT:我應(yīng)該使用哪種簽名算法?
JSON Web Token (JWT) 可以使用許多不同的算法進(jìn)行簽名:RS256、PS512、ES384、HS1;當(dāng)被問及他們想使用哪一個(gè)時(shí),您就會(huì)明白為什么有些開發(fā)人員會(huì)撓頭。
根據(jù)我的經(jīng)驗(yàn),許多主流身份提供商歷來只提供 RS256 或至少默認(rèn)使用它。然而,由于開放銀行等舉措,這些身份提供商現(xiàn)在正在擴(kuò)大他們的支持以涵蓋更多簽名算法,這意味著您需要開始了解要使用哪些算法。
我不是密碼學(xué)家,但通過與 OpenID Connect 和 FIDO2 的合作,我獲得了從業(yè)者對(duì)各種簽名算法的理解以及密碼學(xué)社區(qū)對(duì)每種算法的總體感受。在本文中,我將為您提供一些知識(shí),以便您了解每個(gè)“alg”值的含義并選擇可用的最佳簽名算法。
TL;DR: EdDSA > ECDSA 或 RSASSA-PSS > RSASSA-PKCS1-v1_5 并且知道會(huì)發(fā)生什么。
算法 (alg) 值
在我們查看每個(gè)簽名算法系列之前,讓我們首先澄清我們所說的“alg”值(例如 RS256)的含義。這些是 JSON Web 算法 (JWA),它們是 JavaScript 對(duì)象簽名和加密 (JOSE) 系列的一部分。您將在 JWT 標(biāo)頭中看到“alg”值,告訴您 JWT 是如何簽名的,在 JSON Web Keys (JWK) 中,告訴您密鑰用于什么算法。
作為一般經(jīng)驗(yàn)法則,“alg”值可以分解為:
RS
256
簽名算法
散列算法
- 簽名算法族:在這種情況下,RS 表示 RSASSA-PKCS1-v1_5。
- 簽名算法使用的散列算法。在這種情況下,256 表示 SHA-256。
大多數(shù)簽名算法都有 SHA-256、SHA-384 和 SHA-512 的變體。在某些情況下,您甚至可以使用“RS1”之類的東西,它使用 SHA-1 ?? 并且是 FIDO2 一致性所必需的。
這些算法通常在RFC 7518 中[1]定義,但您可以在JOSE IANA 注冊(cè)表中[2]找到受支持算法的完整列表。
我應(yīng)該使用哪種散列算法?
SHA-256、SHA-384 和 SHA-512 都是相同散列算法系列的變體:SHA-2。
根據(jù)經(jīng)驗(yàn),算法中的數(shù)字是指它將生成的散列的大小。例如,SHA-256 將產(chǎn)生一個(gè) 256 位的哈希值,而 SHA-512 將產(chǎn)生一個(gè) 512 位的哈希值。
每個(gè)給您的安全級(jí)別是其輸出大小的 50%,因此 SHA-256 將為您提供 128 位的安全性,而 SHA-512 將為您提供 256 位的安全性。這意味著攻擊者在開始尋找沖突之前必須生成 2^128 個(gè)哈希值,這要?dú)w功于生日界限[3]。這就是我們使用最少 128 位安全性的原因。
很快您就不會(huì)需要比 SHA-256 更好的東西了。只是不要使用 SHA-1。
驗(yàn)證:了解您的算法
每個(gè)應(yīng)用程序驗(yàn)證JWT簽名應(yīng)該事先知道算法的期望和準(zhǔn)確使用哪個(gè)鍵。您可以通過將每個(gè)公鑰分配給一個(gè)算法來實(shí)現(xiàn)(例如,此密鑰用于 RS384,此密鑰用于 ES256)。當(dāng)單個(gè)算法有多個(gè)密鑰時(shí),您可以使用kidJWT 標(biāo)頭中的密鑰 ID ( ) 來了解要使用哪個(gè)。
基本上,您要確保JWT 中的kid和alg值符合您的期望。如果他們不匹配,那么有人就不好了。
- {
- "typ": "JWT",
- "kid": "123", // is this key...
- "alg": "RS256" // ...allowed to be used for this algorithm?
- }
OpenID Connect 等協(xié)議使用發(fā)現(xiàn)文檔和受 TLS 保護(hù)的端點(diǎn)上可用的 JSON Web 密鑰集 (JWKS) 來促進(jìn)這一點(diǎn)。
現(xiàn)在,你不應(yīng)該只相信 JWT 標(biāo)頭中的“alg”值,也不應(yīng)該接受帶有“none”算法的 JWT 或在其標(biāo)頭中嵌入公鑰的 JWT。
RSASSA-PKCS1-v1_5 (例如 RS256)
RS256 = RSASSA-PKCS1-v1_5 使用 SHA-256
雖然 RS AES -PKCS1-v1_5 對(duì)于加密不再安全,但 RSA SSA -PKCS1-v1_5 仍然適用于數(shù)字簽名。正如我之前提到的,根據(jù)我的經(jīng)驗(yàn),RS256 歷來是大多數(shù) JWT 實(shí)現(xiàn)的默認(rèn)值,許多 SaaS 身份提供商只提供這種簽名算法。很難找到一個(gè)系統(tǒng)不支持用 RS256 簽名的 JWT。
使用 RSASSA-PKCS1-v1_5 簽名的 JWT 具有確定性簽名,這意味著相同的 JWT 標(biāo)頭和有效負(fù)載將始終生成相同的簽名。
RSASSA-PKCS1-v1_5 已經(jīng)存在很長(zhǎng)時(shí)間了,但是現(xiàn)在,您通常應(yīng)該更喜歡 RSASSA-PSS(具有概率簽名的 RSA)。這并不是說 RSASSA-PKCS1-v1_5 被破壞了,而是說 RSASSA-PSS 只是具有其他人沒有的理想功能。事實(shí)上,RFC 8017 現(xiàn)在在使用 RSA 進(jìn)行簽名時(shí)將 RSASSA-PSS 視為一項(xiàng)要求:
盡管沒有已知針對(duì) RSASSA-PKCS1-v1_5 的攻擊,但為了提高健壯性,新應(yīng)用程序中需要 RSASSA-PSS。
RFC 8017[4]
話雖如此,在討論Bleichenbacher[5]對(duì) RSA PKCS#1 加密和簽名標(biāo)準(zhǔn)的攻擊[6]時(shí),Real-Word Cryptography 中的[7]David Wong分享了一個(gè)有趣的統(tǒng)計(jì)數(shù)據(jù):
與完全破壞加密算法的第一次攻擊不同,第二次攻擊是實(shí)現(xiàn)攻擊[針對(duì)簽名驗(yàn)證]。這意味著如果簽名方案被正確實(shí)現(xiàn)(根據(jù)規(guī)范),攻擊就不會(huì)起作用。
然而,2019 年[8]表明,許多用于簽名的 RSA PKCS#1 v1.5 開源實(shí)現(xiàn)實(shí)際上陷入了該陷阱并錯(cuò)誤地實(shí)施了標(biāo)準(zhǔn),這使得 Bleichenbacher 的偽造攻擊的不同變體得以發(fā)揮作用!
真實(shí)世界密碼學(xué)[9]
由于攻擊是針對(duì)簽名驗(yàn)證的,因此您必須確信所有驗(yàn)證您的 JWT 的收件人都在使用不易受到 Bleichenbacher 攻擊的庫(kù)。如果您要與許多第 3 方打交道,那將很困難。
圍繞開放銀行的工作,例如 OpenID 的金融級(jí) API (FAPI),不允許使用 RSASSA-PKCS1-v1_5。對(duì)于我的普通讀者,這是 IdentityServer 中唯一可用的算法,直到 IdentityServer4 版本 3。
進(jìn)一步閱讀
了解如何使用 OpenSSL 為 JWT 簽名生成 RSA 密鑰[10]
RSASSA-PSS (例如 PS256)
PS256 = RSASSA-PSS 使用 SHA-256 和 MGF1 和 SHA-256
RSASSA-PSS 是 RSA 的概率版本,其中相同的 JWT 標(biāo)頭和有效負(fù)載每次都會(huì)生成不同的簽名。與其他算法不同,這是一種很好的概率方法;雖然在簽名生成期間可以使用隨機(jī)值,但它對(duì)安全性并不重要。一般來說,它的實(shí)現(xiàn)要簡(jiǎn)單得多,因此更難出錯(cuò)。
如果您想使用 RSA 密鑰,那么建議您使用 RSASSA-PSS 而不是 RSASSA-PKCS1-v1_5,但幸運(yùn)的是,RSA 密鑰可用于任一簽名方案。兩者之間的簽名長(zhǎng)度也相同。
英國(guó)的開放銀行最初強(qiáng)制要求使用 PS256,但后來將其開放至 ES256。
進(jìn)一步閱讀
- 了解有關(guān)RSASSA-PSS 以及如何在 .NET Core 中使用它的[11]更多信息
- 了解如何使用 OpenSSL 為 JWT 簽名生成 RSA 密鑰[12]
ECDSA (例如 ES256)
ES256 = ECDSA 使用 P-256 和 SHA-256
在橢圓曲線數(shù)字簽名算法 (ECDSA) 的情況下,ES256 中引用散列算法的數(shù)字也與曲線有關(guān)。ES256 使用 P-256(secp256r1,又名 prime256v1),ES384 使用 P-384(secp384r1),而奇怪的是,ES512 使用 P-521(secp521r1)。是的,521。是的,連微軟[13]都打錯(cuò)了。
橢圓曲線加密 (ECC) 比 RSA 更難破解(或者我們可能真的很擅長(zhǎng)破解 RSA)。因此,ECDSA 可以使用比 RSA 短得多的密鑰和短得多的簽名。大約 256 位的短橢圓曲線 (EC) 密鑰提供與 3072 位 RSA 密鑰相同的安全性。
您經(jīng)常會(huì)看到 ECDSA 被列為比 RSA 中的等效項(xiàng)更快,但這僅適用于簽名生成;使用 RSA 進(jìn)行簽名驗(yàn)證通常仍然更快。使用 JWT,您很可能會(huì)進(jìn)行一次簽名并進(jìn)行多次驗(yàn)證。
使用 ECDSA 簽名的 JWT 具有概率簽名,這意味著相同的 JWT 標(biāo)頭和有效負(fù)載將始終生成不同的簽名。但不幸的是,ECDSA 以一種糟糕的方式是概率性的,其中隨機(jī)生成對(duì)簽名的安全性至關(guān)重要。
ECDSA 使用每個(gè)簽名生成的隨機(jī)數(shù)(不超過一次)。未能只使用一次隨機(jī)數(shù)值會(huì)使私鑰很容易恢復(fù),這在索尼的 Playstation 3 和比特幣中[14]都已經(jīng)出現(xiàn)過。在 Playstation 3 中,私鑰因靜態(tài)隨機(jī)數(shù)而被恢復(fù),而在比特幣中,Android 用戶因 Android 上 Java 的 SecureRandom 類中的錯(cuò)誤而受到影響。如果概率簽名的安全性需要隨機(jī)值,那么您應(yīng)該更喜歡不需要的確定性簽名。
RSASSA-PKCS1-v1_5 在簽名驗(yàn)證方面存在問題,而 ECDSA 在簽名生成方面存在問題,當(dāng)您是令牌發(fā)行者時(shí),這更容易處理。
ECDSA 越來越受歡迎,但由于橢圓曲線加密的實(shí)施方式,密碼學(xué)家似乎普遍反對(duì),并擔(dān)心由于使用隨機(jī)值而導(dǎo)致實(shí)施困難。它比 RSA 享有更好的聲譽(yù),但密碼學(xué)家仍然主張遷移到 EdDSA。
JOSE 最初使用的曲線由 NIST 定義。如果您擔(dān)心使用由 NIST 定義的曲線但想要使用 ECDSA,則一種流行的替代方法是使用 Koblitz 曲線,例如 secp256k1(與 secp256r1 相對(duì))。Kobiltz 曲線稍微弱一些,但如果您擔(dān)心 NIST 曲線中使用的無法解釋的隨機(jī)數(shù)表明另一個(gè) NSA 后門,那么 Kobiltz 曲線提供了一個(gè)越來越受歡迎的替代方案。您可以在比特幣、以太坊和 FIDO2 中找到這些曲線的用法。但是,如果您想使用非 NIST 曲線,您應(yīng)該使用 EdDSA。在 JOSE 中,使用 Kobiltz 的算法以 K 結(jié)尾,例如 ES256K。
進(jìn)一步閱讀
- 了解如何在 .NET Core 中使用 ECDSA[15]以及如何在 IdentityServer4 中使用 ECDSA 簽署令牌[16]
- 了解如何使用 OpenSSL 為 JWT 簽名生成 EC 密鑰[17]
- 在 .NET Core 中使用自定義 JWT 簽名算法,以及使用 Kobiltz 曲線的示例[18]
EdDSA
EdDSA = 使用了 EdDSA 簽名算法
EdDSA 與之前算法的趨勢(shì)相反,使用單一alg值。相反,它依賴于crv預(yù)先商定的密鑰中定義的曲線 ( )。
例如,包含 EdDSA 公鑰的 JWK 將如下所示:
- {
- "kty": "OKP",
- "alg": "EdDSA",
- "crv": "Ed25519",
- "x": "60mR98SQlHUSeLeIu7TeJBTLRG10qlcDLU4AJjQdqMQ"
- }
這迫使現(xiàn)代行為使用分配給密鑰的曲線,而不是 JWT,并消除了各種alg相關(guān)的攻擊。
EdDSA 是一種橢圓曲線密碼學(xué)形式,它利用了扭曲的 Edwards 曲線。它是 Schnorr 簽名系統(tǒng)(而不是 DSA)的變體。 EdDSA 在簽名和驗(yàn)證方面都很快,簽名很短,并且可以避開所有類別的安全漏洞。
RFC 8037 定義了 JOSE 對(duì)以下 EdDSA 變體的支持:
- Ed25519:255 位曲線 Curve25519(32 字節(jié)私鑰,32 字節(jié)公鑰,64 字節(jié)簽名)。簽名使用 SHA-512。提供 128 位安全性
- Ed448:448 位曲線 Curve448-Goldilocks(57 字節(jié)私鑰,57 字節(jié)公鑰,114 字節(jié)簽名)。簽名使用 SHAKE256。提供 224 位安全性
使用 EdDSA 簽名的 JWT 具有確定性簽名,這意味著相同的 JWT 標(biāo)頭和有效負(fù)載將始終生成相同的簽名。這是一種確定性的好方法,解決了依賴隨機(jī) nonce 值來保護(hù)私鑰的問題。EdDSA 僅在私鑰創(chuàng)建期間使用隨機(jī)值。這是JOSE 和 JWT 的批評(píng)者推薦[19]的算法。
JWT 庫(kù)中對(duì) EdDSA 的支持有點(diǎn)參差不齊,但預(yù)計(jì)很快就會(huì)看到更多的 EdDSA。
進(jìn)一步閱讀
- 了解有關(guān)EdDSA 以及如何在 .NET Core 中使用它的[20]更多信息
- 開始使用 EdDSA[21]在 .NET Core 中使用ScottBrady.IdentityModel 進(jìn)行 JWT 簽名[22]
- 在cr.yp.to 上[23]閱讀有關(guān) EdDSA 設(shè)計(jì)優(yōu)勢(shì)的更多信息
HMAC (例如 HS256)
HS256 = HMAC 使用 SHA-256
到目前為止,我們一直在談?wù)摲菍?duì)稱密碼學(xué),其中只有令牌發(fā)行者擁有創(chuàng)建簽名的私鑰,而其他所有人都擁有可用于驗(yàn)證簽名的相應(yīng)公鑰。例如,身份提供者擁有私鑰,依賴方使用公鑰。
在極少數(shù)情況下,您將是唯一發(fā)行和驗(yàn)證令牌的人,那么您可以考慮使用對(duì)稱密碼術(shù)和 HS256 之類的東西。 這使用相同的密鑰來創(chuàng)建和驗(yàn)證簽名。
在我看來,如果你發(fā)現(xiàn)自己處于這個(gè)位置,那么我認(rèn)為 JWT 不是適合你的解決方案。如果同一個(gè)實(shí)體同時(shí)進(jìn)行讀取和寫入,那么在 JWT 中對(duì)結(jié)構(gòu)化、明文數(shù)據(jù)進(jìn)行往返的要求是什么?我建議將數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫(kù)中并傳遞引用或使用諸如 Branca 令牌或 JSON Web 加密 (JWE) 之類的東西來確保只有您可以讀取數(shù)據(jù)。
通常,使用 HMAC 進(jìn)行 JWT 簽名被視為一種反模式。
進(jìn)一步閱讀
- 詳細(xì)了解JWE 以及如何在 .NET Core 中使用它們[24]
- 了解如何將 Branca 令牌[25]與 ScottBrady.IdentityModel 結(jié)合使用
不使用任何加密?!
無 = base64 加密 編碼
對(duì)不起,我忍不住了。請(qǐng)不要使用這個(gè)。
建議
盡可能使用 EdDSA,否則使用 ECDSA。 如果您被迫使用 RSA,則更喜歡 RSASSA-PSS 而不是 RSASSA-PKCS1-v1_5。
我不認(rèn)為說 RSA 正在緩慢退出是一個(gè)有爭(zhēng)議的聲明。目前,提供 ECDSA 是一個(gè)不錯(cuò)的選擇,但理想情況下,您會(huì)希望盡可能使用 EdDSA。
但是,無論您使用哪種算法,請(qǐng)確保提前知道期望使用哪種算法以及使用哪個(gè)密鑰進(jìn)行驗(yàn)證。
References
[1] RFC 7518 中: https://tools.ietf.org/html/rfc7518
[2] JOSE IANA 注冊(cè)表中: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms
[3] 生日界限: https://en.wikipedia.org/wiki/Birthday_attack
[4] RFC 8017: https://tools.ietf.org/html/rfc8017
[5] Bleichenbacher: https://en.wikipedia.org/wiki/Daniel_Bleichenbacher
[6] 的攻擊: https://en.wikipedia.org/wiki/Daniel_Bleichenbacher
[7] Real-Word Cryptography 中的: https://www.manning.com/books/real-world-cryptography
[8] 2019 年: https://www.cs.purdue.edu/homes/schau/files/pkcs1v1_5-ndss19.pdf
[9] 真實(shí)世界密碼學(xué): https://www.manning.com/books/real-world-cryptography
[10] 生成 RSA 密鑰: https://www.scottbrady91.com/OpenSSL/Creating-RSA-Keys-using-OpenSSL
[11] RSASSA-PSS 以及如何在 .NET Core 中使用它的: https://www.scottbrady91.com/C-Sharp/JWT-Signing-using-RSASSA-PSS-in-dotnet-Core
[12] 生成 RSA 密鑰: https://www.scottbrady91.com/OpenSSL/Creating-RSA-Keys-using-OpenSSL
[13] 連微軟: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/master/src/Microsoft.IdentityModel.Tokens/JsonWebKeyECTypes.cs#L40
[14] 索尼的 Playstation 3 和比特幣中: https://medium.com/asecuritysite-when-bob-met-alice/not-playing-randomly-the-sony-ps3-and-bitcoin-crypto-hacks-c1fe92bea9bc
[15] 如何在 .NET Core 中使用 ECDSA: https://www.scottbrady91.com/C-Sharp/JWT-Signing-using-ECDSA-in-dotnet-Core
[16] 在 IdentityServer4 中使用 ECDSA 簽署令牌: https://www.scottbrady91.com/Identity-Server/Using-ECDSA-in-IdentityServer4
[17] 生成 EC 密鑰: https://www.scottbrady91.com/OpenSSL/Creating-Elliptical-Curve-Keys-using-OpenSSL
[18] 以及使用 Kobiltz 曲線的示例: https://www.scottbrady91.com/C-Sharp/Supporting-Custom-JWT-Signing-Algorithms-in-dotnet-Core
[19] JOSE 和 JWT 的批評(píng)者推薦: https://www.scottbrady91.com/JOSE/Alternatives-to-JWTs
[20] EdDSA 以及如何在 .NET Core 中使用它的: https://www.scottbrady91.com/C-Sharp/EdDSA-for-JWT-Signing-in-dotnet-Core
[21] 開始使用 EdDSA: https://github.com/scottbrady91/IdentityModel
[22] ScottBrady.IdentityModel 進(jìn)行 JWT 簽名: https://github.com/scottbrady91/IdentityModel
[23] cr.yp.to 上: https://ed25519.cr.yp.to/
[24] JWE 以及如何在 .NET Core 中使用它們: https://www.scottbrady91.com/C-Sharp/JSON-Web-Encryption-JWE-in-dotnet-Core
[25] 如何將 Branca 令牌: https://www.scottbrady91.com/C-Sharp/Replacing-JWTs-with-Branca-and-PASETO-in-dotnet-Core