Open chunpu opened 8 years ago
本周在解码 5oKs5bSW 这个 base64 的时候发现竟然产生了乱码,一开始怀疑库有问题,结果在浏览器输入 atob('5oKs5bSW')
5oKs5bSW
atob('5oKs5bSW')
又在 bash 中试了一下 echo '5oKs5bSW' | base64 -d 却可以正常返回 悬崖
echo '5oKs5bSW' | base64 -d
悬崖
到底是什么原因呢?我们先了解一下 base64 的基本原理
首先,为什么管 base64 的编码叫 btoa,而解码叫 atob
btoa = binary to ASCII = encode
atob = ASCII to binary = decode
C语言中有一个函数叫 atoi,意思是 convert a string to an integer,也就是 "10" => 10
convert a string to an integer
"10" => 10
i 是 integer 很好理解,那为什么 a 是 string 呢? a 其实是 ASCII 的缩写,ASCII 也是一种编码
字符串为什么还有编码呢?因为电脑只认识二进制0和1,string 和 二进制的对应关系就是编码,比如 01100001 对应字符 'a' 就是由 ASCII 编码规定的
理解了 atoi 再理解 atob 就很简单了,a 是 ASCII,也就是 string,而 b 就是 binary,atob 就是 convert ASCII to binary
convert ASCII to binary
虽然有时候我们用字符串作为 btoa 的参数,但 base64 大部分是用在二进制文件上的
任何数据都能被 base64,因为 base64 接受的参数是 binary,最常见的有 img, mp3, gzip 等
base64 最大的好处是二进制文件本来是完全不可读而且不可显示的,转成 base64 就成了纯文本,不仅可读可编辑,而且传输数据也方便
base64 是可逆的,在只可以传递纯文本的时候,通过 base64 我们就可以传递一切了
不过正因为如此,base64 肯定是不能用来加密的,因此 base64 的 encode 和 decode 应该称为编码和解码,而不能称为加密和解密
虽然不能用来加密,但可以用来混淆,比如百度贴吧的种子链接,比如 dnQudHVtYmxyLmNvbS90dW1ibHJfbzQwZGE1NlJMVTF2NWVvdXBfNDgwLm1wNA==
dnQudHVtYmxyLmNvbS90dW1ibHJfbzQwZGE1NlJMVTF2NWVvdXBfNDgwLm1wNA==
有人说,感觉 base64 有几种字典表啊
我们先来看看标准的 base64 字典表
base64 使用 64 个 ASCII 字符
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
[A-Za-z0-9+/] = 26 + 26 + 10 + 2 = 64
注意这64位是按顺序的,就是说 0 = 00000000 = A, 63 = 11111111 = /
0 = 00000000 = A
63 = 11111111 = /
标准 base64 中的两个非字母数字 + 和 / 设计的特别坑爹,因为它们不管在 url 中还有文件系统中都属于特殊字符
+
/
而剩下的那个 padding 字符 = 就更坑爹了,因为 = 直接就是 query string 中的 equal 字符
=
因此机智的人类又发明了 base64 url safe 版本,和标准版区别有三
-
_
也就是下面这个表
这个规则并不是一些语言库自己定的,而是被写进了 base64 的标准 RFC 4648 https://www.ietf.org/rfc/rfc4648.txt,因此大可放心的用
python 和 golang 中都有 url safe base64
golang https://golang.org/pkg/encoding/base64/#URLEncoding
python https://docs.python.org/3/library/base64.html#base64.urlsafe%5Fb64encode
那 JS 怎么办呢?在 Github 上搜索了一番,尝试了不下5个库,结果不是不支持 utf8 就是不支持 url safe base64
最后自己动手,实现了一个 JS 版的全功能 base64
https://github.com/chunpu/min-base64
我们知道64是2的6次方,一连串二进制被 6bit 分组
那为什么不是 5bit 分组或者 7bit 分组呢?
7bit 分组需要 2^7 也就是128个字符,找不到这么多现成的字符,而 5bit 分组只需要32个字符,连大小写字符都没用完,太浪费了
6bit 分组造成的一个后果就是 base64 后的文本大小始终是大于等于源数据的 8/6,也就是说 base64 后的文本 至少比源文件大三分之一,因为虽然按 6bit 分组,但一个字符还是8bit。但要注意的是在 gzip 后这个大小差距会减少
base64 有一个 padding 字符 =
base64 按 6bit 分组,而 byte 按 8bit 分组,最小公倍数是24
因此不管把 6bit 组转成 8bit 组还是把 8bit 组转成 6bit 组,都需要先用 0 补成 24 的倍数
"abc"
[97, 98, 99]
[01100001, 01100010, 01100011]
'a'.charCodeAt(0).toString(2)
[011000, 010110, 001001, 100011]
[24, 22, 9, 35]
['Y', 'W', 'J', 'j']
YWJj
但真的要把字符串拼出来然后再 split 吗?当然不行
我们必须用男人的方式来解决此问题,没错就是二进制!
97 >> 2 = 24
((97 & 0x03) << 4) | (98 >> 4) = 22
((98 & 0x0f) << 2) | (99 >> 6) = 9
99 & 0x3f = 35
相信大家看到这里已经点了上方的关闭,那就不往下写了,大家拜拜
本文首发自 小猿无知又可爱(naivemonkey)
本周在解码
5oKs5bSW
这个 base64 的时候发现竟然产生了乱码,一开始怀疑库有问题,结果在浏览器输入atob('5oKs5bSW')
又在 bash 中试了一下
echo '5oKs5bSW' | base64 -d
却可以正常返回悬崖
到底是什么原因呢?我们先了解一下 base64 的基本原理
btoa 和 atob 的意义
首先,为什么管 base64 的编码叫 btoa,而解码叫 atob
btoa = binary to ASCII = encode
atob = ASCII to binary = decode
C语言中有一个函数叫 atoi,意思是
convert a string to an integer
,也就是"10" => 10
i 是 integer 很好理解,那为什么 a 是 string 呢? a 其实是 ASCII 的缩写,ASCII 也是一种编码
字符串为什么还有编码呢?因为电脑只认识二进制0和1,string 和 二进制的对应关系就是编码,比如 01100001 对应字符 'a' 就是由 ASCII 编码规定的
理解了 atoi 再理解 atob 就很简单了,a 是 ASCII,也就是 string,而 b 就是 binary,atob 就是
convert ASCII to binary
base64 的作用
虽然有时候我们用字符串作为 btoa 的参数,但 base64 大部分是用在二进制文件上的
任何数据都能被 base64,因为 base64 接受的参数是 binary,最常见的有 img, mp3, gzip 等
base64 最大的好处是二进制文件本来是完全不可读而且不可显示的,转成 base64 就成了纯文本,不仅可读可编辑,而且传输数据也方便
base64 是可逆的,在只可以传递纯文本的时候,通过 base64 我们就可以传递一切了
不过正因为如此,base64 肯定是不能用来加密的,因此 base64 的 encode 和 decode 应该称为编码和解码,而不能称为加密和解密
虽然不能用来加密,但可以用来混淆,比如百度贴吧的种子链接,比如
dnQudHVtYmxyLmNvbS90dW1ibHJfbzQwZGE1NlJMVTF2NWVvdXBfNDgwLm1wNA==
base64 的字典表
有人说,感觉 base64 有几种字典表啊
我们先来看看标准的 base64 字典表
base64 使用 64 个 ASCII 字符
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
[A-Za-z0-9+/] = 26 + 26 + 10 + 2 = 64
注意这64位是按顺序的,就是说
0 = 00000000 = A
,63 = 11111111 = /
URL Safe Base64
标准 base64 中的两个非字母数字
+
和/
设计的特别坑爹,因为它们不管在 url 中还有文件系统中都属于特殊字符而剩下的那个 padding 字符
=
就更坑爹了,因为=
直接就是 query string 中的 equal 字符因此机智的人类又发明了 base64 url safe 版本,和标准版区别有三
+
被-
代替,加号对应减号/
被_
代替=
这个 padding 字符也就是下面这个表
这个规则并不是一些语言库自己定的,而是被写进了 base64 的标准 RFC 4648 https://www.ietf.org/rfc/rfc4648.txt,因此大可放心的用
python 和 golang 中都有 url safe base64
golang https://golang.org/pkg/encoding/base64/#URLEncoding
python https://docs.python.org/3/library/base64.html#base64.urlsafe%5Fb64encode
那 JS 怎么办呢?在 Github 上搜索了一番,尝试了不下5个库,结果不是不支持 utf8 就是不支持 url safe base64
最后自己动手,实现了一个 JS 版的全功能 base64
https://github.com/chunpu/min-base64
为什么选择64而不是其他数字呢?
我们知道64是2的6次方,一连串二进制被 6bit 分组
那为什么不是 5bit 分组或者 7bit 分组呢?
7bit 分组需要 2^7 也就是128个字符,找不到这么多现成的字符,而 5bit 分组只需要32个字符,连大小写字符都没用完,太浪费了
6bit 分组造成的一个后果就是 base64 后的文本大小始终是大于等于源数据的 8/6,也就是说 base64 后的文本 至少比源文件大三分之一,因为虽然按 6bit 分组,但一个字符还是8bit。但要注意的是在 gzip 后这个大小差距会减少
base64 的补齐
base64 有一个 padding 字符
=
base64 按 6bit 分组,而 byte 按 8bit 分组,最小公倍数是24
因此不管把 6bit 组转成 8bit 组还是把 8bit 组转成 6bit 组,都需要先用 0 补成 24 的倍数
一次加密事例
"abc"
[97, 98, 99]
[01100001, 01100010, 01100011]
,取法'a'.charCodeAt(0).toString(2)
[011000, 010110, 001001, 100011]
[24, 22, 9, 35]
['Y', 'W', 'J', 'j']
,也就是YWJj
但真的要把字符串拼出来然后再 split 吗?当然不行
我们必须用男人的方式来解决此问题,没错就是二进制!
97 >> 2 = 24
((97 & 0x03) << 4) | (98 >> 4) = 22
((98 & 0x0f) << 2) | (99 >> 6) = 9
99 & 0x3f = 35
相信大家看到这里已经点了上方的关闭,那就不往下写了,大家拜拜
本文首发自 小猿无知又可爱(naivemonkey)