Как вручную проверить подпись JWT с помощью онлайн-инструментов

Насколько я понимаю, это простой процесс проверки подписи JWT. Но когда я использую некоторые онлайн-инструменты, чтобы сделать это за меня, это не совпадает. Как я могу вручную проверить подпись JWT без использования библиотеки JWT? Мне нужен быстрый метод (с использованием доступных онлайн-инструментов), чтобы продемонстрировать, как это делается.

Я создал свой JWT на https://jwt.io/#debugger-io со следующей информацией:

  • Алгоритм:HS256
  • Секрет:hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6
  • Заголовок:
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  • Полезная нагрузка:
    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }
    
  • Проверить подпись (раздел):
    • Секретное значение изменено на указанное выше
    • «Проверено» секретное значение в кодировке base64 (независимо от того, отмечен он или нет, значение все равно будет другим)

JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.wDQ2mU5n89f2HsHm1dluHGNebbXeNr748yJ9kUNDNCA

Ручная попытка проверки подписи JWT:

Использование калькулятор base64UrlEncode (http://www.simplycalc.com/base64url-encode.php или https://www.base64encode.org/)

Если я:(Не актуальная ценность на сайтах, измененная, чтобы показать, что инструменты в конечном итоге построят для меня)

base64UrlEncode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") + "." + base64UrlEncode("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ")

Я получил:

ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5.ZXlKemRXSWlPaUl4TWpNME5UWTNPRGt3SWl3aWJtRnRaU0k2SWtwdmFHNGdSRzlsSWl3aWFXRjBJam94TlRFMk1qTTVNREl5ZlE=

NOTE: there's some confusion on my part if I should be encoding the already encoded values, or use the already encoded values as-is.

(i.e. using base64UrlEncode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") + "." + base64UrlEncode("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ") vs "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ").

Regardless on which I should do, the end result still doesn't match the signature. I'm leaning towards that I should NOT re-encode the encoded value, whether that's true or not.

Затем с помощью Калькулятор генератора HMAC (https://codebeautify.org/hmac-generator или https://www.freeformatter.com/hmac-generator.html#ad-output)

(Не актуальная ценность на сайтах, измененная, чтобы показать, что инструменты в конечном итоге построят для меня)

HMACSHA256(
 "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5.ZXlKemRXSWlPaUl4TWpNME5UWTNPRGt3SWl3aWJtRnRaU0k2SWtwdmFHNGdSRzlsSWl3aWFXRjBJam94TlRFMk1qTTVNREl5ZlE = ",
  "hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6"
)

Что меня понимает:

a2de322575675ba19ec272e83634755d4c3c2cd74e9e23c8e4c45e1683536e01

И это не соответствует подписи JWT:

wDQ2mU5n89f2HsHm1dluHGNebbXeNr748yJ9kUNDNCAMзнак равноa2de322575675ba19ec272e83634755d4c3c2cd74e9e23c8e4c45e1683536e01


Цель:

Причина, по которой мне нужно это подтвердить, заключается в том, чтобы доказать возможность подтвердить, что JWT не был подделан, без декодирования JWT.

Веб-интерфейс моих клиентов не требует декодирования JWT, поэтому им не нужно устанавливать для этого пакет jwt. Им просто нужно выполнить простую проверку, чтобы подтвердить, что JWT не был подделан (хотя это маловероятно), прежде чем они сохранят JWT для будущих вызовов API.

Возможно, ваша проблема в том, что вы кодируете уже закодированное значение: base64UrlEncode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"). Строка «eyJh ...» уже является результатом кодирования {"alg":"HS256","typ":"JWT"}, поэтому повторно кодировать ее нет смысла.

jps 02.05.2018 09:22

@jps, это хороший момент, и тот, который я проверил, ничего не изменил, если я его снова закодирую или нет, в любом случае подпись неверна. Чтение инструкций звучит так, как будто мне нужно base64UrlEncode значение уже в JWT, в конечном итоге кодируя его дважды ... хотя я начинаю склоняться к тому, чтобы не нужно было кодировать его второй раз. Я добавлю эту заметку в вопрос для уточнения, спасибо.

skplunkerin 02.05.2018 17:14

Это всегда только одноразовое кодирование. В base64UrlEncode(header) + "." + base64UrlEncode(payload) полезная нагрузка и заголовок относятся к читаемому JSON.

jps 02.05.2018 17:19

Я согласен с тобой там @jps. Я хочу, чтобы это устранило проблему, я добавил примечание об этом в вопрос.

skplunkerin 02.05.2018 17:22

Я просто попробовал онлайн-инструменты HMAC (о которых я не знал раньше), и также получил другой результат, чем на jwt.io. Я тоже удивлен и сейчас не могу понять, что не так.

jps 02.05.2018 17:44

Я начинаю задаваться вопросом, неправильно ли jwt.io делает свою подпись ... или добавляет к указанному мной секрету, что могло бы вызвать это.

skplunkerin 02.05.2018 18:06

@jps Ваш ответ имеет смысл, но не соответствует подписи, которую я изначально пытался проверить. Я только что снял флажок jwt.io для "секретной кодировки base64" и подтвердил совпадение вашей логики. (кажется, что флажок не использует секрет, поскольку подпись возвращается к исходному значению подписи, когда вы не указываете секрет). Спасибо, что помогли мне разобраться!

skplunkerin 04.05.2018 21:46

Рад помочь. Это интересный вопрос, и я тоже кое-что узнал.

jps 04.05.2018 22:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
8
6 063
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Все дело в форматах и ​​кодировках.

На https://jwt.io вы получаете этот токен на основе ваших входных значений и секрета:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

Мы хотим доказать, что подпись:

3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

верно.

Подпись - это хэш HMAC-SHA256, закодированный в Base64url. (как описано в RFC7515)

Когда вы используете онлайн генератор HMAC для вычисления хэша для

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

с секретом

hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6

ты получаешь

de921a2a4b225fd66ff0983e8566eb0f6e1584bdfa84120568da40e1f571dbd3

как результат, который является значением HMAC-SHA256, но не в кодировке Base64url. Этот хеш представляет собой шестнадцатеричное строковое представление большого числа.

Чтобы сравнить его со значением из https://jwt.io, вам нужно преобразовать значение из его шестнадцатеричного строкового представления обратно в число и Base64url закодировать его.

Следующий скрипт делает это, а также использует crypto-js для вычисления собственного хэша. Это также может быть способом проверки без библиотек JWT.

var CryptoJS = require("crypto-js");

// the input values
var base64Header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
var base64Payload = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ";
var secret = "hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6";

// two hashes from different online tools
var signatureJWTIO = "3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M";
var onlineCaluclatedHS256 =  "de921a2a4b225fd66ff0983e8566eb0f6e1584bdfa84120568da40e1f571dbd3";

// hash calculation with Crypto-JS. 
// The two replace expressions convert Base64 to Base64url format by replacing 
// '+' with '-', '/' with '_' and stripping the '=' padding
var base64Signature = CryptoJS.HmacSHA256(base64Header + "." + base64Payload , secret).toString(CryptoJS.enc.Base64).replace(/\+/g,'-').replace(///g,'_').replace(/\=+$/m,'');

// converting the online calculated value to Base64 representation
var base64hash = new Buffer.from(onlineCaluclatedHS256, 'hex').toString('base64').replace(///g,'_').replace(/\+/g,'-').replace(/\=+$/m,'')


// the results:
console.info("Signature from JWT.IO             : " + signatureJWTIO);
console.info("NodeJS calculated hash            : " + base64Signature);
console.info("online calulated hash (converted) : " + base64hash);

Результаты следующие:

Signature from JWT.IO             : 3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

NodeJS calculated hash            : 3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

online calulated hash (converted) : 3pIaKksiX9Zv8Jg-hWbrD24VhL36hBIFaNpA4fVx29M

идентичный!

Заключение:

Значения, рассчитанные различными онлайн-инструментами, верны, но не могут быть напрямую сопоставлены из-за разных форматов и кодировок. Небольшой сценарий, показанный выше, может быть лучшим решением.

У меня была такая же проблема, пока я не понял, что использую простую кодировку base64 вместо base64url. Между ними есть еще несколько мелких деталей. Вот пошаговое руководство, которое, надеюсь, сделает весь процесс более понятным.

Заметки

Примечание 1: Вы должны удалить все пробелы и символы новой строки из ваших строк JSON (заголовок и полезные данные). Это неявно выполняется на jwt.io, когда вы генерируете токен JWT.

Заметка 2: Для преобразования строки JSON в строку base64url на сайте cryptii.com создайте следующую конфигурацию:

First view: Text

Second view: Encode
    Encoding: Base64
    Variant: Standard 'base64url' (RFC 4648 §5)

Third view: Text

Заметка 3: Чтобы преобразовать HEX-код (подпись) HMAC в строку base64url на сайте cryptii.com, создайте следующую конфигурацию:

First view: Bytes
    Format: Hexadecimal
    Group by: None

Second view: Encode
    Encoding: Base64
    Variant: Standard 'base64url' (RFC 4648 §5)

Third view: Text

Руководство по эксплуатации

Вам понадобятся всего два онлайн-инструмента:

  1. [Инструмент 1]:cryptii.com - для кодировки base64url,
  2. [Инструмент 2]:codebeautify.org - для расчета HMAC.

На cryptii.com вы можете выполнять как кодирование / декодирование base64url, так и вычисление HMAC, но для HMAC вам необходимо предоставить ключ HEX, который отличается от ввода jwt.io, поэтому я использовал отдельный сервис для расчета HMAC.

Входные данные

В этом руководстве я использовал следующие данные:

  • Заголовок:

    {"alg":"HS256","typ":"JWT"}
    
  • Полезная нагрузка:

    {"sub":"1234567890","name":"John Doe","iat":1516239022}
    
  • Секретный ключ):

    The Earth is flat!
    

Секрет не в кодировке base64.

Шаг 1. Преобразование заголовка [Инструмент 1]

  • Заголовок (простой текст):

    {"alg":"HS256","typ":"JWT"}
    
  • Заголовок (в кодировке base64url):

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    

Шаг 2. Преобразование полезной нагрузки [Инструмент 1]

  • Полезная нагрузка (простой текст):

    {"sub":"1234567890","name":"John Doe","iat":1516239022}
    
  • Полезная нагрузка (в кодировке base64url):

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    

Шаг 3. Рассчитайте код HMAC (подпись) [Инструмент 2]

Рассчитайте HMAC с использованием алгоритма SHA256.

  • Входная строка (заголовок и полезная нагрузка в кодировке base64url, соединенные точкой):

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    
  • Расчетный код (шестнадцатеричное число):

    c8a9ae59f3d64564364a864d22490cc666c74c66a3822be04a9a9287a707b352
    

Вычисленный код HMAC - это шестнадцатеричное представление подписи. Примечание: его следует кодировать в base64url не как обычную текстовую строку, а как последовательность байтов.

Шаг 4. Закодируйте вычисленный код HMAC в base64url [Инструмент 1]:

  • Подпись (байты):

    c8a9ae59f3d64564364a864d22490cc666c74c66a3822be04a9a9287a707b352
    
  • Подпись (в кодировке base64url):

    yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I
    

Резюме

Вот наши результаты (все в кодировке base64url):

  • Заголовок:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    
  • Полезная нагрузка:

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    
  • Подпись:

    yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I
    

Результаты jwt.io:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I

Как видите, все три части идентичны.

+1 для «Рассчитанный код HMAC является шестнадцатеричным представлением подписи. Примечание: он должен быть закодирован в base64url не как обычная текстовая строка, а как последовательность байтов».

isapir 21.10.2019 18:52

Другие вопросы по теме