关于base64的urlsafe问题
最近php在和java做通信的时候,java返回了一个字符串如下:
eyJleHAiOjE1NTI2Mzc2MTgsInVzZXIiOnsidWlkIjoxNDgzNzQzLCJhY0lkIjozMTMyMTU5MTIwNjg4NDM5MCwibmFtZSI6Iui_mzEifSwiaWF0IjoxNTUwMDQ1NjE4fQ
php提示解析失败,然后线上的解析工具都解析不出来,按照java的说法,是先需要一次urldecode之后才可
然后尝试urldecode,但是在php中失败,在线的urldecode解析失败。
相当费解,但是因为这是一个jwt串的payload部分,进而使用jwt.io的在线解析工具尝试,成功.....
翻看jwt.io官方提供的php demo,看到如下代码:
public static function urlsafeB64Decode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
进而查阅文档,解释如下:
原因之1:
Base64编码要求把3个8位字节(3X8=24)转化为4个6位的字节(4X6=24),之后在6位的前面补两个0,形成8位一个字节的形式。 如果剩下的字符不足3个字节,则用0填充,输出字符使用'=',因此编码后输出的文本末尾可能会出现1或2个'='。
所以有了第一段代码:
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
但是在很多文章里说,不要等号也不邮箱。测试了,确实如此,但是为什么java返回的值缺了几个等号,就不知道了。
原因之2:
由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+和/分别变成-和_
然后翻阅php手册的base64函数,也有相关的介绍:
Base64 for URL parameters/filenames, that adhere to RFC 4648. Defaults
to dropping the padding on encode since it's not required for
decoding, and keeps the URL free of % encodings.<?php function base64url_encode($data, $pad = null) {
$data = str_replace(array('+', '/'), array('-', '_'), base64_encode($data)); if (!$pad) { $data = rtrim($data, '='); } return $data; } function base64url_decode($data) { return base64_decode(str_replace(array('-', '_'), array('+', '/'), $data)); }
所以说我上面的串中有下划线,自然解析不出来了。就有了jwt的demo中的
base64_decode(strtr($input, '-_', '+/'));
这两个问题导致了和java之间的一些争议,java反复告诉我,因为他们用了urlencode,所以只需要urldecode就可以了。但是php中始终失败。
这里面其实是java的一个误区把,他们使用了一个叫Base64.GetUrlDecode().decode()的方法进行解密,相应的方法进行加密。然后告诉我直接使用urldecode就可以。但是实际上Base64.GetUrlDecode这里的urldecode只是一个base64的“url safe”的加解密方式,并不是我们通常意义上的urldecode和urlencode,我猜测这个Base64.GetUrlDecoded的底层应该也只是一个字符串的替换。
终上所述。
补齐等号,替换掉横杠下划线,这个问题得到解决。
很多时候因为不同语言对一些基础的加解密或者编码的处理还是有一些不一样的,一旦没理解清楚,容易踩坑。