小码农

  • 首页
  • 工作
  • 生活
  • 在线工具
    • GitHub 文件下载
小码农
学习的过程往往都是推衍的。
  1. 首页
  2. 工作
  3. 算法
  4. 正文

Base64编码原理、实现

2020年11月15日 226点热度 0人点赞 0条评论

0x01 Base64 编码

Base64 编码在数据传输过程中应用广泛,例如传统的邮件只支持可见字符的传送,像 ASCII 码的控制字符就不能通过邮件传送。这样就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所以就传送不了。正好 Base64 编码使用 64 个可见字符来表示二进制数据。

0x02 Base64 原理


如上图所示是将 "Base" 进行Base64编码得到 "QmFzZQ="的过程。

  • 根据ASCII编码表获取 "Base" 对应的二进制,一个字符占一个字节,也就是一个字符8位
  • 将 "Base" 的二进制编码按每6位划分,正好3个8位字节可以得到4个6位字节。
  • 在6位字节前补两个0形成8位一个字节。如果剩下字符不足6位,后面用0填充。
  • 根据形成的前两位为0的8位字节进行查表(Base64编码表),得到Base64编码。为满足Base64编码字节数为4的倍数,最后需要填充足够的 "=" 符号
Base64 编码表

0x03 Base64 实现(Golang)

在看下面内容之前,请务必了解 位运算 中的 左移、右移、与运算

  • 定义编码表
// 编码表
var EncodeDict = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
// 解码表
var DecodeDict = map[byte]int{'0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58,
    '7': 59, '8': 60, '9': 61, 'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
    'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17,
    'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27,
    'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37,
    'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47,
    'w': 48, 'x': 49, 'y': 50, 'z': 51, '+': 62, '/': 63}
  • Encode 实现
func Base64Encode(input string) string {
    data := []byte(input)
    var result []byte
    dataLen := len(data)
    for i := 0; i < dataLen; i += 3 {
        // 取第一个字节
        c := data[i]
        idx := c >> 2
        result = append(result, EncodeDict[idx])
        // 取第二个字符
        if i+1 >= dataLen {
            idx = (c & 3) << 4
            result = append(result, EncodeDict[idx], '=', '=')
            break
        } else {
            idx = (c & 3) << 4 + data[i+1] >> 4
            result = append(result, EncodeDict[idx])
        }
        c = data[i+1]
        // 取第三个字符
        if i+2 >= dataLen {
            idx = (c & 15) << 2
            result = append(result, EncodeDict[idx], '=')
            break
        } else {
            idx = (c & 15) << 2 + data[i+2] >> 6
            result = append(result, EncodeDict[idx], EncodeDict[data[i+2]&63])
        }
    }
    return string(result)
}

在Base64编码实现中,我们每次读入3个字节的数据(因为三个8位字节对应Base64的4个6位字节),分别对三个字节进行处理,我们使用右移2位操作得到第一个字节的前6位形成一个前面填充0的8位字节进行查表;在处理第二个字节之前,我们先通过与运算拿到第一个字节的最后两位,并通过左移运算放到6位字节的最高位,最后加上第二个字节的前4位,就可以得到第二个前面填充0的8位字节进行查表,后面依次内推,无非通过左移、右移、与运算分别得到四个前面填充0的8位字节。

值得注意的是,在处理第二、三个字节时,需要判断当前是否已经到字符串结尾,如果到尾部,需要添加上对应数量的 '=' 符号

  • Decode 实现
func Base64Decode(input string) string {
    data := []byte(input)
    dataLen := len(data)
    for i := 0; i < dataLen%4; i++ {
        data = append(data, '=')
    }
    var result []byte
    for i := 0; i < dataLen; i += 4 {
        result = append(result, byte(DecodeDict[data[i]]<<2 + DecodeDict[data[i+1]]&63>>4))
        if data[i+2] == '=' {
            result = append(result, byte(DecodeDict[data[i+1]]&15<<4))
            break
        } else {
            result = append(result, byte(DecodeDict[data[i+1]]&15<<4 + DecodeDict[data[i+2]]&63>>2))
        }
        if data[i+3] == '=' {
            result = append(result, byte(DecodeDict[data[i+2]]&3<<6))
            break
        } else {
            result = append(result, byte(DecodeDict[data[i+2]]&3<<6 + DecodeDict[data[i+3]]&63))
        }
    }
    return string(result)
}

同编码相似,解码的时候,每次处理4个字符(因为Base64编码4个6位字节等于三个8位字节),同样使用位运算中的与、左移、右移实现字节的变换与获取,最后使用'+'拼接出三个8位字节,再转换成字符串

在解码之前,因先判断Base64编码是否为4的倍数,如果不是,需要填充足够的'='符号

0x04 实验结果

备注:该文章第一次发布为2020年07月15日,是站长本人在csdn博客发布

标签: Base64 加密 编码
最后更新:2020年11月16日

小码农

学习的过程往往都是推衍的。

点赞

文章评论

取消回复

COPYRIGHT © 2020 小码农. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS

蜀ICP备19010688号