Logo Logo
GitHub Designed by Logto

什么是签名密钥 (Signing key)?

OpenID Connect (OIDC) 的上下文中,一个 签名密钥,通常是一个非对称密钥对,用于签署和验证 JSON Web Tokens (JWTs) 。OpenID 提供者使用签名密钥来签署诸如 ID 令牌 访问令牌 (access tokens) 之类的令牌,以确保它们的完整性和真实性。

虽然签名密钥的概念可以更广泛,但我们将专注于它们在 OIDC 中用来保护令牌的方式。其他用例包括签署电子邮件、文档和软件包,可以从相同的原理中衍生出来。

示例:ID 令牌签名

当用户向 OpenID 提供者进行认证 (authentication) 时,提供者会发布一个包含用户信息( 声明 (claims) )并由提供者的签名密钥签署的 ID 令牌。由于 ID 令牌是一个 JWT,它由三个部分组成:头部、负载和签名。

1. 头部

假设头部是:

{
  "alg": "ES384",
  "typ": "JWT"
}

JSON 表明 ID 令牌使用 ECDSA 算法和 P-384 曲线进行签名。typ 字段指定令牌类型为 JWT。

2. 负载

负载包含基本的用户信息:

{
  "sub": "1234567890",
  "name": "Alice"
}

sub 声明是用户的唯一标识符,name 是他们的显示名称。

3. 签署令牌

根据 JWT 格式,头部和负载应进行 Base64URL 编码,并用 . 连接以进行签署:

{{header}}.{{payload}}

在这种情况下,值将为:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0

假设 OpenID 提供者使用以下私钥来签署令牌:

-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBW9PDXInlNT2hjOtQr
g4pkVkyJsKia33dHrsbOG4Z77pfYN7SYZCHh9YdLXTTKinehZANiAAQX/FB1s6Gj
YnDSGCY08PRUAQ8CCRCt8Ph/VDHfLj1xSbrjp8wFf0NjH7jcfNebpV1fvu4XKbP3
Ro7h0G6elN1TMsVECJPv4ieDNkYOsgT4UboJypC5E/rmvrlJTMM6Y/k=
-----END PRIVATE KEY-----

要签署令牌,OpenID 提供者需要使用私钥生成签名:

signature = sign(header + '.' + payload, private_key)

然后 Base64URL 编码的签名为:

Cjy6A_FHnwQBP0hRawoGTkRy8m8o0Ncc1q4BeyxYr0fxhKYmJJinIWZPXJdaAXRO9wOFuH2-UML2yWHjot_LnCPO6362asMvgNkEJMZ6UtqyOPlsCOJ7voTPOCT6sYu2

4. 组装 JWT

最后,OpenID 提供者通过用 . 连接头部、负载和签名来组装 JWT:

{{header}}.{{payload}}.{{signature}}

在这种情况下,ID 令牌将为:

eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0.Cjy6A_FHnwQBP0hRawoGTkRy8m8o0Ncc1q4BeyxYr0fxhKYmJJinIWZPXJdaAXRO9wOFuH2-UML2yWHjot_LnCPO6362asMvgNkEJMZ6UtqyOPlsCOJ7voTPOCT6sYu2

现在 ID 令牌已准备好发送给 客户端 (Client) 进行进一步处理。

5. 验证令牌

当客户端收到 ID 令牌时,可以使用 OpenID 提供者的公钥验证签名。通常,公钥通过 OpenID Connect (OIDC) 发现 (Discovery) 端点 (jwks_uri) 以 JSON Web 密钥 Set (JWKS) 格式提供。

在这个例子中,公钥为:

-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEF/xQdbOho2Jw0hgmNPD0VAEPAgkQrfD4
f1Qx3y49cUm646fMBX9DYx-43HzXm6VdX77uFymz90aO4dBunpTdUzLFRAiT7+In
gzZGDrIE+FG6CcqQuRP65r65SUzDOmP5
-----END PUBLIC KEY-----

对应的 JWK 值为:

{
  "kty": "EC",
  "crv": "P-384",
  "x": "F_xQdbOho2Jw0hgmNPD0VAEPAgkQrfD4f1Qx3y49cUm646fMBX9DYx-43HzXm6Vd",
  "y": "X77uFymz90aO4dBunpTdUzLFRAiT7-IngzZGDrIE-FG6CcqQuRP65r65SUzDOmP5"
}

现在,客户端可以使用公钥验证签名。

选择正确的算法

有多种算法可用于签署 JWT:

  • 对称算法:HMAC 与 SHA 系列(例如,HS256、HS384、HS512)是使用相同密钥进行签署和验证的对称算法。由于需要在各方之间共享密钥,所以不推荐用于大多数情况。
  • 非对称算法:RSA(例如,RS256、RS384、RS512)和 ECDSA(例如,ES256、ES384、ES512)是使用一对密钥的非对称算法:一个用于签署的私钥和一个用于验证的公钥。
    • RSA 被广泛使用并支持许多库和平台。然而,与 ECDSA 相比,它的密钥大小和签名大小要大得多。
    • ECDSA 的性能更高,生成的签名更小,适合受限环境。由于它不太常见,请确保你的平台支持它。

由于性能和安全性的优势,ECDSA 应该是新应用程序的首选。

其他签名密钥场景

虽然上面的示例集中于 OIDC 中的 ID 令牌,但签名密钥的概念广泛用于各种场景,如签署电子邮件、文档和软件包。关键原则保持不变:

  • 对于对称密钥,签署和验证使用相同的密钥。这适用于各方可以安全地共享密钥的场景,或者单一实体负责签署和验证。
  • 对于非对称密钥,使用私钥进行签署,并使用相应的公钥进行验证。这适用于签署和验证各方为不同实体的大多数场景。

另请参阅