在 C# 中使用 RS256(非对称)验证 JWT

Verify JWT with RS256 (asymmetric) in C#

我有一些这样的代码,我认为它失败了,因为它使用的是非对称 RS256 但具有“SymmetricSecurityKey()”。令牌是从 https://jwt.io/

手工生成的
  1. 如何将其转换为使用我的非对称 public 密钥?
  2. 另外,我是 C# 的新手,我想以 dotnet 标准为目标,所以我也想知道我是否使用了错误的库? (我取决于预览版)
λ cat Program.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using System.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;

namespace jwttest
{
    class Program
    {
        static void Main(string[] args)
        {
            string jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA";
            var pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB";
            var rawKey = Encoding.ASCII.GetBytes(pubKey);

            var tokenHandler = new JwtSecurityTokenHandler();
            // var rsa = ?
            tokenHandler.ValidateToken(jwt, new TokenValidationParameters {
                IssuerSigningKey = new SymmetricSecurityKey(rawKey)
            },
            out SecurityToken validatedToken);
        }
    }
}

C:\src\jwttest (cgt-test-5 -> origin)
λ dotnet run
[2020-08-18T23:41:05.7108585-07:00 Info] raw=System.Byte[] [392]
Unhandled exception. Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: 'System.Text.StringBuilder'.
Exceptions caught:
 'System.Text.StringBuilder'.
token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
   at jwttest.Program.Main(String[] args) in C:\src\jwttest\Program.cs:line 22

λ cat jwttest.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <!-- Using preview release because it only depends on dotnet standard.  Prior versions need framework. -->
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.2-preview-10803222715" />
  </ItemGroup>
</Project>

λ cat jwt.json
{
  "alg": "RS256",
  "typ": "JWT"
}
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}
  • 关于您的第一个问题:
    根据您发布的堆栈跟踪,您似乎正在使用 .NET Core 3.1。这使您可以轻松地导入 public X.509/SPKI 密钥,如下所示:

    var pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB";
    
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(pubKey), out _); // import the public X.509/SPKI DER encoded key
    

    ImportSubjectPublicKeyInfo() 自 .NET Core 3.0 起可用。

    编辑开始: 在 .NET Core 的早期版本(3.0 之前)或 .NET Framework 中 ImportSubjectPublicKeyInfo() 不可用,因此至少需要 .NET Standard 2.1

    对于早期版本,例如.NET Standard 2.0,一种可能是使用 BouncyCastle, more precisely its Org.BouncyCastle.OpenSsl.PemReader class, which allows the import of public keys in X509/SPKI format (and, irrelevant for you, also in PKCS#1 format). In this answer 您会找到一个如何使用 PemReader 的示例。 PemReader 处理,顾名思义,PEM 编码,即转换为 DER 编码(即删除 header、页脚和换行符,以及其余部分的 Base64 解码)作为ImportSubjectPublicKeyInfo() 要求 不得完成 。另请注意,PemReader 预计在 header (-----BEGIN PUBLIC KEY-----\n) 之后至少有一个换行符,在页脚 (\n-----END PUBLIC KEY-----) 之前有第二个换行符,换行符在base64编码后body每64个字符可选为PemReader.

    另一种可能性是包 opensslkey 提供了方法 opensslkey.DecodeX509PublicKey(),它可以处理类似于 ImportSubjectPublicKeyInfo 的 DER 编码中的 X509/SPKI 密钥。 编辑结束

  • 关于你的第二个问题:
    有几个 .NET standard versions, e.g. .NET Core 3.0 implements .NET Standard 2.1. The package System.IdentityModel.Tokens.Jwt 6.7.2-preview-10803222715 您正在使用 requires .NET Standard 2.0.

    System.IdentityModel.Tokens.Jwt 是一个支持创建和验证 JSON Web 令牌 (JWT) 的包。对于发布的令牌,验证可以按如下方式实现:

    string jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA";
    
    var tokenHandler = new JwtSecurityTokenHandler();
    bool verified = false;
    try
    {
        tokenHandler.ValidateToken(jwt, new TokenValidationParameters
        {
            ValidateAudience = false,                       
            ValidateLifetime = false,
            ValidateIssuer = false,
            IssuerSigningKey = new RsaSecurityKey(rsa)
        },
        out _);
    
        verified = true;
    }
    catch 
    {
        verified = false;
    }
    
    Console.WriteLine("Verified: " + verified);
    

    验证可以通过验证参数控制,即通过 ValidateToken(). Since the posted token does not contain the claims iss, aud and exp (this can be verified e.g. on https://jwt.io/ 的第二个参数),在我的示例中它们被排除在验证之外。

    在教程 Creating And Validating JWT Tokens In ASP.NET Core 中,您会找到更详细的解释,尤其是在 验证令牌 .

    一章中

    ValidateToken()本质上封装了JWT签名的验证过程。 @zaitsman 评论中的链接问题的JWT is a data structure that consists of three parts: header, payload and signature, the individual parts being Base64url encoded and separated from each other by a dot.
    The signature is created using various algorithms, e.g. in your case RS256, which means that the data (Base64url encoded header and payload including separator) is signed using the algorithm RSA with PKCS#1 v1.5 padding and digest SHA256.
    The verification of a token corresponds to the verification of the signature, which can also be done solely with cryptographic APIs (i.e. without participation of System.IdentityModel.Tokens.Jwt), as it is done in the accepted answer