JWT:我应该使用哪种签名算法?
JSON Web Token (JWT) 可以使用许多不同的算法进行签名:RS256、PS512、ES384、HS1;当被问及他们想使用哪一个时,您就会明白为什么有些开发人员会挠头。
根据我的经验,许多主流身份提供商历来只提供 RS256 或至少默认使用它。然而,由于开放银行等举措,这些身份提供商现在正在扩大他们的支持以涵盖更多签名算法,这意味着您需要开始了解要使用哪些算法。
我不是密码学家,但通过与 OpenID Connect 和 FIDO2 的合作,我获得了从业者对各种签名算法的理解以及密码学社区对每种算法的总体感受。在本文中,我将为您提供一些知识,以便您了解每个“alg”值的含义并选择可用的最佳签名算法。
TL;DR: EdDSA > ECDSA 或 RSASSA-PSS > RSASSA-PKCS1-v1_5 并且知道会发生什么。
算法 (alg) 值
在我们查看每个签名算法系列之前,让我们首先澄清我们所说的“alg”值(例如 RS256)的含义。这些是 JSON Web 算法 (JWA),它们是 JavaScript 对象签名和加密 (JOSE) 系列的一部分。您将在 JWT 标头中看到“alg”值,告诉您 JWT 是如何签名的,在 JSON Web Keys (JWK) 中,告诉您密钥用于什么算法。
作为一般经验法则,“alg”值可以分解为:
RS
256
签名算法
散列算法
•签名算法族:在这种情况下,RS 表示 RSASSA-PKCS1-v1_5。•签名算法使用的散列算法。在这种情况下,256 表示 SHA-256。
大多数签名算法都有 SHA-256、SHA-384 和 SHA-512 的变体。在某些情况下,您甚至可以使用“RS1”之类的东西,它使用 SHA-1 ???? 并且是 FIDO2 一致性所必需的。
这些算法通常在RFC 7518 中[1]定义,但您可以在JOSE IANA 注册表中[2]找到受支持算法的完整列表。
我应该使用哪种散列算法?
SHA-256、SHA-384 和 SHA-512 都是相同散列算法系列的变体:SHA-2。
根据经验,算法中的数字是指它将生成的散列的大小。例如,SHA-256 将产生一个 256 位的哈希值,而 SHA-512 将产生一个 512 位的哈希值。
每个给您的安全级别是其输出大小的 50%,因此 SHA-256 将为您提供 128 位的安全性,而 SHA-512 将为您提供 256 位的安全性。这意味着攻击者在开始寻找冲突之前必须生成 2^128 个哈希值,这要归功于生日界限[3]。这就是我们使用最少 128 位安全性的原因。
很快您就不会需要比 SHA-256 更好的东西了。只是不要使用 SHA-1。
验证:了解您的算法
每个应用程序验证JWT签名应该事先知道算法的期望和准确使用哪个键。您可以通过将每个公钥分配给一个算法来实现(例如,此密钥用于 RS384,此密钥用于 ES256)。当单个算法有多个密钥时,您可以使用kid
JWT 标头中的密钥 ID ( ) 来了解要使用哪个。
基本上,您要确保JWT 中的kid
和alg
值符合您的期望。如果他们不匹配,那么有人就不好了。
{
"typ": "JWT",
"kid": "123", // is this key...
"alg": "RS256" // ...allowed to be used for this algorithm?
}
OpenID Connect 等协议使用发现文档和受 TLS 保护的端点上可用的 JSON Web 密钥集 (JWKS) 来促进这一点。
现在,你不应该只相信 JWT 标头中的“alg”值,也不应该接受带有“none”算法的 JWT 或在其标头中嵌入公钥的 JWT。
RSASSA-PKCS1-v1_5 (例如 RS256)
RS256 = RSASSA-PKCS1-v1_5 使用 SHA-256
虽然 RS AES -PKCS1-v1_5 对于加密不再安全,但 RSA SSA -PKCS1-v1_5 仍然适用于数字签名。正如我之前提到的,根据我的经验,RS256 历来是大多数 JWT 实现的默认值,许多 SaaS 身份提供商只提供这种签名算法。很难找到一个系统不支持用 RS256 签名的 JWT。
使用 RSASSA-PKCS1-v1_5 签名的 JWT 具有确定性签名,这意味着相同的 JWT 标头和有效负载将始终生成相同的签名。
RSASSA-PKCS1-v1_5 已经存在很长时间了,但是现在,您通常应该更喜欢 RSASSA-PSS(具有概率签名的 RSA)。这并不是说 RSASSA-PKCS1-v1_5 被破坏了,而是说 RSASSA-PSS 只是具有其他人没有的理想功能。事实上,RFC 8017 现在在使用 RSA 进行签名时将 RSASSA-PSS 视为一项要求:
尽管没有已知针对 RSASSA-PKCS1-v1_5 的攻击,但为了提高健壮性,新应用程序中需要 RSASSA-PSS。
RFC 8017[4]
话虽如此,在讨论Bleichenbacher[5]对 RSA PKCS#1 加密和签名标准的攻击[6]时,Real-Word Cryptography 中的[7]David Wong分享了一个有趣的统计数据:
与完全破坏加密算法的第一次攻击不同,第二次攻击是实现攻击[针对签名验证]。这意味着如果签名方案被正确实现(根据规范),攻击就不会起作用。
然而,2019 年[8]表明,许多用于签名的 RSA PKCS#1 v1.5 开源实现实际上陷入了该陷阱并错误地实施了标准,这使得 Bleichenbacher 的伪造攻击的不同变体得以发挥作用!
真实世界密码学[9]
由于攻击是针对签名验证的,因此您必须确信所有验证您的 JWT 的收件人都在使用不易受到 Bleichenbacher 攻击的库。如果您要与许多第 3 方打交道,那将很困难。
围绕开放银行的工作,例如 OpenID 的金融级 API (FAPI),不允许使用 RSASSA-PKCS1-v1_5。对于我的普通读者,这是 IdentityServer 中唯一可用的算法,直到 IdentityServer4 版本 3。
进一步阅读
•了解如何使用 OpenSSL 为 JWT 签名生成 RSA 密钥[10]
RSASSA-PSS (例如 PS256)
PS256 = RSASSA-PSS 使用 SHA-256 和 MGF1 和 SHA-256
RSASSA-PSS 是 RSA 的概率版本,其中相同的 JWT 标头和有效负载每次都会生成不同的签名。与其他算法不同,这是一种很好的概率方法;虽然在签名生成期间可以使用随机值,但它对安全性并不重要。一般来说,它的实现要简单得多,因此更难出错。
如果您想使用 RSA 密钥,那么建议您使用 RSASSA-PSS 而不是 RSASSA-PKCS1-v1_5,但幸运的是,RSA 密钥可用于任一签名方案。两者之间的签名长度也相同。
英国的开放银行最初强制要求使用 PS256,但后来将其开放至 ES256。
进一步阅读
•了解有关RSASSA-PSS 以及如何在 .NET Core 中使用它的[11]更多信息•了解如何使用 OpenSSL 为 JWT 签名生成 RSA 密钥[12]
ECDSA (例如 ES256)
ES256 = ECDSA 使用 P-256 和 SHA-256
在椭圆曲线数字签名算法 (ECDSA) 的情况下,ES256 中引用散列算法的数字也与曲线有关。ES256 使用 P-256(secp256r1,又名 prime256v1),ES384 使用 P-384(secp384r1),而奇怪的是,ES512 使用 P-521(secp521r1)。是的,521。是的,连微软[13]都打错了。
椭圆曲线加密 (ECC) 比 RSA 更难破解(或者我们可能真的很擅长破解 RSA)。因此,ECDSA 可以使用比 RSA 短得多的密钥和短得多的签名。大约 256 位的短椭圆曲线 (EC) 密钥提供与 3072 位 RSA 密钥相同的安全性。
您经常会看到 ECDSA 被列为比 RSA 中的等效项更快,但这仅适用于签名生成;使用 RSA 进行签名验证通常仍然更快。使用 JWT,您很可能会进行一次签名并进行多次验证。
使用 ECDSA 签名的 JWT 具有概率签名,这意味着相同的 JWT 标头和有效负载将始终生成不同的签名。但不幸的是,ECDSA 以一种糟糕的方式是概率性的,其中随机生成对签名的安全性至关重要。
ECDSA 使用每个签名生成的随机数(不超过一次)。未能只使用一次随机数值会使私钥很容易恢复,这在索尼的 Playstation 3 和比特币中[14]都已经出现过。在 Playstation 3 中,私钥因静态随机数而被恢复,而在比特币中,Android 用户因 Android 上 Java 的 SecureRandom 类中的错误而受到影响。如果概率签名的安全性需要随机值,那么您应该更喜欢不需要的确定性签名。
RSASSA-PKCS1-v1_5 在签名验证方面存在问题,而 ECDSA 在签名生成方面存在问题,当您是令牌发行者时,这更容易处理。
ECDSA 越来越受欢迎,但由于椭圆曲线加密的实施方式,密码学家似乎普遍反对,并担心由于使用随机值而导致实施困难。它比 RSA 享有更好的声誉,但密码学家仍然主张迁移到 EdDSA。
JOSE 最初使用的曲线由 NIST 定义。如果您担心使用由 NIST 定义的曲线但想要使用 ECDSA,则一种流行的替代方法是使用 Koblitz 曲线,例如 secp256k1(与 secp256r1 相对)。Kobiltz 曲线稍微弱一些,但如果您担心 NIST 曲线中使用的无法解释的随机数表明另一个 NSA 后门,那么 Kobiltz 曲线提供了一个越来越受欢迎的替代方案。您可以在比特币、以太坊和 FIDO2 中找到这些曲线的用法。但是,如果您想使用非 NIST 曲线,您应该使用 EdDSA。在 JOSE 中,使用 Kobiltz 的算法以 K 结尾,例如 ES256K。
进一步阅读
•了解如何在 .NET Core 中使用 ECDSA[15]以及如何在 IdentityServer4 中使用 ECDSA 签署令牌[16]•了解如何使用 OpenSSL 为 JWT 签名生成 EC 密钥[17]•在 .NET Core 中使用自定义 JWT 签名算法,以及使用 Kobiltz 曲线的示例[18]
EdDSA
EdDSA = 使用了 EdDSA 签名算法????♂️
EdDSA 与之前算法的趋势相反,使用单一alg
值。相反,它依赖于crv
预先商定的密钥中定义的曲线 ( )。
例如,包含 EdDSA 公钥的 JWK 将如下所示:
{
"kty": "OKP",
"alg": "EdDSA",
"crv": "Ed25519",
"x": "60mR98SQlHUSeLeIu7TeJBTLRG10qlcDLU4AJjQdqMQ"
}
这迫使现代行为使用分配给密钥的曲线,而不是 JWT,并消除了各种alg
相关的攻击。
EdDSA 是一种椭圆曲线密码学形式,它利用了扭曲的 Edwards 曲线。它是 Schnorr 签名系统(而不是 DSA)的变体。 EdDSA 在签名和验证方面都很快,签名很短,并且可以避开所有类别的安全漏洞。
RFC 8037 定义了 JOSE 对以下 EdDSA 变体的支持:
•Ed25519:255 位曲线 Curve25519(32 字节私钥,32 字节公钥,64 字节签名)。签名使用 SHA-512。提供 128 位安全性•Ed448:448 位曲线 Curve448-Goldilocks(57 字节私钥,57 字节公钥,114 字节签名)。签名使用 SHAKE256。提供 224 位安全性
使用 EdDSA 签名的 JWT 具有确定性签名,这意味着相同的 JWT 标头和有效负载将始终生成相同的签名。这是一种确定性的好方法,解决了依赖随机 nonce 值来保护私钥的问题。EdDSA 仅在私钥创建期间使用随机值。这是JOSE 和 JWT 的批评者推荐[19]的算法。
JWT 库中对 EdDSA 的支持有点参差不齐,但预计很快就会看到更多的 EdDSA。
进一步阅读
•了解有关EdDSA 以及如何在 .NET Core 中使用它的[20]更多信息•开始使用 EdDSA[21]在 .NET Core 中使用ScottBrady.IdentityModel 进行 JWT 签名[22]•在cr.yp.to 上[23]阅读有关 EdDSA 设计优势的更多信息
HMAC (例如 HS256)
HS256 = HMAC 使用 SHA-256
到目前为止,我们一直在谈论非对称密码学,其中只有令牌发行者拥有创建签名的私钥,而其他所有人都拥有可用于验证签名的相应公钥。例如,身份提供者拥有私钥,依赖方使用公钥。
在极少数情况下,您将是唯一发行和验证令牌的人,那么您可以考虑使用对称密码术和 HS256 之类的东西。 这使用相同的密钥来创建和验证签名。
在我看来,如果你发现自己处于这个位置,那么我认为 JWT 不是适合你的解决方案。如果同一个实体同时进行读取和写入,那么在 JWT 中对结构化、明文数据进行往返的要求是什么?我建议将数据存储在数据库中并传递引用或使用诸如 Branca 令牌或 JSON Web 加密 (JWE) 之类的东西来确保只有您可以读取数据。
通常,使用 HMAC 进行 JWT 签名被视为一种反模式。
进一步阅读
•详细了解JWE 以及如何在 .NET Core 中使用它们[24]•了解如何将 Branca 令牌[25]与 ScottBrady.IdentityModel 结合使用
不使用任何加密?!
无 = base64 加密 编码
对不起,我忍不住了。请不要使用这个。
建议
尽可能使用 EdDSA,否则使用 ECDSA。 如果您被迫使用 RSA,则更喜欢 RSASSA-PSS 而不是 RSASSA-PKCS1-v1_5。
我不认为说 RSA 正在缓慢退出是一个有争议的声明。目前,提供 ECDSA 是一个不错的选择,但理想情况下,您会希望尽可能使用 EdDSA。
但是,无论您使用哪种算法,请确保提前知道期望使用哪种算法以及使用哪个密钥进行验证。
References
[1]
RFC 7518 中: https://tools.ietf.org/html/rfc7518[2]
JOSE IANA 注册表中: 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]
真实世界密码学: 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 的批评者推荐: 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 进行 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