Verifikasi JWT dengan RS256 (asimetris) di C #

Aug 19 2020

Saya memiliki beberapa kode seperti ini yang saya percaya gagal karena menggunakan Asymmetric RS256 tetapi memiliki "SymmetricSecurityKey ()". Token itu dibuat dengan tanganhttps://jwt.io/

  1. Bagaimana cara saya mengonversinya untuk menggunakan kunci publik asimetris saya?
  2. Juga, saya baru mengenal C # dan saya ingin menargetkan standar dotnet, jadi saya juga bertanya-tanya apakah saya menggunakan libs yang salah? (Saya bergantung pada rilis pratinjau)
λ 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
}

Jawaban

2 Topaco Aug 19 2020 at 13:26
  • Mengenai pertanyaan pertama Anda:
    Menurut pelacakan tumpukan yang Anda posting, Anda tampaknya menggunakan .NET Core 3.1. Ini memungkinkan Anda untuk dengan mudah mengimpor kunci X.509 / SPKI publik Anda sebagai berikut:

    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() tersedia sejak .NET Core 3.0.

    Edit mulai: Di ​​versi .NET Core (sebelum 3.0) atau di .NET Framework ImportSubjectPublicKeyInfo()tidak tersedia, jadi setidaknya .NET Standard 2.1 diperlukan.

    Untuk versi sebelumnya, misalnya .NET Standard 2.0, salah satu kemungkinannya adalah menggunakan BouncyCastle , lebih tepatnya Org.BouncyCastle.OpenSsl.PemReaderkelasnya, yang memungkinkan impor kunci publik dalam format X509 / SPKI (dan, tidak relevan untuk Anda, juga dalam format PKCS # 1). Dalam jawaban ini Anda akan menemukan contoh cara menggunakan PemReader. PemReaderproses, seperti namanya, pengkodean PEM, yaitu konversi ke pengkodean DER (yaitu penghapusan header, footer dan jeda baris, serta penguraian sisa Base64) seperti yang disyaratkan oleh ImportSubjectPublicKeyInfo() tidak boleh dilakukan . Perhatikan juga bahwa PemReadermengharapkan setidaknya satu jeda baris segera setelah header ( -----BEGIN PUBLIC KEY-----\n) dan yang kedua tepat sebelum footer ( \n-----END PUBLIC KEY-----), jeda baris dalam isi yang dikodekan Base64 setelah setiap 64 karakter adalah opsional PemReader.

    Kemungkinan lain adalah paket opensslkey menyediakan metode tersebut opensslkey.DecodeX509PublicKey(), yang dapat memproses kunci X509 / SPKI dalam pengkodean DER yang serupa ImportSubjectPublicKeyInfo. Edit akhir

  • Mengenai pertanyaan kedua Anda:
    Ada beberapa versi standar .NET , misalnya .NET Core 3.0 mengimplementasikan .NET Standard 2.1. Paket System.IdentityModel.Tokens.Jwt 6.7.2-preview-10803222715 yang Anda gunakan memerlukan .NET Standard 2.0.

    System.IdentityModel.Tokens.Jwtadalah paket yang mendukung pembuatan dan validasi JSON Web Tokens (JWT). Dalam kasus token yang diposting, validasi dapat diterapkan sebagai berikut:

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

    Validasi dapat dikontrol melalui parameter validasi, yaitu melalui parameter ke-2 dari ValidateToken(). Karena token yang diposting tidak mengandung klaim iss , aud dan exp (ini dapat diverifikasi misalnya padahttps://jwt.io/), mereka dikecualikan dari validasi dalam contoh saya.

    Dalam tutorial Membuat Dan Memvalidasi Token JWT Di ASP.NET Core Anda akan menemukan penjelasan yang lebih detail, terutama pada bab Memvalidasi Token .

    ValidateToken()pada dasarnya merangkum proses verifikasi tanda tangan JWT. Sebuah JWT adalah struktur data yang terdiri dari tiga bagian: header, payload dan tanda tangan, bagian-bagian individu menjadi Base64url dikodekan dan terpisah satu sama lain oleh sebuah titik.
    Tanda tangan dibuat menggunakan berbagai algoritme, misalnya dalam kasus Anda RS256 , yang berarti bahwa data (header dan muatan berenkode Base64url termasuk pemisah) ditandatangani menggunakan algoritme RSA dengan padding PKCS # 1 v1.5 dan ringkasan SHA256.
    Verifikasi token sesuai dengan verifikasi tanda tangan, yang juga dapat dilakukan hanya dengan API kriptografi (yaitu tanpa partisipasi System.IdentityModel.Tokens.Jwt ), seperti yang dilakukan dalam jawaban yang diterima dari pertanyaan terkait di komentar @zaitsman.