Проверка токена доступа с помощью at_hash

Я пытаюсь проверить токены доступа против at_hash. Заголовок токена выглядит следующим образом

{ "typ": "JWT", "alg": "RS256", "x5t": "MclQ7Vmu-1e5_rvdSfBShLe82eY", "kid": "MclQ7Vmu-1e5_rvdSfBShLe82eY" }

Как мне получить из токена доступа к значению претензии base64, закодированному at_hash, которое находится в токене id? Есть ли онлайн-инструмент, который мог бы помочь мне в этом? Является ли SHA256 хеш-калькулятором не правильным инструментом для этого?

Спасибо

Ответ 1

Является ли калькулятор хеш файлов SHA256 неправильным инструментом для этого?

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

Как мне получить из токена доступа к значению претензии base64, закодированному at_hash, которое находится в токене id?

Это моя первая итерация программы на С# 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" : "[email protected]"
}

at_hash value == lOtI0BRou0Z4LPtQuE8cCw

Вот как проверить значение at_hash. Вы можете пропустить часть google, если хотите использовать данные, которые я использовал, но если вы хотите протестировать их на новые данные, вы можете получить их в Google...

Получить токен доступа из Googles Игровая площадка O2Auth

Перейдите сюда

 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, чтобы избежать необходимости использовать базовые строки, то есть 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 

поэтому мы получили два ==, добавленных к результату:). Причина, по которой они не в токене, состоит в том, что люди, написавшие спецификацию, не считают передачу лишних байтов по сети, это хорошая идея, если их можно добавить на другом конце.

Ответ 2

В спецификации описывается точно:

https://openid.net/specs/openid-connect-core-1_0.html

3.1.3.6. Идентификатор ID

at_hash НЕОБЯЗАТЕЛЬНЫЙ. Хешировать токен доступа. Его значением является base64url-кодирование самой левой половины хэша октетов представления ASCII значения access_token, где используемым алгоритмом хэша является хэш-алгоритм, используемый в параметре заголовка alg заголовка JOSE ID Token. Например, если alg RS256, hash значение access_token с SHA-256, то возьмите самые левые 128 бит и base64url закодируйте их. Значение at_hash является строкой, чувствительной к регистру.

Ответ 3

У меня возникла аналогичная проблема при создании секретов клиентов.

Глядя на класс HashExtensions, который использовал IdentityServer, был полезен; в моем случае я не получал байты с кодировкой UTF8. Я подозреваю, что связанный онлайн-инструмент использует другой подход к кодированию массива байтов в строки.

Ответ 4

Для 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 до последней версии.