Проверка токена доступа с помощью 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

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