13

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

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

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

谢谢

4

4 回答 4

8

SHA256 哈希计算器不是一个正确的工具吗?

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

如何从我的访问令牌中获取 id 令牌中的 Base64 编码的 at_hash 声明值?

这是我第一次 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值的方法。如果你想使用我用过的数据,你可以跳过谷歌部分,但如果你想在新数据上测试它,你可以在谷歌获得它......

从 Googles O2Auth Playground 获取访问令牌

到这里

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

不要选择任何东西,靠近页面底部有一个输入框。输入openid并点击Authorize APIs,单击您要使用的 id 并选择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 

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

于 2016-04-19T04:05:43.967 回答
3

它在规范中准确描述:

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

3.1.3.6。身份令牌

at_hash 可选。访问令牌哈希值。它的值是access_token值的ASCII表示的八位字节的hash的最左半部分的base64url编码,其中使用的hash算法是ID Token的JOSE Header的alg Header Parameter中使用的hash算法。例如,如果 alg 是 RS256,则使用 SHA-256 对 access_token 值进行散列,然后取最左边的 128 位并使用 base64url 对其进行编码。at_hash 值是区分大小写的字符串。

于 2016-03-22T18:16:38.590 回答
2

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

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

于 2016-04-06T17:36:14.883 回答
0

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

于 2019-02-01T16:41:02.360 回答