参考 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))
}