实战:Base64编解码(二进制转文本)
说实话,Base64 这玩意儿,你天天都在用,只是你可能没意识到。
比如你发邮件里的附件,比如网页上那些小图片被嵌在 HTML 里,再比如某些 API 返回的「加密」字符串……背后基本都是 Base64 在干活。
它干的事很简单:把二进制数据变成纯文本。为什么需要这个?因为很多传输通道(比如 HTTP、SMTP)只认文本字符,遇到 0x00 这种字节直接就炸了。Base64 就是那个「翻译官」。
核心一句话:Base64 不是加密,是编码。它只是换了一种表示方式,不提供任何安全性。
Base64 的原理,我用人话讲一遍
二进制数据是一个字节一个字节的,每个字节 8 位。Base64 的做法是:每 3 个字节(24 位)为一组,拆成 4 个 6 位的片段。每个 6 位片段对应一个索引,然后去一张「字母表」里查表,得到 4 个可见字符。
字母表长这样:
A-Z 对应 0-25
a-z 对应 26-51
0-9 对应 52-61
+ 对应 62
/ 对应 63
= 用作填充
如果最后剩下的字节不够 3 个,就用 = 补齐。所以你看到的 Base64 字符串长度永远是 4 的倍数,末尾可能有一两个 =。
举个例子:字符串 "Man" 三个字节,转成 Base64 就是 "TWFu"。你可以在命令行里验证一下。
我个人的习惯:调试时经常用 Base64 来快速检查一段二进制数据是不是对的。比如先编码成字符串看一眼,再解码回去对比。比直接看十六进制直观多了。
代码实现:C++ 手写一个 Base64
标准库没有直接提供 Base64,但实现起来也就几十行。我直接贴一个我项目里用过的版本,去掉了一些边界检查,保留核心逻辑。
#include <iostream>
#include <string>
#include <vector>
#include <cstdint>
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
std::string base64_encode(const std::vector<uint8_t>& data) {
std::string result;
int i = 0;
int j = 0;
uint8_t char_array_3[3];
uint8_t char_array_4[4];
for (size_t idx = 0; idx < data.size(); ++idx) {
char_array_3[i++] = data[idx];
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; i < 4; ++i)
result += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i) {
for (j = i; j < 3; ++j)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; j < i + 1; ++j)
result += base64_chars[char_array_4[j]];
while (i++ < 3)
result += '=';
}
return result;
}
解码就是逆过程:把 4 个字符变回 3 个字节,遇到 = 就跳过。代码我就不贴了,思路完全对称。
我曾经踩过一个坑:解码时没处理 = 填充,结果最后几个字节全是乱的。调试了一下午才发现是填充位没对齐。所以写解码时,填充处理一定要仔细。
Base64 的变种,你知道吗?
标准的 Base64 用 +/ 作为最后两个字符。但有些场景下,+ 和 / 在 URL 里会被转义,所以有了 URL-safe Base64:把 + 换成 -,/ 换成 _,并且去掉末尾的 =。
另外还有 Base64URL、MIME 版的 Base64(每 76 个字符换行)等等。你想想看,光一个 Base64 就有这么多变种,写通用代码时一定要确认对方用的是哪个版本。
| 变种名称 | 第62字符 | 第63字符 | 填充 | 常见场景 |
|---|---|---|---|---|
| 标准 Base64 | + | / | = | 邮件附件、一般文本 |
| URL-safe Base64 | - | _ | 无 | JWT Token、URL参数 |
| MIME Base64 | + | / | = | 电子邮件内容 |
Base64 的优缺点,我直接说
优点:
- 纯文本,兼容性好,任何文本通道都能过
- 实现简单,查表就行,性能不错
- 可读性还行,肉眼能看出是不是 Base64
缺点:
- 体积膨胀 33%。3 个字节变 4 个字符,这是硬伤
- 不是加密,别拿它当密码用
- 大文件编码后体积变大,传输效率低
我的建议:如果只是传小数据(几百 KB 以内),Base64 完全够用。如果是大文件,考虑用 gzip 压缩后再 Base64,或者直接用二进制传输(比如 multipart/form-data)。
流程图:Base64 编码核心逻辑
下面这张图把编码过程画清楚了,你一看就懂。
什么时候用 Base64?我总结三条
- 文本协议里传二进制:比如 JSON 里放图片、放文件内容。不用 Base64 你根本没法放。
- 数据签名/哈希后的展示:比如 JWT 的 payload 部分,签名后的二进制用 Base64 编码成字符串,方便传输和存储。
- 简单的数据混淆:注意我说的是「混淆」,不是加密。防君子不防小人,比如隐藏一些配置参数。
最后说一句:Base64 是 C++ 程序员工具箱里的常备工具。你不需要背字母表,但一定要理解它的设计思路——用空间换兼容性。很多协议层面的设计,背后都是这个道理。