实战: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 编码流程 原始二进制数据 每 3 个字节(24 位)为一组 拆成 4 个 6 位片段(索引值 0-63) 查 Base64 字母表得到 4 个字符 输出 Base64 字符串

什么时候用 Base64?我总结三条

  1. 文本协议里传二进制:比如 JSON 里放图片、放文件内容。不用 Base64 你根本没法放。
  2. 数据签名/哈希后的展示:比如 JWT 的 payload 部分,签名后的二进制用 Base64 编码成字符串,方便传输和存储。
  3. 简单的数据混淆:注意我说的是「混淆」,不是加密。防君子不防小人,比如隐藏一些配置参数。

最后说一句:Base64 是 C++ 程序员工具箱里的常备工具。你不需要背字母表,但一定要理解它的设计思路——用空间换兼容性。很多协议层面的设计,背后都是这个道理。

公众号:蓝海资料掘金营,微信 deep3321