chunpu / shortcuts-to-master

shortcuts to master
0 stars 0 forks source link

你不知道的 base64 #9

Open chunpu opened 8 years ago

chunpu commented 8 years ago

本周在解码 5oKs5bSW 这个 base64 的时候发现竟然产生了乱码,一开始怀疑库有问题,结果在浏览器输入 atob('5oKs5bSW')

image

image

又在 bash 中试了一下 echo '5oKs5bSW' | base64 -d 却可以正常返回 悬崖

到底是什么原因呢?我们先了解一下 base64 的基本原理

btoa 和 atob 的意义

首先,为什么管 base64 的编码叫 btoa,而解码叫 atob

image

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 等

image

base64 最大的好处是二进制文件本来是完全不可读而且不可显示的,转成 base64 就成了纯文本,不仅可读可编辑,而且传输数据也方便

base64 是可逆的,在只可以传递纯文本的时候,通过 base64 我们就可以传递一切了

不过正因为如此,base64 肯定是不能用来加密的,因此 base64 的 encode 和 decode 应该称为编码解码,而不能称为加密和解密

虽然不能用来加密,但可以用来混淆,比如百度贴吧的种子链接,比如 dnQudHVtYmxyLmNvbS90dW1ibHJfbzQwZGE1NlJMVTF2NWVvdXBfNDgwLm1wNA==

image

base64 的字典表

有人说,感觉 base64 有几种字典表啊

我们先来看看标准的 base64 字典表

base64 使用 64 个 ASCII 字符image

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

[A-Za-z0-9+/] = 26 + 26 + 10 + 2 = 64

注意这64位是按顺序的,就是说 0 = 00000000 = A, 63 = 11111111 = /

URL Safe Base64

标准 base64 中的两个非字母数字 +/ 设计的特别坑爹,因为它们不管在 url 中还有文件系统中都属于特殊字符image

而剩下的那个 padding 字符 = 就更坑爹了,因为 = 直接就是 query string 中的 equal 字符

因此机智的人类又发明了 base64 url safe 版本,和标准版区别有三

也就是下面这个表

image

这个规则并不是一些语言库自己定的,而是被写进了 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 base64image

最后自己动手,实现了一个 JS 版的全功能 base64

https://github.com/chunpu/min-base64

为什么选择64而不是其他数字呢?

我们知道64是2的6次方,一连串二进制被 6bit 分组

那为什么不是 5bit 分组或者 7bit 分组呢?

7bit 分组需要 2^7 也就是128个字符,找不到这么多现成的字符,而 5bit 分组只需要32个字符,连大小写字符都没用完,太浪费了

image

6bit 分组造成的一个后果就是 base64 后的文本大小始终是大于等于源数据的 8/6,也就是说 base64 后的文本 至少比源文件大三分之一,因为虽然按 6bit 分组,但一个字符还是8bit。但要注意的是在 gzip 后这个大小差距会减少

base64 的补齐

base64 有一个 padding 字符 =

base64 按 6bit 分组,而 byte 按 8bit 分组,最小公倍数是24

因此不管把 6bit 组转成 8bit 组还是把 8bit 组转成 6bit 组,都需要先用 0 补成 24 的倍数

一次加密事例

  1. 源文件是字符串 "abc"
  2. 先转成二进制 [97, 98, 99]
  3. 对应的二进制就是 [01100001, 01100010, 01100011],取法 'a'.charCodeAt(0).toString(2)
  4. 正好24位不用补齐
  5. 重新按6位分割成新的数组 [011000, 010110, 001001, 100011]
  6. 对应十进制就是 [24, 22, 9, 35]
  7. 映射到 base64 表中就是 ['Y', 'W', 'J', 'j'],也就是 YWJj

但真的要把字符串拼出来然后再 split 吗?当然不行

我们必须用男人的方式来解决此问题,没错就是二进制!

image

  1. 011000 = 01100001, 右移两位就是, 也就是 97 >> 2 = 24
  2. 010110 = 01100001, 01100010,((97 & 0x03) << 4) | (98 >> 4) = 22
  3. 001001 = 01100010, 01100011,((98 & 0x0f) << 2) | (99 >> 6) = 9
  4. 100011 = 0110001199 & 0x3f = 35

相信大家看到这里已经点了上方的关闭,那就不往下写了,大家拜拜

本文首发自 小猿无知又可爱(naivemonkey)

image

chunpu commented 8 years ago

image