轉自 https://mp.weixin.qq.com/s/TcSNPY1a6z8kP76usH6dCA
Base32 與 Base64
Base32
是一個 binary-to-text encoding
schemes,顧名思義,就是將二進制數據轉換為編碼只有基礎 32 個字符的數據編碼方式,Base64
則是 64 個。注意編碼不等同于加密,網上有誤解 Base 編碼方式為加密方式,實際上標準 Base64 編碼解碼無需額外信息即完全可逆。
Base 編碼常見用途如下
如定義所言,binary to text
一些協議如 HTTP
, FTP (File Transfer Protocol)
[當指定發送文本時], SMTP (Simple Mail Transfer Protocol)
是 text-based protocol
,也就是只支持文本傳輸,不支持二進制傳輸。是的,http 上傳文件,圖片時使用的 multipart/form-data 也是需要轉成文本的。
所以附件如圖片,文件等(binary)就可以用 Base64 編碼為 text再傳輸。
將資源編碼為字符串
如 data URI scheme
定義了如下語法來識別網頁中的資源:
data:[<media type>][;base64],<data>
HTML 中可以在標簽中指定識別 Base64編碼 來展示資源,
<div>
<p>Taken from wikpedia</p>
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />
</div>
但因為 Base64 是每 3 個原始字符編碼成 4 個字符,不夠時補 =
(下文會詳述),因此編碼后的大小是有可能會比原文件大的,所以 html 用 Base64 來展示圖片而不是用具體的圖片好處大概就只有少建立一條 http 連接以及少一個 http 請求(在 HTTP 1.1 以下),這種辦法只有大量的小圖片才有優越性了。
統一轉成『合法』字符
為了避免出現不符合規則的字符,方便把含有不可見字符串的信息用可見字符串表示出來。比如 http 協議當中的 headers 頭部,必須進行 URLEncode 不然出現的等號可能使解析失敗,空格也會使 http 請求解析出現問題,比如請求行也就是 request 就是以空格來劃分的 POST /hi/you HTTP/1
,值得注意的是 Base32 的字符列表里有不合法字符 /。
還有避免原始信息經過百花齊開的路由,網關多次轉發,因有部分系統不支持此不可識別字符或將此作為控制符,將其轉義、丟棄等,造成信息丟失,所以如電子郵件里的附件也是用 base64 編碼的。
base64url
有 base64 編碼的變種 base64url
,將base64 編碼中的 +
換成 -
以及將 /
換成_
,甚至不需要往后面補=
了。這樣子在 url 中傳遞東西時,不再需要 URL encode,好處就是長度短了,以及好看了一點,畢竟 % 有點視覺污染(實際上,還可以直接將編碼后的東西存數據庫了,因為 base64 比 URLEncode 更通用了 )
Base64 的由來——參考 RFC
RFC 向來都不會說明設計的歷史由來,自然 base64 編碼也是一樣,我參考的 rfc4648
也只是說明了因為當時開發者們自己發明使用base 64并不規范,沒有統一的標準,因此定義了一份通用標準。
然后呢,Base64 就是自己選了 ASCII 子集(64 個字符)為標準字符集,當然這也是因為 64 是 2的 x 次方 (如 64 就是 2 的6次方),而1個 bit 分別有 0和 1 兩種狀態,6 個 bit也就是 2 的 6 次方=64 個狀態,剛好可以表示 64 個字符,因此 6 個 bit 就可以表達出 64 個字符了。就是下面定義的 64 個:
Table 1: The Base 64 Alphabet
Value Encoding Value Encoding Value Encoding Value Encoding
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 (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y
編碼定義
The encoding process represents 24-bit groups of input bits as output
strings of 4 encoded characters.
- 輸入:二進制(圖片,文件,字符串本質就是二進制)
- 輸出:編碼后的字符串
- 處理過程:處理輸入的二進制時,每 24 個 bit (3 個字節)作為一組,編碼輸出為 base64 處理后的 4 個標準字符集中的字符。
值得注意的是,網上的示例或說明中,都或多或少有以下偏頗之處:
- 輸入的例子可以是16 進制數字、二進制、一串數字等,很多文章舉的例子都是字符串;讓人忽略 binary to text 的 binary
- 是每 24 位(同樣需要注意不一定是 3 個 8 位的字符,3 個字節bytes才準確)為一組來處理,輸出 4 個編碼后的字符。強調這點是因為,24 位為一組,不夠的都需要補 =,如按其他人的文章說的 8 位 8 位的轉,根本不清楚要補多少 =
- 24 位轉成 4 個編碼后的字符(也就是 4*8=32位),所以編碼后的長度肯定會變大
- 綜上所述,RFC 原文才是最對的定義,有時細微的區別意味著理解有問題。下面會一一說明。
特殊處理
When fewer than 24 input
bits are available in an input group, bits with value zero are added
(on the right) to form an integral number of 6-bit groups.
Padding at the end of the data is performed using the '=' character.
- 每 24 位為一組來編碼輸入的 binary 時,如果最后的一組不足24 位,往后補 0直到 補足到 24
- 對于最后對于全為 0 的一組,補充
=
舉一些例子來說明一下:
Input data: 0x14fb9c03d97e
16進制: 1 4 f b 9 c | 0 3 d 9 7 e
2進制: 00010100 11111011 10011100 | 00000011 11011001 01111110
6位一組: 000101 001111 101110 011100 | 000000 111101 100101 111110
Decimal: 5 15 46 28 0 61 37 62
Output: F P u c A 9 l +
16 進制的 0x14fb9c03d97e
作為輸入,先轉成二進制,然后 2 進制的每 24 位 選出來編碼,上面例子就是:00010100 11111011 10011100
,然后 6 位一組的分開,得到 000101 001111 101110 011100
。
然后分別轉 10 進制,也就是 000101
變成 5,001111
變成 15等,再去 base64 定義的字符列表中找出此 10 進制對應的字符,以此類推,就是 base64 后的結果了。
上面例子是輸入剛好是有48 位, 2個 24 位,剛剛夠,不需要補 =
下面看看需要補 =
的例子:
Input data: 0x14fb9c03
Hex: 1 4 f b 9 c | 0 3
8-bit: 00010100 11111011 10011100 | 00000011 開始補 0 =》00000000 00000000
pad
6-bit: 000101 001111 101110 011100 | 000000 110000 000000 000000
Decimal: 5 15 46 28 0 48
pad with = =
Output: F P u c A w = =
注意上述輸入只有 32 位,第一個 24 位處理完后,還剩下 8 位,因此需要補16 個 0.
補完后,就是 48 位的輸入了,照樣每 24 位輸出 4 個編碼后的字符。
觀察后半部分,000000 110000 000000 000000
,第一個 000000 因為后面還有內容,所以10 進制為 0,因此編碼字符為 A,這個很正常;而 1100000 之后的兩個 6 位 0,都是純粹的填充(pading)了,因此并不用 A 而都用 = 代替掉,注意不用 A
Base64 decode
說完 encode,decode 就容易啦,無非就是逆過程。
一串 base64 后的字符串,根據每個字符在 base64 字符表里找到對應的 10 進制,然后轉成 2 進制,最后多余補足的 000000 去掉。
參考:
https://www.lucidchart.com/techblog/2017/10/23/base64-encoding-a-visual-explanation/
https://www.liaoxuefeng.com/wiki/1016959663602400/1017684507717184
https://www.zhangxinxu.com/wordpress/2018/08/js-base64-atob-btoa-encode-decode/
https://www.wikiwand.com/en/Binary-to-text_encoding
https://www.wikiwand.com/en/Data_URI_scheme
https://tools.ietf.org/html/rfc4648#page-3