Base64编码原理及应用

2018-06-11

前言

Base64是一种用可见字符传输二进制数据的编码方式。本文介绍Base64编码的原理,以及在web中的应用。


简介

有些场合,我们需要储存和发送文件,又不方便直接使用二进制流。这时候,我们需要把二进制数据翻译成成字符串,写进代码或XML里。ASCII编码虽然也能做到,但因为含有无法显示和打印字符,不便于阅读和传输。(你可以用记事本打开一个jpg格式的图片,看看这堆乱码。)

我们期望编码的结果可控、可复制、可打印、方便阅读,最好只包含大小写字母、数字、一些简单的符号如+、/和=。于是有了Base64。

有些场合,非ASCII字符容易出现编码问题,也可以使用Base64。比如,你可以对中文进行编码:

//unicode:
你只要尝试过飞,日后走路时也会仰望天空
    
//base64:
5L2g5Y+q6KaB5bCd6K+V6L+H6aOe77yM5pel5ZCO6LWw6Lev5pe25Lmf5Lya5Luw5pyb5aSp56m6

这种编码方式并不是为了安全,而是为了显示。因此,编码结果是唯一的,也是可逆的。


编码原理

编码的时候,取3字节的数据,放入一个24位的缓冲区,先来的字节占高位。这24位数据,每次取6位,按照下表转化为字符。

value char value char value char value char
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 +
15 P 31 f 47 v 63 /


于是,缓冲区这3个字节数据,编码成了4位字符。举个例子,对笔者的名字进行编码如下:

文本 J A Y
ASCII编码 74 65 89
二进制位 0 1 0 0 1 0 1 0 0 1 0 0 0 0 0 1 0 1 0 1 1 0 0 1
Base64索引 18 36 5 25
Base64编码 S k F Z


最终编码为:SkFZ。所有数据按照以上方式,3个字节为一组进行编码转换。如果要编码的字节数不能被3整除,则余下的位数补0,并在编码结果最后附加等号“=”来表示。剩余两个八位字节时附上两个等号,剩余一个八位字节时附上一个等号,具体如下表:

文本 A
二进制位 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Base64编码 Q Q = =
文本 B C
二进制位 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0
Base64编码 Q
k M =


Base64使用64个字符,因此每个字符最多表示6位数据(26=64)。每个字符在储存和传输时占用一个字节(8位),因此,从二进制编码为Base64,会增加至少三分之一的数据长度。


Base64的应用

DataUrl:

DataUrl是一种将文件通过Base64编码写进网页源代码的技术,也是Base64最广泛的应用之一。你可以查看下面两张图的源代码,右边这张的src是正常的,而左边的src是以“data:image/jpeg;base64”开头的一段编码后的数据。

Base64图片示例 普通图片示例

网站上使用图标、图片、字体文件,文件小而数量多,每个文件都占用一次完整http请求,拖累了整个网页加载的速度。这时候我们希望能直接在页面中嵌入这些资源,这就是DataUrl。除了上面例子的,在<img>标签的src中使用,也可以在CSS的background-image、@font-face的src使用,IE8以上都兼容。


不过,DataUrl也不能滥用。虽然减少了一次请求,但Base64编码的原理使得图片变得更大。同样参考上面的图片,原图大小14kb,编码之后19kb。而且,浏览器并不会对DataUrl传输的文件进行缓存,意味着每次加载页面都得重新传输。


链接:

举一个老司机最熟悉的,“迅雷下载专用地址”的例子。事实上,所谓的专用链接,不过是对真实下载地址前后分别添加AA和ZZ,然后使用Base64编码而来。举个例子,最新版本的Node.js的下载链接:

//1.原始链接:
https://nodejs.org/dist/v8.11.2/node-v8.11.2-x64.msi

//2.前后加AA和ZZ
AAhttps://nodejs.org/dist/v8.11.2/node-v8.11.2-x64.msi

//3.Base64编码
QUFodHRwczovL25vZGVqcy5vcmcvZGlzdC92OC4xMS4yL25vZGUtdjguMTEuMi14NjQubXNpWlo=

//4.加上协议,得到迅雷下载链接
thunder://QUFodHRwczovL25vZGVqcy5vcmcvZGlzdC92OC4xMS4yL25vZGUtdjguMTEuMi14NjQubXNpWlo=

你可以使用上述编码后的链接下载到所需的文件。当然,以后在网上看到别人发的迅雷专用链接,你也可以解码得到原始下载链接。

JWT:

Base64编码的cookie或token,典型的代表就是JWT了,可以参考我另一篇博客《Json Web Token》。


其他:

MIME格式的邮件、微软的MHT、XML需要储存图片或文件的场景。这些格式都只能支持字符串,而又有使用图片和文件的需求,于是使用Base64进行编码。


编码和解码

Node.js可以使用Buffer读取二进制,当然也可以进行Base64编码和解码:

let b = new Buffer('JAY');
    let s = b.toString('base64');
    // SkFZ
    
    let b = new Buffer('SkFZ', 'base64')
    let s = b.toString();
    // JAY


PHP自带编码解码函数:

echo base64_encode('JAY');
    //SkFZ
    
    echo base64_decode('SkFZ');
    //JAY


Python也有内置的方法:

>>> import base64
    >>> base64.b64encode('JAY')
    'SkFZ'
    >>> base64.b64decode('SkFZ')
    'JAY'


javascript

javascript也有原生的atob()和btoa():

let encoded = btoa('JAY'); // 'SkFZ'
    style="margin-top: 0px; margin-bottom: 10px; padding: 9.5px; border-radius: 4px; background-color: rgb(239, 239, 239); box-sizing: border-box; font-stretch: normal; font-size: 13px; line-height: 1.42857; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; overflow: auto; color: rgb(51, 51, 51); word-break: break-all; overflow-wrap: break-word; border: 1px solid rgb(204, 204, 204);">let encoded = btoa('JAY'); // 'SkFZ'
    let decoded = atob('SkFZ'); // 'JAY'

这两个函数只支持ASCII,并且不兼容IE6-9。虽然网上也有很多第三方库,但这里推荐使用polyfill兼容,对中文先encodeURIComponent编码后再btoa()。



参考文献

  1. https://en.wikipedia.org/wiki/Base64




本文未经许可禁止转载,如需转载关注微信公众号【工程师加一】并留言。