Проверка токена доступа с помощью at_hash
Я пытаюсь проверить токены доступа против at_hash. Заголовок токена выглядит следующим образом
{
"typ": "JWT",
"alg": "RS256",
"x5t": "MclQ7Vmu-1e5_rvdSfBShLe82eY",
"kid": "MclQ7Vmu-1e5_rvdSfBShLe82eY"
}
Как я могу получить из моего токена доступа к значению претензии at_hash в кодировке Base64, которое находится в токене id? Есть ли онлайн-инструмент, который мог бы помочь мне с этим? Разве SHA256 хеш-калькулятор не подходит для этого?
Спасибо
5 ответов
Разве SHA256 хеш-калькулятор не подходит для этого?
Это не работает, потому что вам нужно использовать двоичные данные для одного из шагов, и почти все веб-инструменты ожидают какой-то текст в качестве ввода и генерируют текст в качестве вывода. Онлайн инструменты не подходят для этого. Я напишу инструмент, чтобы вы могли видеть, как это делается.
Как я могу получить из моего токена доступа к значению претензии at_hash в кодировке Base64, которое находится в токене id?
Это моя первая итерация программы на C# 2:), так что, если она уродливая, это потому, что я никогда не использовал ее раньше. Объяснение после этого объяснит, как вычислить токен at_hash, включая, почему нам нужен decode_base64
,
using System;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Text;
namespace AtHash
{
class AtHash
{
private const String access_token = "ya29.eQGmYe6H3fP_d65AY0pOMCFikA0f4hzVZGmTPPyv7k_l6HzlEIpFXnXGZjcMhkyyuqSMtN_RTGJ-xg";
private const String id1 = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUxMWQ1N2QxZmY0ODA0YjMxYzA1MWI3MWY2ZDVlNWExZmQyOTdjZjgifQ";
private const String id2 = "eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwMTY5NDg0NDc0Mzg2Mjc2MzM0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiZW1haWwiOiJiaWxsZDE2MDBAZ21haWwuY29tIiwiYXRfaGFzaCI6ImxPdEkwQlJvdTBaNExQdFF1RThjQ3ciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaWF0IjoxNDMyMTQyMjIyLCJleHAiOjE0MzIxNDU4MjJ9";
private byte[] decode_base64(String str) {
List<byte> l = new List<Byte>(Encoding.Default.GetBytes(str));
while (l.Count % 4 != 0 ){
l.Add(Convert.ToByte('='));
}
return Convert.FromBase64String(Encoding.Default.GetString(l.ToArray()));
}
public String sha256_at_hash(String access_token) {
SHA256Managed hashstring = new SHA256Managed();
byte[] bytes = Encoding.Default.GetBytes(access_token);
byte[] hash = hashstring.ComputeHash(bytes);
Byte[] sixteen_bytes = new Byte[16];
Array.Copy(hash, sixteen_bytes, 16);
return Convert.ToBase64String(sixteen_bytes).Trim('=');
}
public static void Main (string[] args) {
AtHash ah = new AtHash();
byte[] id1_str = ah.decode_base64 (id1);
byte[] id2_str = ah.decode_base64 (id2);
Console.WriteLine(Encoding.Default.GetString(id1_str));
Console.WriteLine(Encoding.Default.GetString(id2_str));
Console.WriteLine ("\n\tat_hash value == " + ah.sha256_at_hash(access_token));
}
}
}
Вывод этой программы (форматирование мое)
{
"alg":"RS256",
"kid":"e11d57d1ff4804b31c051b71f6d5e5a1fd297cf8"
}
{
"exp" : 1432145822,
"iat" : 1432142222,
"azp" : "407408718192.apps.googleusercontent.com",
"aud" : "407408718192.apps.googleusercontent.com",
"email_verified" : true,
"iss" : "accounts.google.com",
"at_hash" : "lOtI0BRou0Z4LPtQuE8cCw",
"sub" : "110169484474386276334",
"email" : "billd1600@gmail.com"
}
at_hash value == lOtI0BRou0Z4LPtQuE8cCw
Это как проверить at_hash
значение. Вы можете пропустить часть Google, если хотите использовать данные, которые я использовал, но если вы хотите проверить их на новых данных, вы можете получить их в Google...
Получить токен доступа от Googles O2Auth Playground
Иди сюда
https://developers.google.com/oauthplayground/
Ничего не выбирайте, в нижней части страницы есть поле ввода. Введите openid
и ударил Authorize APIs
щелкните по идентификатору, который хотите использовать, и выберите allow
, Выбрать Exchange authorization code for tokens
, В случае успеха вы получите что-то похожее на следующее.
{
"access_token": "ya29.eQGmYe6H3fP_d65AY0pOMCFikA0f4hzVZGmTPPyv7k_l6HzlEIpFXnXGZjcMhkyyuqSMtN_RTGJ-xg",
"token_type": "Bearer", "expires_in": 3600,
"refresh_token": "1/r5RRN6oRChjLtY5Y_T3lrqOy7n7QZJDQUVm8ZI1xGdoMEudVrK5jSpoR30zcRFq6",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUxMWQ1N2QxZmY0ODA0YjMxYzA1MWI3MWY2ZDVlNWExZmQyOTdjZjgifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwMTY5NDg0NDc0Mzg2Mjc2MzM0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiZW1haWwiOiJiaWxsZDE2MDBAZ21haWwuY29tIiwiYXRfaGFzaCI6ImxPdEkwQlJvdTBaNExQdFF1RThjQ3ciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaWF0IjoxNDMyMTQyMjIyLCJleHAiOjE0MzIxNDU4MjJ9.jtnP4Ffw2bPjfxRAEvHI8j88YBI4OJrw2BU7AQUCP2AUOKRC5pxwVn3vRomGTKiuMbnHqMyMiVSQZWTjAgjQrmaANxTEA68UMKh3dtu63hh4LHkGJly2hFcIKwbHxMWPDRO9nv8LxAUeCF5ccMgFNXhu-i-CeVtrMOsjCq6j5Qc"
}
Id_token состоит из трех частей, разделенных точкой .
, Первые две части кодируются в base64. Я игнорирую третью часть id_token. Нам нужно base64 декодировать оба. Обратите внимание, я использую Perl, чтобы избежать необходимости дополнять строки base64, т.е. Perl обрабатывает это для нас.
Первая часть, которую вы уже знаете, дает нам алгоритм, который нам нужно использовать.
perl -MMIME::Base64 -e 'print decode_base64("eyJhbGciOiJSUzI1NiIsImtpZCI6ImUxMWQ1N2QxZmY0ODA0YjMxYzA1MWI3MWY2ZDVlNWExZmQyOTdjZjgifQ")'
{
"alg":"RS256",
"kid":"e11d57d1ff4804b31c051b71f6d5e5a1fd297cf8"
}
Вторая часть дает это at_hash
значение.
perl -MMIME::Base64 -e 'print decode_base64("eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwMTY5NDg0NDc0Mzg2Mjc2MzM0IiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiZW1haWwiOiJiaWxsZDE2MDBAZ21haWwuY29tIiwiYXRfaGFzaCI6ImxPdEkwQlJvdTBaNExQdFF1RThjQ3ciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaWF0IjoxNDMyMTQyMjIyLCJleHAiOjE0MzIxNDU4MjJ9")'
{
"iss":"accounts.google.com",
........
"at_hash":"lOtI0BRou0Z4LPtQuE8cCw",
........
"exp":1432145822
}
Теперь мы знаем, что at_hash
значение в том, что мы можем использовать access_token, чтобы убедиться, что они одинаковы... Следующая Perl-программа делает это.
#!/usr/bin/env perl
use strict;
use warnings;
use MIME::Base64;
use Digest::SHA qw(sha256);
my $data = "ya29.eQGmYe6H3fP_d65AY0pOMCFikA0f4hzVZGmTPPyv7k_l6HzlEIpFXnXGZjcMhkyyuqSMtN_RTGJ-xg";
my $digest = sha256($data);
my $first_16_bytes = substr($digest,0,16);
print encode_base64($first_16_bytes);
Эта программа может быть запущена следующим образом
perl sha256.pl
lOtI0BRou0Z4LPtQuE8cCw==
Обратите внимание, мы получили at_hash
но почему они не одинаковы..., они на самом деле одинаковы, просто один из них не хватает отступов. =
знаки добавляются, пока не будет выполнено следующее.
(strlen($base64_string) % 4 == 0)
В нашем случае
strlen("lOtI0BRou0Z4LPtQuE8cCw") == 22
поэтому мы получили два ==
добавил к результату:). Причина, по которой они не входят в маркер, заключается в том, что люди, написавшие спецификацию, не верят, что передача ненужных байтов по сети - хорошая идея, если их можно добавить на другом конце.
Это точно описано в спецификации:
https://openid.net/specs/openid-connect-core-1_0.html
3.1.3.6. Идентификационный токен
at_hash ДОПОЛНИТЕЛЬНО. Хэш-значение токена доступа. Его значение - это кодировка base64url самой левой половины хеша октетов представления ASCII значения access_token, где используемый алгоритм хеширования - это алгоритм хеширования, используемый в параметре заголовка alg заголовка JOSE идентификатора токена. Например, если alg - RS256, хэшируйте значение access_token с помощью SHA-256, затем возьмите самые левые 128 бит и закодируйте их base64url. Значение at_hash является чувствительной к регистру строкой.
Я столкнулся с похожей проблемой при создании клиентских секретов.
Было полезно посмотреть на класс HashExtensions, который использует IdentityServer; в моем случае я не получал байты с кодировкой UTF8. Я подозреваю, что связанный с вами онлайн-инструмент использует другой подход для кодирования байтового массива в строки.
Для Perl приведенный выше код должен быть дополнен следующим образом:
#!/usr/bin/env perl
use strict;
use warnings;
use MIME::Base64;
use Digest::SHA;
my $access_token = "SOMETHING";
my $digest = Digest::SHA::sha256( $access_token );
my $first_16_bytes = substr( $digest, 0, 16 );
print MIME::Base64::encode_base64url( $first_16_bytes );
Тогда он действительно работает в соответствии со стандартами.
Убедитесь, что вы обновили свой модуль MIME::Base64 до последней версии.
Решение Голанга
func verifyAtHash(accessToken string, atHash string) bool {
h := sha256.New() // for RS256, ES256, PS256
h.Write([]byte(accessToken)) // hash documents that Write never return an error
sum := h.Sum(nil)[:h.Size()/2] // left-most 128 bits
atHashFromAccessToken := base64.RawURLEncoding.EncodeToString(sum)
return atHashFromAccessToken == atHash
}
более полное решение здесь с другим алгоритмом подписи (см.verifyHashClaim
):https://github.com/hashicorp/cap/blob/v0.3.1/oidc/id_token.go#L95