使用 at_hash 验证访问令牌

Validating access token with at_hash

我正在尝试根据 at_hash 验证访问令牌。令牌头是这样的

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

如何从我的访问令牌获取 ID 令牌中的 Base64 编码 at_hash 声明值?有没有在线工具可以帮助我解决这个问题? SHA256 哈希计算器不是正确的工具吗?

谢谢

规范中完全描述了它:

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

3.1.3.6. ID Token

at_hash OPTIONAL. Access Token hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string.

我 运行 在生成客户端机密时遇到了一些类似的问题。

查看 IdentityServer 使用的 HashExtensions class 很有帮助;在我的例子中,我没有得到 UTF8 编码的字节。我怀疑您链接的在线工具采用不同的方法将字节数组编码为字符串。

Is SHA256 hash calculator not a correct tool for this?

它不起作用,因为您需要在其中一个步骤中使用二进制数据,并且几乎所有网络工具都需要某种文本作为输入并生成文本作为输出。在线工具不适用于此。我将编写一个工具,以便您了解它是如何完成的。

How do I get from my access token to the Base64 encoded at_hash claim value that is in the id token?

这是我的第一个 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 游乐场获取访问令牌

到这里

 https://developers.google.com/oauthplayground/

不要select任何东西,在页面底部附近有一个输入框。输入 openid 并点击 Authorize APIs,点击您要使用的 ID,然后点击 select allow。 Select 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 

所以我们在结果中添加了两个 == :)。它们不在令牌中的原因是因为编写规范的人不相信通过网络传递不必要的字节是一个好主意,如果它们可以添加到另一端。

对于 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 模块升级到最新版本。