Base 64 编码
Base64 是一种用 64 个可打印字符来表示任意二进制数据的二进制编码方法。可以用来处理和传输任意二进制数据。
Base64 最初是用在电子邮件中,为了满足电子邮件中不能直接使用非 ASCII 码字符的规定,用来传输二进制文件的。除此之外,它还可将任意不可打印的二进制数据,转化为可打印的文本编码,使用文本软件进行编辑(二进制流中有很多都是无法显示和打印的,比如 ASCII 中的控制字符,二进制文件 jpg、pad、exe 等,如果用记事本打开这些文件,会看到一堆乱码)。
原理
Base64 编码将一个 8 位字节序列拆散为 6 位的片段,并为每个 6 位的片段分配一个字符,这个字符是 Base64 字母表中的 64 个字符之一。这 64 个输出字符都是很常见的 ASCII 字符,包括大小写字母、数字、+ 和 /,还使用了特殊字符 =。
Base64 编码表 (The Base64 Alphabet):
索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 |
---|---|---|---|---|---|---|---|
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | ||
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
还可以自己定义 64 个字符的排列顺序,这样就可以自定义 Base64 编码,不过,通常情况下没有必要。
注意:由于 Base64 编码用了 8 位字符来表示信息中的 6 个位,所以 Base64 编码字符串大约比原始值增加了 33%,(8 - 6) / 6 = 1/3,选择 Base64 编码时要考虑一下文件的 size。另外,Base64 是一种通过查表的数据编码方法,目的是让数据符合传输协议的要求,不能将其误用于数据加密或数据校验,即使使用自定义的编码表也不行。
编码实现
下面是一个简单的 Base64 编码实例。在这里,三个字符组成的输入值 “Ow!” 是 Base64 编码的,得到的是 4 个字符的 Base64 编码值 “T3ch”。它是按以下方式工作的。
8 位字符 | O | w | ! |
---|---|---|---|
8 位值(十六进制) | $4F | $77 | $21 |
8 位值(二进制) | 01001111 | 01110111 | 00100001 |
对上面 8 位值(二进制)进行拆分 | - | - | - | - |
---|---|---|---|---|
6 位值(二进制) | 010011 | 110111 | 011100 | 100001 |
6 位值(十进制) | 19 | 55 | 28 | 33 |
Base64 字符 | T | 3 | c | h |
1 | * 字符串 "Ow!" 被拆分成 3 个 8 位的字节 (0x4F、0x77、0x21); |
- 用 JavaScript 描述
1 | var str = 'Ow!'; |
- 前提 – 确定具体的编码方案
需要注意的是,ASCII 码 128 位后的字符,各个编码方案不兼容,比如汉字有 gb2312、utf-8、gbk 等,每一种编码的 Base64 对应值都不一样,是用 Base64 进行编码前,需要确定编码方案。下面以’严’的 UTF-8 为例:
“严”的 UTF-8 编码为 E4B8A5,写成二进制就是三字节的 “11100100 10111000 10100101”。将这个 24 位的二进制字符串,转换成四组一共 32 位的二进制值”111001 001011 100010 100101”,相应的十进制数为 57、11、34、37,它们对应的 Base64 值就为5、L、i、l。
1 | // NodeJS(btoa 不支持中文) |
填充
Base64 编码收到一个 8 位字节序列,将这个二进制序列流划分成 6 位的块。二进制序列有时不能正好平均地分为 6 位的块,在这种情况下,就在序列末尾填充零位,使二进制序列的长度成为 24 的倍数(6 和 8 的最小公倍数)。
对已填充的二进制进行编码时,任何完全填充(不包括原始数据中的位)的 6 位组都有特殊的第 65 个符号 “=” 表示。如果 6 位组是部分填充的,就将填充位设置为 0。
下面是一个填充实例:
输入数据 | 二进制序列(填充位以 x 表示) | 已编码数据 |
---|---|---|
a:a | 011000 010011 101001 100001 | YTph |
a:aa | 011000 010011 101001 100001 011000 01xxxx xxxxxx xxxxxx | YTphYQ== |
a:aaa | 011000 010011 101001 100001 011000 010110 0001xx xxxxxx | YTphYWE= |
a:aaaa | 011000 010011 101001 100001 011000 010110 000101 1000001 | YTphYWFh |
初始输入字符串为 “a:a” 为 3 个字节(24 位)。24 是 6 和 8 的倍数,因此按照上面给出的例子计算。无需填充就会得到 Base64 编码为 “YTph”。
然而,再增加一个字符,输入字符串变为 “a:aa”,转换为二进制就会有 32 位长。而 6 和 8 的下一个公倍数为 48,因此要添加 16 位的填充码。填充的前 4 位是与数据位混合在一起的。得到的 6 位组 01xxxx,会被当作 010000、十进制中的 16,或者 Base64 编码的 Q 来处理。剩下的两个 6 位组都是填充码,用 = 来表示。
‘O’ 的填充实例:
1 | 'O'.charCodeAt().toString(2); |
浏览器中 Base64 编码生成
字符的编码
Web API 中 atob
和 btoa
用来对字符进行 Base64 编码和解码。btoa
将二进制的字节数据(binary bytes)编码成 ASCII 字符串,atob
解码通过 Base64 编码的字符串数据。
1 | btoa('a:a'); // YTph |
注:btoa
和 atob
分别是 Binary to ASCII 和 ASCII to binary 的缩写。b 不是 Base64 的缩写,计算机中的所有数据的本质都是二进制,我们所看到的文字、符号、图片、语音视频…,都是二进制数据在具体编码(字符编码、图像编码…)下的表现。
IE9 不支持 atob
和 btoa
,可手动实现 Base64 编解码函数:js-base64、base64。
- 编码 Unicode 字符
btoa
仅仅支持 Latin1 范围的字符(其编码范围是 0x00-0xFF
,0x00-0x7F
之间完全和 ASCII 一致,0x80-0x9F
之间是控制字符,0xA0-0xFF
之间是文字符号),不能编码 Unicode 字符,对 Unicode 字符串进行编码都会触发字符越界的异常。
1 | var str = 'Hello, 中国!'; |
原因是这个函数将每个字符视为二进制数据的字节,而不管实际组成字符的字节数是多少,所以如果任何字符的码位超出 0x00 ~ 0xFF
范围,则会引发 InvalidCharacterError 异常。这只是 btoa
这个方法的局限,并不是 Base64 的局限,Base64 可以编码所有的二进制数据。
解决办法是先把 Unicode 字符串转换为 UTF-8 编码。
1 | btoa(encodeURIComponent('Hello, 中国!')); // SGVsbG8lMkMlMjAlRTQlQjglQUQlRTUlOUIlQkQlRUYlQkMlODE= |
文件的编码
dataURL 是 data 类型的 URL,指含有 Base64 数据的 URL,用于在 URL 中使用二进制数据,在 RFC2397 中提出,完整的格式为:
1 | data:文件类型;编码方式,编码后的文件内容 |
比如,文本和图片的 dataURL 表示:
1 | data:text/plain;charset=UTF-8;base64,5L2g5aW977yM5Lit5paH77yB |
dataURL 在 img src
、background url
、link href
、script src
… 中使用。
- dataURL 生成
Web API 中的 FileReader.readAsDataURL
可以将一个文件转为 dataURL,图片还可以使用 canvas 的 canvas.toDataURL
生成。将一个 dataURL 转为 Blob
或者 File
,dataURLtoBlob、dataURLtoFile。
URL Safe
由于标准的 Base64 编码可能会出现 +
、/
、=
字符,这在 URL、Cookie 中不能直接作为参数,会造成歧义,所以又有一种 “URL Safe” 的 Base64 编码,其实就是把字符 +
和 /
分别变成 -
和 _
,把 =
去掉。
1 | base64.b64encode('i\xb7\x1d\xfb\xef\xff') # abcd++// |
Base64 的应用
网络传输协议中
Base64 的用途之一就是便于用文本或二进制文件数据的传送。 在网络传输中,Base64 一般适合以下场合的使用:
1 | * 传输信道只支持 ASCII 字符,不方便传输二进制流的场合; |
- SMTP
Base64 最早就是用来邮件传输协议(SMTP)中的,是作为 MIME 多媒体电子邮件标准的一部分开发的,原因是邮件传输协议是一个文本协议,只支持 ASCII 字符(纯文本,可打印)传输,如果要传输二进制文件(比如邮件附件中的图像、声音等)和非 ASCII 字符,就需要用 Base64 将二进制文件内容和非 ASCII 字符编码为只包含 ASCII 字符的内容,并指定 MIME(多用途互联网邮件扩展类型)(详情参考 HTTP 里面相关说明)。
例如,正文为空,带一个名为 hello.txt 的附件,内容为您好!世界!
。导出邮件源码,其关键部分如下所示:
1 | MIME-Version: 1.0 # 表示当前使用MIME标准1.0版本 |
不过,MIME 使用的不是标准 Base64 编码。
- HTTP
HTTP 是超文本传输协议,可以传输纯文本(底层也是二进制流),也可以直接传输二进制数据流。在传输纯文本的时候,由于有些数据格式不符合协议本身的规范,这时候就需要对这些数据进行编码处理成安全格式,从而可以合法地作为首部字段和正文的值。
1 | 特殊字符:=、空格、:、+、/、换行符...; |
当然,在 HTTP 中,URI 编码一般都会用配套的 URIencode,而不使用 Base64,除非需要编码二进制文件为 DataURL。对于 URL 的编码,请看相关章节。
注:HTTP 也支持 MIME,请查看相关章节。
dataURL
- img src、background url、link href、script src、图片预览和上传
注:图片预览和上传这个需求用 Blob 更合理,Blob 通过 URL.createObjectURL 也能预览。