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

Cha*_*yer 10 c# rsa public-key jwt

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

\n
    \n
  1. 如何将其转换为使用我的非对称公钥?
  2. \n
  3. 另外,我是 C# 新手,我想以 dotnet 标准为目标,所以我也想知道我是否使用了错误的库?(我依赖于预览版)
  4. \n
\n
\xce\xbb cat Program.cs\n\xef\xbb\xbfusing System;\nusing System.IdentityModel.Tokens.Jwt;\nusing System.Text;\nusing System.Linq;\nusing Microsoft.IdentityModel.Tokens;\nusing System.Security.Cryptography;\n\nnamespace jwttest\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            string jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA";\n            var pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB";\n            var rawKey = Encoding.ASCII.GetBytes(pubKey);\n\n            var tokenHandler = new JwtSecurityTokenHandler();\n            // var rsa = ?\n            tokenHandler.ValidateToken(jwt, new TokenValidationParameters {\n                IssuerSigningKey = new SymmetricSecurityKey(rawKey)\n            },\n            out SecurityToken validatedToken);\n        }\n    }\n}\n\nC:\\src\\jwttest (cgt-test-5 -> origin)\n\xce\xbb dotnet run\n[2020-08-18T23:41:05.7108585-07:00 Info] raw=System.Byte[] [392]\nUnhandled exception. Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: \'System.Text.StringBuilder\'.\nExceptions caught:\n \'System.Text.StringBuilder\'.\ntoken: \'System.IdentityModel.Tokens.Jwt.JwtSecurityToken\'.\n   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)\n   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)\n   at jwttest.Program.Main(String[] args) in C:\\src\\jwttest\\Program.cs:line 22\n\n\xce\xbb cat jwttest.csproj\n<Project Sdk="Microsoft.NET.Sdk">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <!-- Using preview release because it only depends on dotnet standard.  Prior versions need framework. -->\n    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.2-preview-10803222715" />\n  </ItemGroup>\n</Project>\n\n\xce\xbb cat jwt.json\n{\n  "alg": "RS256",\n  "typ": "JWT"\n}\n{\n  "sub": "1234567890",\n  "name": "John Doe",\n  "admin": true,\n  "iat": 1516239022\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Top*_*aco 14

  • 关于你的第一个问题:
    根据你发布的堆栈跟踪,你似乎正在使用 .NET Core 3.1。这使您可以轻松导入公共 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
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    对于早期版本,例如.NET Standard 2.0,一种可能性是使用BouncyCastle,更准确地说是它的Org.BouncyCastle.OpenSsl.PemReader类,它允许导入 X509/SPKI 格式的公钥(并且与您无关,也可以采用 PKCS#1 格式)。在此答案中,您将找到如何使用 的示例PemReaderPemReader顾名思义,处理 PEM 编码,即ImportSubjectPublicKeyInfo() 不得按照要求进行到 DER 编码的转换(即删除页眉、页脚和换行符,以及剩余部分的 Base64 解码) 。另请注意,PemReader预计在页眉 ( ) 之后至少有一个换行符-----BEGIN PUBLIC KEY-----\n,在页脚 ( ) 之前至少有一个\n-----END PUBLIC KEY-----换行符,Base64 编码正文中每 64 个字符之后的换行符对于 是可选的PemReader

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

  • 关于你的第二个问题:.NET标准
    有几个版本,例如.NET Core 3.0实现了.NET Standard 2.1。您正在使用的包System.IdentityModel.Tokens.Jwt 6.7.2-preview-10803222715需要 .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);
    
    Run Code Online (Sandbox Code Playgroud)

    验证可以通过验证参数来控制,即通过 的第二个参数ValidateToken()。由于发布的令牌不包含声明 issaudexp (这可以在https://jwt.io/上进行验证),因此在我的示例中它们被排除在验证之外。

    在教程“在 ASP.NET Core 中创建和验证 JWT 令牌”中,您会找到更详细的说明,特别是在“验证令牌”一章中。

    ValidateToken()本质上封装了JWT签名的验证过程。JWT是一种数据结构,由三部分组成:标头、有效负载和签名,各个部分均经过 Base64url 编码并通过点分隔。
    签名是使用各种算法创建的,例如在您的情况下RS256,这意味着数据(Base64url 编码的标头和包括分隔符的有效负载)使用带有 PKCS#1 v1.5 填充和摘要 SHA256 的 RSA 算法进行签名。
    令牌的验证对应于签名的验证,这也可以仅使用加密 API 来完成(即无需System.IdentityModel.Tokens.Jwt的参与),因为它是在链接问题的接受答案中完成的@zaitsman 的评论。