Base64是一种基于64个可打印字符来表示二进制数据的方法。
当我们用文本编辑器打开jpg
、pdf
、exe
这些文件格式的时候,会看到一大堆的乱码,这是因为二进制文件包含很多无法显示和打印的字符。所以,如果想要让记事本这样的文本编辑器处理二进制数据,就需要一个从二进制到字符串的转换方法。Base64就是一种最常见的二进制编码方法。
原理
文本文件和二进制文件
使用计算机时,我们会遇到各种各样的文件格式,有像txt
、cpp
、java
、md
这样的可以用文本编辑器直接打开的文件格式,我们称之为 文本文件,也有一些文件用编辑器打开时是一堆乱码,比如像上面提到的jpg
、wav
、pdf
,这些文件格式称之为 二进制文件。
我们知道,所有文件在计算机的存储都是一堆0和1的序列,也就是说不论是文本文件还是二进制文件,在物理上,计算机的存储是都是二进制的。 所谓的文本文件和二进制文件的区别并不是物理上的,而是逻辑上的。广义上来说,文本文件也是二进制文件。二者的区别是因为你看待数据的方式不同而产生的差别,具体的说二者的区别就在于打开这个文件的程序对其内容的解释上。
以文件的读写过程为例,这实际包含了如下的两个转换过程
第一个过程中,文件被应用程序从磁盘读取到文件缓冲区,二者都是一堆的0和1的序列,这个时候文本文件和二进制文件并没有什么区别。
接下来,不同的应用程序,根据面对的不同文件格式,对一堆的0和1序列进行解释。对于文本文件,应用程序直接将其中的数据(也就是这一堆0和1序列按照ASCII或者Unicode编码的方式解释出来),显示成文本的形式。对于其他类型的文件,一般包括了控制信息和内容信息,应用程序按照文件格式,从这些数据中解析出对应的数据内容,比如常见的wav文件解析过程,而这些就是所谓的二进制文件。
Base64的转换
Base64编码选择的64个可打印字符为字母A-Z,a-z、数字0-9,这样共有62个字符,此外还有两个字符在不同的系统中而不同。比如对于MIME格式,其采用的为
1
| const static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
然后,而二进制数据进行处理,每3个字节一组,一共是 $ 3 × 8 = 24 $ bit,划分为4组,每组正好6个bit。
![base64]()
这样,我们得到4个数字作为索引,然后查表,获得相应的4个字符,就得到编码后的字符串。所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。
如果要编码的二进制数据不是3的倍数,Base64就会在后面补0,每补1个0就在编码出来的字符串后加上1个=
,解码的时候会自动将=
去掉。
Python中内置的base64
可以直接进行base64的编解码
1 2 3 4 5
| >>> import base64 >>> base64.b64encode(b'hello, world!') b'aGVsbG8sIHdvcmxkIQ==' >>> base64.b64decode(b'aGVsbG8sIHdvcmxkIQ==') b'hello, world!'
|
C++实现
此处源码可在 base64的C++实现
encode方法
- 首先计算生成base64字符串的长度
- 原来的二进制流中,每3个字节为整体,合为一个32位的bit串
- 将这个32位bit串的低24bit的每6个比特作为索引,得到映射表中对应的字符
- 如果原来的字节串不是3的倍数,则补零,并且每补一个0加一个
=
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| std::string encode_base64(const std::vector<uint8_t> unencoded) { std::string encoded; const auto size = unencoded.size(); encoded.reserve(((size / 3) + (size % 3 > 0)) * 4);
uint32_t value; auto cursor = unencoded.begin(); for (size_t position = 0; position < size / 3; position++) { value = (*cursor++) << 16;
value += (*cursor++) << 8; value += (*cursor++); encoded.append(1, table[(value & 0x00FC0000) >> 18]); encoded.append(1, table[(value & 0x0003F000) >> 12]); encoded.append(1, table[(value & 0x00000FC0) >> 6]); encoded.append(1, table[(value & 0x0000003F) >> 0]); }
switch (size % 3) { case 1: value = (*cursor++) << 16;
encoded.append(1, table[(value & 0x00FC0000) >> 18]); encoded.append(1, table[(value & 0x0003F000) >> 12]); encoded.append(2, pad); break; case 2: value = (*cursor++) << 16;
value += (*cursor++) << 8;
encoded.append(1, table[(value & 0x00FC0000) >> 18]); encoded.append(1, table[(value & 0x0003F000) >> 12]); encoded.append(1, table[(value & 0x00000FC0) >> 6]); encoded.append(1, pad); break; }
return encoded; }
|
decode方法
- 首先判断base64字符串的长度是不是4的倍数
- 去除
=
这样的pad,=
只可能是1个或2个
- 遍历整个base64字符串,每4个为一个单位(对应也就是3个字节的二进制数,以value表示),对于每个字符得到其在映射表中的索引,从而得到value的值,进而解析出每个字节的值
对应代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| bool decode(std::vector<uint8_t>& out, const std::string& in) { const static uint32_t mask = 0x000000FF;
const auto length = in.length(); if ((length % 4) != 0) return false;
size_t padding = 0; if (length > 0) { if (in[length - 1] == pad) padding++; if (in[length - 2] == pad) padding++; }
std::vector<uint8_t> decoded; decoded.reserve((length / 4) * 3 - padding);
uint32_t value = 0; for (auto cursor = in.begin(); cursor < in.end();) { for (size_t position = 0; position < 4; position++) { value <<= 6; if (*cursor >= 0x41 && *cursor <= 0x5A) value |= *cursor - 0x41; else if (*cursor >= 0x61 && *cursor <= 0x7A) value |= *cursor- 0x47; else if (*cursor >= 0x30 && *cursor <= 0x39) value |= *cursor + 0x04; else if (*cursor == 0x2B) value |= 0x3E; else if (*cursor == 0x2F) value |= 0x3F; else if (*cursor == pad) { switch (in.end() - cursor) { case 1: decoded.push_back((value >> 16) & mask); decoded.push_back((value >> 8) & mask); out = decoded; return true; case 2: decoded.push_back((value >> 10) & mask); out = decoded; return true; } } else return false;
cursor++; }
decoded.push_back((value >> 16) & mask); decoded.push_back((value >> 8) & mask); decoded.push_back((value >> 0) & mask); }
out = decoded; return true; }
|
调用
写一个简单的demo验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include "base64.h" #include <iostream>
int main() { std::string encoded_str("aGVsbG8sIHdvcmxkIQ=="); std::cout << "Encoded base64 string: " << encoded_str << std::endl;
std::vector<uint8_t> orignal_vector; if ((decode_base64(orignal_vector, encoded_str)) == false) { std::cout << "decode error" << std::endl; return 0; } std::string orignal_str(orignal_vector.begin(), orignal_vector.end()); std::cout << "Original string is " << orignal_str << std::endl;
std::string encoded = encode_base64(orignal_vector); std::cout << "Encoded again: " << encoded << std::endl; return 0; }
|
编译后得到
1 2 3 4 5
| $ gcc -o demo demo.cpp base.cpp $ ./demo Encoded base64 string: aGVsbG8sIHdvcmxkIQ== Original string is hello, world! Encoded again: aGVsbG8sIHdvcmxkIQ==
|
应用
上面说了base64的编码方法,那么为什么要使用base64编码呢,有哪些情景需求?
我们知道在计算机中任何数据都是按ascii码存储的,而ascii码的128~255之间的值是不可见字符。而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。
HTML内嵌base64编码图片
MIME
X.509 公钥证书