关于OAuth2.1 PKCE利用crypto的sha256哈希结果转换base64url计算结果不一致的问题(Flutter)

由 月琳 发布
  | 32 次浏览

前言

最近在写第三方flutter app并抓包分析某原生app登录逻辑的时候,遇到了使用OAuth2.1 PKCE授权码模式的登录方式,该模式下需要将code_verifier先进行sha256哈希后再进行base64(URL-Save) 编码成最终的code_challenge。

OAuth2.1 PKCE授权码模式详解:https://shanhy.blog.csdn.net/article/details/114080598

code_verifier转code_challenge的工具:https://tonyxu-io.github.io/pkce-generator/

问题详情

  1. 在对code_verifier进行sha256哈希+base64URL编码后,所得结果与预期的完全不一致;在进行初步对比排查后发现是在base64URL这一步骤出现的差异。
  2. 并且在使用各种线上工具对已经sha256哈希后的字符串进行base64URL编码,也与预期结果完全不一致。

核心要点(太长不看版)

编码不同的问题。sha256哈希后的结果数据是32个byte(字节编码),PKCE模式下的base64应该基于这32个byte进行编码;而将哈希后的结果转换成十六进制字符串后进行base64编码会不一样。

分析

在搜索科普了不少文章后才意识到问题所在。

进行数据观测的时候我分别打印出了以下三种数据格式:

// Flutter,使用了crypto插件
var codeVerifier = "Tl-HbpKwvp54QYiUeYOKrvE8jO9ZUX54L-6VwkgiWzU";
var sha256Result = sha256.convert(utf8.encode(codeVerifier));
print(sha256Result.bytes); // 1、sha256哈希后,打印字节编码
print(sha256Result.toString()); // 2、sha256哈希后,打印成字符串
print(utf8.encode(sha256Result.toString())); // 3、sha256哈希后,打印字符串的utf8编码
print(base64UrlEncode(utf8.encode(sha256Result.toString()))); // 4、打印base64Url编码结果(与正常情况不一致)

输出结果分别为:

  1. [93, 154, 247, 89, 229, 56, 246, 48, 54, 31, 68, 109, 191, 35, 46, 82, 100, 162, 83, 19, 141, 204, 139, 6, 104, 112, 146, 122, 62, 224, 232, 60]
  2. 5d9af759e538f630361f446dbf232e5264a253138dcc8b066870927a3ee0e83c
  3. [53, 100, 57, 97, 102, 55, 53, 57, 101, 53, 51, 56, 102, 54, 51, 48, 51, 54, 49, 102, 52, 52, 54, 100, 98, 102, 50, 51, 50, 101, 53, 50, 54, 52, 97, 50, 53, 51, 49, 51, 56, 100, 99, 99, 56, 98, 48, 54, 54, 56, 55, 48, 57, 50, 55, 97, 51, 101, 101, 48, 101, 56, 51, 99]
  4. NWQ5YWY3NTllNTM4ZjYzMDM2MWY0NDZkYmYyMzJlNTI2NGEyNTMxMzhkY2M4YjA2Njg3MDkyN2EzZWUwZTgzYw==

其实根据这个结果其实可发现,结果一开头的十进制字节编码93对应结果二开头的十六进制字符串5d,而5d又分别对应十进制UTF8/ASCII编码的53100(结果三开头)。而结果四与在线字符串转base64结果一样(都是不正确的)。正确结果应该是XZr3WeU49jA2H0RtvyMuUmSiUxONzIsGaHCSej7g6Dw=

查阅资料得知,sha256哈希后的结果数据是32个字节编码(例如结果一),而PKCE模式下的base64应该基于这32个byte进行编码。结果四之所以与正确结果完全不同,是因为将哈希后的结果转换成十六进制字符串后(例如结果二)又转换成utf8编码(例如结果三)进行base64URl编码,导致结果大不相同。线上的字符串与base64转换工具也是相似的原因导致的结果不一致。

解决方法(Flutter)

改动前的错误的flutter dart代码:

// 注:使用了插件包 crypto: ^3.0.1
String str = sha256.convert(utf8.encode(codeVerifier)).toString();
return base64UrlEncode(utf8.encode(str));

改正后:

var str = sha256.convert(utf8.encode(codeVerifier)).bytes;
return base64UrlEncode(str);

PS:(果然理论知识还是挺重要的啊,不然就不会犯这种错。)

额外参考资料:

ascii 和 byte以及UTF-8的转码规则(还有base64)

一文读懂SHA256算法原理及其实现


暂无评论

发表评论