使用C#验证具有RS256(非对称)的JWT

10

我有一些类似此代码的内容,我认为它失败了,因为它使用了非对称 RS256 但是却有“SymmetricSecurityKey()”。这些令牌是手工从https://jwt.io/生成的。

  1. 如何将其转换为使用我的非对称公钥?
  2. 而且,我是C#新手,我想要针对 dotnet standard,所以我想知道我是否在使用错误的库?(我依赖于预览版本)
λ 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
}

1
https://dev59.com/HFsW5IYBdhLWcg3w8rH2 - zaitsman
1个回答

14
  • Regarding your 1st question:
    According to your posted stack trace, you seem to be using .NET Core 3.1. This allows you to easily import your public X.509/SPKI key as follows:

    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() is available since .NET Core 3.0.

    Edit start: In earlier versions of .NET Core (before 3.0) or in the .NET Framework ImportSubjectPublicKeyInfo() is not available, so at least .NET Standard 2.1 is required.

    For earlier versions, e.g. .NET Standard 2.0, one possibility is to use 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 you will find an example of how to use PemReader. PemReader processes, as the name suggests, a PEM encoding, i.e. the conversion to a DER encoding (i.e. the removal of header, footer and line breaks, as well as the Base64 decoding of the remainder) as required by ImportSubjectPublicKeyInfo() must not be done. Also note that PemReader expects at least one line break immediately after the header (-----BEGIN PUBLIC KEY-----\n) and a second one immediately before the footer (\n-----END PUBLIC KEY-----), the line breaks in the Base64 encoded body after every 64 characters are optional for PemReader.

    Another possibility is the package opensslkey providing the method opensslkey.DecodeX509PublicKey(), which can process an X509/SPKI key in DER encoding analogous to ImportSubjectPublicKeyInfo. Edit end

  • Regarding your 2nd question:
    There are several .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 you are using requires .NET Standard 2.0.

    System.IdentityModel.Tokens.Jwt is a package that supports the creation and validation of JSON Web Tokens (JWT). In the case of the posted token, the validation could be implemented as follows:

    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);
    

    The validation can be controlled via the validation parameters, i.e. via the 2nd parameter of ValidateToken(). Since the posted token does not contain the claims iss, aud and exp (this can be verified e.g. on https://jwt.io/), they are excluded from the validation in my example.

    In the tutorial Creating And Validating JWT Tokens In ASP.NET Core you will find a more detailed explanation, especially in the chapter Validating A Token.

    ValidateToken() essentially encapsulates the verification process of the JWT signature. A 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 of the linked question in the comment of @zaitsman.


1
非常感谢!你真是救星——我一直在试图按照微软文档的指示操作,结果发现我使用了错误版本的dotnet。这是为了让我想要与建立在dotnet framework上的旧代码和建立在dotnet core 3.1上的新代码一起工作的库。我觉得很奇怪RSACryptoServiceProvider()不会接受一个字符串并自动检测和转换pki密钥。 - Charles Thayer
糟糕,一个问题是如果我针对dotnetstandard2.0目标,则此部分无法工作,但使用dotnetstandard2.1可以。RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(pubKey), out _); // 导入公共X.509 / SPKI DER编码密钥 - Charles Thayer
1
对于低于 .NET Standard 2.1 的版本,ImportSubjectPublicKeyInfo 不可用,需要另一种选项,请参见我的第一个要点的编辑部分。 - Topaco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接