Проверить JWT с RS256 (асимметричным) на C #
У меня есть такой код, который, как мне кажется, дает сбой, потому что он использует асимметричный RS256, но имеет «SymmetricSecurityKey ()». Жетоны были созданы вручную изhttps://jwt.io/
- Как мне преобразовать это, чтобы использовать мой асимметричный открытый ключ?
- Кроме того, я новичок в 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
}
Ответы
Что касается вашего 1-го вопроса:
согласно вашей опубликованной трассировке стека, похоже, вы используете .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
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). В этом ответе вы найдете пример того, как использоватьPemReader
.PemReader
процессы, как следует из названия, кодирование PEM, то есть преобразование в кодировку DER (то есть удаление заголовков, нижних колонтитулов и разрывов строк, а также декодирование остатка Base64), как того требует,ImportSubjectPublicKeyInfo()
не должно выполняться . Также обратите внимание, чтоPemReader
ожидается как минимум один разрыв строки сразу после header (-----BEGIN PUBLIC KEY-----\n
) и второй сразу перед footer (\n-----END PUBLIC KEY-----
), разрывы строк в кодированном Base64 теле после каждых 64 символов являются необязательными дляPemReader
.Другой возможностью является пакет opensslkey, предоставляющий метод
opensslkey.DecodeX509PublicKey()
, который может обрабатывать ключ X509 / SPKI в кодировке DER, аналогичной кодировке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 (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);
Валидацией можно управлять с помощью параметров валидации, т. Е. С помощью 2-го параметра ValidateToken(). Поскольку опубликованный токен не содержит утверждений iss , aud и exp (это можно проверить, например, наhttps://jwt.io/), в моем примере они исключены из проверки.
В учебнике « Создание и проверка токенов JWT в ASP.NET Core» вы найдете более подробное объяснение, особенно в главе « Проверка токена» .
ValidateToken()
по существу инкапсулирует процесс проверки подписи JWT. JWT представляет собой структуру данных , которая состоит из трех частей: заголовок, полезной нагрузки и подписи, отдельные части , являющиеся Base64url кодируются и отделяются друг от друга точкой.
Подпись создается с использованием различных алгоритмов, например, в вашем случае RS256 , что означает, что данные (заголовок в кодировке Base64url и полезная нагрузка, включая разделитель) подписываются с использованием алгоритма RSA с заполнением PKCS # 1 v1.5 и дайджестом SHA256.
Проверка токена соответствует проверке подписи, которая также может выполняться исключительно с помощью криптографических API (то есть без участия System.IdentityModel.Tokens.Jwt ), как это делается в принятом ответе на связанный вопрос в комментарий @zaitsman.