ตรวจสอบ JWT ด้วย RS256 (ไม่สมมาตร) ใน C #

Aug 19 2020

ฉันมีรหัสแบบนี้ซึ่งฉันเชื่อว่าล้มเหลวเนื่องจากใช้ Asymmetric RS256 แต่มี "SymmetricSecurityKey ()" โทเค็นถูกสร้างขึ้นด้วยมือจากhttps://jwt.io/

  1. ฉันจะแปลงสิ่งนี้เพื่อใช้คีย์สาธารณะแบบไม่สมมาตรได้อย่างไร
  2. นอกจากนี้ฉันยังใหม่กับ C # และฉันต้องการกำหนดเป้าหมายมาตรฐานดอทเน็ตดังนั้นฉันจึงสงสัยว่าฉันใช้ libs ผิดหรือเปล่า? (ฉันขึ้นอยู่กับรุ่นตัวอย่าง)
λ 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
}

คำตอบ

2 Topaco Aug 19 2020 at 13:26
  • เกี่ยวกับคำถามที่ 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
    

    ImportSubjectPublicKeyInfo() สามารถใช้ได้ตั้งแต่. NET Core 3.0

    แก้ไขเริ่มต้น: ในรุ่นก่อนหน้า .NET แกน (ก่อน 3.0) หรือใน .NET Framework ImportSubjectPublicKeyInfo()ไม่สามารถใช้ได้ดังนั้นอย่างน้อย.NET มาตรฐาน 2.1เป็นสิ่งจำเป็น

    สำหรับเวอร์ชันก่อนหน้าเช่น. NET Standard 2.0 ความเป็นไปได้อย่างหนึ่งคือการใช้BouncyCastleซึ่งเป็นOrg.BouncyCastle.OpenSsl.PemReaderคลาสที่แม่นยำกว่าซึ่งอนุญาตให้นำเข้าคีย์สาธารณะในรูปแบบ X509 / SPKI (และไม่เกี่ยวข้องกับคุณเช่นกันในรูปแบบ PKCS # 1) ในคำตอบนี้PemReaderคุณจะได้พบตัวอย่างของวิธีการใช้ PemReaderกระบวนการเป็นชื่อที่แสดงให้เห็นการเข้ารหัส PEM คือการแปลงการเข้ารหัส DER (เช่นการกำจัดของส่วนหัวส่วนท้ายและสายแบ่งการเช่นเดียวกับการถอดรหัส Base64 ที่เหลือ) ตามที่กำหนดไว้ต้องไม่สามารถทำได้ImportSubjectPublicKeyInfo() นอกจากนี้ทราบว่าPemReaderคาดว่าเส้นแบ่งอย่างน้อยหนึ่งทันทีหลังจากที่ส่วนหัว ( -----BEGIN PUBLIC KEY-----\n) และคนที่สองทันทีก่อนที่ส่วนท้าย ( \n-----END PUBLIC KEY-----) แบ่งบรรทัดในเข้ารหัส Base64 ร่างกายทุกครั้งหลัง 64 PemReaderตัวอักษรเป็นตัวเลือกสำหรับ

    เป็นไปได้ก็คือแพคเกจopensslkeyให้วิธีการopensslkey.DecodeX509PublicKey()ซึ่งสามารถดำเนินการที่สำคัญ X509 / SPKI ใน DER ImportSubjectPublicKeyInfoเข้ารหัสจะคล้าย แก้ไขจุดสิ้นสุด

  • เกี่ยวกับคำถามที่ 2 ของคุณ:
    มี. 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 Tokens (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()2 ตั้งแต่โทเค็นโพสต์ไม่ได้มีการเรียกร้อง iss , AUDและประสบการณ์ (ซึ่งสามารถตรวจสอบได้เช่นบนhttps://jwt.io/) ซึ่งไม่รวมอยู่ในการตรวจสอบความถูกต้องในตัวอย่างของฉัน

    ในการสอนการสร้างและการตรวจสอบความถูก JWT ราชสกุลใน ASP.NET หลักคุณจะพบคำอธิบายรายละเอียดมากขึ้นโดยเฉพาะอย่างยิ่งในบทValidating เป็นสัญลักษณ์

    ValidateToken()สรุปกระบวนการตรวจสอบลายเซ็น JWT เป็นหลัก JWTเป็นโครงสร้างข้อมูลที่ประกอบด้วยสามส่วนคือส่วนหัวของน้ำหนักบรรทุกและลายเซ็นแต่ละส่วนเป็น Base64url เข้ารหัสและแยกออกจากกันโดยจุด
    ลายเซ็นถูกสร้างขึ้นโดยใช้อัลกอริทึมต่างๆเช่นในกรณีของคุณRS256ซึ่งหมายความว่าข้อมูล (ส่วนหัวที่เข้ารหัส Base64url และเพย์โหลดรวมถึงตัวคั่น) ถูกเซ็นชื่อโดยใช้อัลกอริทึม RSA พร้อม PKCS # 1 v1.5 padding และแยกย่อย SHA256
    การตรวจสอบโทเค็นนั้นสอดคล้องกับการตรวจสอบลายเซ็นซึ่งสามารถทำได้ด้วย API การเข้ารหัสเท่านั้น (เช่นไม่มีส่วนร่วมของSystem.IdentityModel.Tokens.Jwt ) ตามที่ได้ดำเนินการในคำตอบที่ยอมรับของคำถามที่เชื่อมโยงใน ความคิดเห็นของ @zaitsman.