使用at_hash验证访问令牌

时间:2016-03-22 11:01:18

标签: rsa identityserver3 oauth2

我试图针对at_hash验证访问令牌。令牌头像这样

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

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

由于

4 个答案:

答案 0 :(得分:5)

  

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值的方法。如果您想使用我使用的数据,您可以跳过谷歌部分,但如果您想在新数据上测试它,您可以在Google上获取...

从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 

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

答案 1 :(得分:3)

在规范中描述了完全

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

  

3.1.3.6。 ID令牌

     

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

答案 2 :(得分:2)

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

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

答案 3 :(得分: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模块升级到最新版本。