Насколько я понимаю, это простой процесс проверки подписи JWT. Но когда я использую некоторые онлайн-инструменты, чтобы сделать это за меня, это не совпадает. Как я могу вручную проверить подпись JWT без использования библиотеки JWT? Мне нужен быстрый метод (с использованием доступных онлайн-инструментов), чтобы продемонстрировать, как это делается.
Я создал свой JWT на https://jwt.io/#debugger-io со следующей информацией:
HS256hONPMX3tHWIp9jwLDtoCUwFAtH0RwSK6
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
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.
@jps, это хороший момент, и тот, который я проверил, ничего не изменил, если я его снова закодирую или нет, в любом случае подпись неверна. Чтение инструкций звучит так, как будто мне нужно base64UrlEncode значение уже в JWT, в конечном итоге кодируя его дважды ... хотя я начинаю склоняться к тому, чтобы не нужно было кодировать его второй раз. Я добавлю эту заметку в вопрос для уточнения, спасибо.
Это всегда только одноразовое кодирование. В base64UrlEncode(header) + "." + base64UrlEncode(payload) полезная нагрузка и заголовок относятся к читаемому JSON.
Я согласен с тобой там @jps. Я хочу, чтобы это устранило проблему, я добавил примечание об этом в вопрос.
Я просто попробовал онлайн-инструменты HMAC (о которых я не знал раньше), и также получил другой результат, чем на jwt.io. Я тоже удивлен и сейчас не могу понять, что не так.
Я начинаю задаваться вопросом, неправильно ли jwt.io делает свою подпись ... или добавляет к указанному мной секрету, что могло бы вызвать это.
@jps Ваш ответ имеет смысл, но не соответствует подписи, которую я изначально пытался проверить. Я только что снял флажок jwt.io для "секретной кодировки base64" и подтвердил совпадение вашей логики. (кажется, что флажок не использует секрет, поскольку подпись возвращается к исходному значению подписи, когда вы не указываете секрет). Спасибо, что помогли мне разобраться!
Рад помочь. Это интересный вопрос, и я тоже кое-что узнал.





Все дело в форматах и кодировках.
На 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
Вам понадобятся всего два онлайн-инструмента:
base64url,На 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.
Заголовок (простой текст):
{"alg":"HS256","typ":"JWT"}
Заголовок (в кодировке base64url):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Полезная нагрузка (простой текст):
{"sub":"1234567890","name":"John Doe","iat":1516239022}
Полезная нагрузка (в кодировке base64url):
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Рассчитайте HMAC с использованием алгоритма SHA256.
Входная строка (заголовок и полезная нагрузка в кодировке base64url, соединенные точкой):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Расчетный код (шестнадцатеричное число):
c8a9ae59f3d64564364a864d22490cc666c74c66a3822be04a9a9287a707b352
Вычисленный код HMAC - это шестнадцатеричное представление подписи.
Примечание: его следует кодировать в base64url не как обычную текстовую строку, а как последовательность байтов.
base64url [Инструмент 1]:Подпись (байты):
c8a9ae59f3d64564364a864d22490cc666c74c66a3822be04a9a9287a707b352
Подпись (в кодировке base64url):
yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I
Вот наши результаты (все в кодировке base64url):
Заголовок:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Полезная нагрузка:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Подпись:
yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I
Результаты jwt.io:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.yKmuWfPWRWQ2SoZNIkkMxmbHTGajgivgSpqSh6cHs1I
Как видите, все три части идентичны.
+1 для «Рассчитанный код HMAC является шестнадцатеричным представлением подписи. Примечание: он должен быть закодирован в base64url не как обычная текстовая строка, а как последовательность байтов».
Возможно, ваша проблема в том, что вы кодируете уже закодированное значение:
base64UrlEncode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"). Строка «eyJh ...» уже является результатом кодирования{"alg":"HS256","typ":"JWT"}, поэтому повторно кодировать ее нет смысла.