参考 RFC4648
以上的 RFC 描述了几种常用的编码方案:Base64,Base32 和 Base16。数据的 Base 编码用于多种存储或者传输场景中,例如图片的传输,其中以 Base64 尤为常见。
Base64 编码
Base64 编码使用了 US-ASCII 的一个子集 —— 65 个字符,将每 6 位数据都表示成一个可打印字符,额外的第 65 个字符=
表示一种特殊的处理。编码的过程 —— 用 4 个作为输出的编码字符表示 1 个 24 位输入组。1 个 24 位输入组由 3 个连续的 8 位输入组组成,这 24 位的输入组又被当作 4 个 6 位的输出组(每个输出位组都被翻译成 1 个单独的 Base64 编码字符)。
每 1 个 6 位输出组都是 64 个可打印字符的索引值。例如000000(值 0)
表示A
。Base64 编码字符如下表所示:
字符 | 值 | 字符 | 值 | 字符 | 值 | 字符 | 值 |
---|---|---|---|---|---|---|---|
A | 0 | Q | 16 | g | 32 | w | 48 |
B | 1 | R | 17 | h | 33 | x | 49 |
C | 2 | S | 18 | i | 34 | y | 50 |
D | 3 | T | 19 | j | 35 | z | 51 |
E | 4 | U | 20 | k | 36 | 0 | 52 |
F | 5 | V | 21 | l | 37 | 1 | 53 |
G | 6 | W | 22 | m | 38 | 2 | 54 |
H | 7 | X | 23 | n | 39 | 3 | 55 |
I | 8 | Y | 24 | o | 40 | 4 | 56 |
J | 9 | Z | 25 | p | 41 | 5 | 57 |
K | 10 | a | 26 | q | 42 | 6 | 58 |
L | 11 | b | 27 | r | 43 | 7 | 59 |
M | 12 | c | 28 | s | 44 | 8 | 60 |
N | 13 | d | 29 | t | 45 | 9 | 61 |
O | 14 | e | 30 | u | 46 | + | 62 |
P | 15 | f | 31 | v | 47 | / | 63 |
如果要输入数据剩下的位数少于 24,就会进行特殊的处理(位0
会被添加到数据的右边以使位数是 6 位组的整数倍),并在数据的末端使用=
字符填充。
- 输入数据是 24 位的整数倍,编码的结果就是 4 个字符的整数倍,不会存在填充的
=
字符。 - 输入数据最后剩下 8 位,编码后会在最后填充 2 个
=
字符(注:将用 0 填充后的数据的前面 12 位所表示的字符写入之后再写入 2 个=
字符)。 - 输入数据最后剩下 16 位,编码后会在最后填充 1 个
=
字符(注:将用 0 填充后的数据的前面 18 位所表示的字符写入之后再写入 1 个=
字符)。
URL 和文件名安全的 Base64 编码
一种可选的建议就是使用~
作为第 63 个字符。而~
字符在某些文件系统中有特殊含义。剩下的非保留的 URI 字符就是.
,但某些文件系统不允许在文件名在有多个.
,因此.
也不可用。
填充字符=
在 URI 中会被%
编码,但如果数据长度是已知的,就可以避免填充。
该编码可能会被称为base64url
,它不应该被认为跟 Base64 编码一样,也不应该被认为它仅仅就是 Base64。除非特别说明,不然 Base64 就是指 Base64 编码。
除了第 62 和第 63 个字符之外,该编码在技术上完全等同于 Base64。以下就是 URL 和 文件名安全的 Base64 字符表:
字符 | 值 | 字符 | 值 | 字符 | 值 | 字符 | 值 |
---|---|---|---|---|---|---|---|
A | 0 | Q | 16 | g | 32 | w | 48 |
B | 1 | R | 17 | h | 33 | x | 49 |
C | 2 | S | 18 | i | 34 | y | 50 |
D | 3 | T | 19 | j | 35 | z | 51 |
E | 4 | U | 20 | k | 36 | 0 | 52 |
F | 5 | V | 21 | l | 37 | 1 | 53 |
G | 6 | W | 22 | m | 38 | 2 | 54 |
H | 7 | X | 23 | n | 39 | 3 | 55 |
I | 8 | Y | 24 | o | 40 | 4 | 56 |
J | 9 | Z | 25 | p | 41 | 5 | 57 |
K | 10 | a | 26 | q | 42 | 6 | 58 |
L | 11 | b | 27 | r | 43 | 7 | 59 |
M | 12 | c | 28 | s | 44 | 8 | 60 |
N | 13 | d | 29 | t | 45 | 9 | 61 |
O | 14 | e | 30 | u | 46 | - | 62 |
P | 15 | f | 31 | v | 47 | _ | 63 |
Base64 编码实现
以下代码由 Go 语言的标准库 base64 修改而来。
package main
import (
"bytes"
"encoding/base64"
"fmt"
)
const stdBase64Char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
const stdPad = '='
func EncodeStringToString(src string) string {
if len(src) == 0 {
return ""
}
dst := bytes.NewBuffer(nil)
fullLen := (len(src) / 3) * 3
si, di := 0, 0
for ; si < fullLen; si, di = si+3, di+4 {
val := uint(src[si+0])<<16 | uint(src[si+1])<<8 | uint(src[si+2])
dst.Write([]byte{
stdBase64Char[val>>18&0x3f],
stdBase64Char[val>>12&0x3f],
stdBase64Char[val>>6&0x3f],
stdBase64Char[val&0x3f],
})
}
remain := len(src) - fullLen
if remain == 0 {
return dst.String()
}
val := uint(src[si+0]) << 16
if remain == 2 {
val |= uint(src[si+1]) << 8
}
dst.Write([]byte{
stdBase64Char[val>>18&0x3F],
stdBase64Char[val>>12&0x3F],
})
switch remain {
case 2:
dst.Write([]byte{
stdBase64Char[val>>6&0x3f],
stdPad,
})
case 1:
dst.Write([]byte{
stdPad,
stdPad,
})
}
return dst.String()
}
func main() {
raw := "Hello world"
//raw = ""
//raw = "Hello worl"
//raw = "Hello worldA"
//raw = "这是中文啊"
//raw = "这是中文啊a"
//raw = "这是中文啊ab"
fmt.Println(base64.StdEncoding.EncodeToString([]byte(raw)))
fmt.Println(EncodeStringToString(raw))
}