Base64 是將任意二進位資料編碼為 64 個可列印 ASCII 字元的標準演算法 (RFC 4648),廣泛用於電子郵件 (MIME)、HTTP Basic Auth、Data URI、JWT、XML/JSON 二進位嵌入等場景。本文從位元層級拆解原理,提供生產級 C++ 實作並解決常見坑點。
一、字元表與索引對照
索引: 0-25 26-51 52-61 62 63 填補
字元: A-Z a-z 0-9 + / =
- 標準表:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
- URL 安全變體 (Base64URL):
+ → -, / → _, 移除填補 =。
二、編碼核心邏輯:3 位元組 → 4 字元
每 3 個位元組 (24 bits) 切成 4 組 6 bits,對應索引表:
輸入: [b1:8bits] [b2:8bits] [b3:8bits]
└────┬────┘ └────┬────┘ └────┬────┘
6b 6b 6b 6b
輸出: [i1] [i2] [i3] [i4]
索引: b1>>2 (b1&3)<<4|b2>>4 (b2&15)<<2|b3>>6 b3&63
三、填補規則 (Padding)
| 輸入餘數 | 處理 | 輸出長度 | 填補 |
| 0 (3 的倍數) | 正常 3→4 | 4 的倍數 | 無 |
| 1 | 1 字元 → 2 個 6-bit (低 4 補 0) + 2 個 = | 4 | == |
| 2 | 2 字元 → 3 個 6-bit (低 2 補 0) + 1 個 = | 4 | = |
解碼時遇到 = 即停止,忽略後續字元。
四、MIME 換行規範
RFC 2045 規定每 76 字元插入 CRLF。解碼時必須跳過 \r \n 及空白字元。
五、高效 C++ 實作 (無依賴、支援任意二進位)
#include
#include
#include
class Base64 {
static constexpr std::array ENC_TABLE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static constexpr std::array DEC_TABLE = []{
std::array t{}; t.fill(-1);
for (int i = 0; i < 64; ++i) t[(unsigned char)ENC_TABLE[i]] = i;
return t;
}();
public:
// 編碼:輸入 byte vector,輸出 string (含換行選項)
static std::string encode(const std::vector& in, bool withNewline = false) {
std::string out; out.reserve((in.size() + 2) / 3 * 4 + (withNewline ? in.size() / 57 + 1 : 0));
size_t i = 0, n = in.size();
for (; i + 2 < n; i += 3) {
out.push_back(ENC_TABLE[in[i] >> 2]);
out.push_back(ENC_TABLE[((in[i] & 3) << 4) | (in[i+1] >> 4)]);
out.push_back(ENC_TABLE[((in[i+1] & 15) << 2) | (in[i+2] >> 6)]);
out.push_back(ENC_TABLE[in[i+2] & 63]);
if (withNewline && out.size() % 76 == 0) out.push_back('\n');
}
size_t rem = n - i;
if (rem == 1) {
out.push_back(ENC_TABLE[in[i] >> 2]);
out.push_back(ENC_TABLE[(in[i] & 3) << 4]);
out.push_back('='); out.push_back('=');
} else if (rem == 2) {
out.push_back(ENC_TABLE[in[i] >> 2]);
out.push_back(ENC_TABLE[((in[i] & 3) << 4) | (in[i+1] >> 4)]);
out.push_back(ENC_TABLE[(in[i+1] & 15) << 2]);
out.push_back('=');
}
return out;
}
// 解碼:自動跳過空白、換行、填補
static std::vector decode(const std::string& in) {
std::vector out; out.reserve(in.size() * 3 / 4);
unsigned char buf[4]; int buf_idx = 0;
for (char ch : in) {
signed char v = DEC_TABLE[(unsigned char)ch];
if (v == -1) continue; // 跳過空白、換行
if (ch == '=') break; // 遇到填補結束
buf[buf_idx++] = (unsigned char)v;
if (buf_idx == 4) {
out.push_back((buf[0] << 2) | (buf[1] >> 4));
out.push_back((buf[1] << 4) | (buf[2] >> 2));
out.push_back((buf[2] << 6) | buf[3]);
buf_idx = 0;
}
}
// 處理末尾不足 4 字元 (理論上不應發生,若有填補已 break)
return out;
}
};
六、常見坑點與對策
| 坑點 | 症狀 | 對策 |
輸入含空字元 \0 | strlen 截斷 | 傳遞長度或用 vector<byte>/string_view |
| 解碼輸出非文字 | 直接印出亂碼 | 以十六進位/十六進位字串/檔案儲存 |
| 大檔案記憶體暴增 | OOM | 流式處理:分塊讀取、編碼、寫入 |
| URL 安全需求 | +/= 在 URL 需再編碼 | 使用 Base64URL 變體 (-_ 無填補) |
| 多執行緒靜態表初始化 | C++11 前競爭 | C++11 起 constexpr / static 保證執行緒安全 |
七、效能優化方向
- 查表法:解碼用 256-byte 反查表 (如上),O(1) 無分支。
- SIMD (SSSE3/AVX2/NEON):
vpshufb 並行查表、位元重組,單核可達 5-10 GB/s。開源庫:base64-simd, fastbase64, Chromium base::Base64Encode。
- 零拷貝:直接對記憶體映射檔案操作,避免中間緩衝區。
八、單元測試向量 (RFC 4648 §10)
"" → ""
"f" → "Zg=="
"fo" → "Zm8="
"foo" → "Zm9v"
"foob" → "Zm9vYg=="
"fooba" → "Zm9vYmE="
"foobar" → "Zm9vYmFy"
Base64 雖非加密,卻是現代網路協定的基石。掌握其位元級原理、填補規則、換行處理與高效實作,能讓您在處理郵件、Token、資料序列化、WebSocket 幀等場景時游刃有餘。完整專案 (含 CMake、GoogleTest、SIMD 版本切換) 請參考文章附件。
- 上一篇文章: 不注入 DLL 也能拿命令列:跨進程讀取 GetCommandLine 內存地址技術
- 下一篇文章: GUI RunAs:圖形化介面以不同身分啟動程式,支援桌面選擇、SYSTEM 權限與特權管理
Copyright ©
程式碼之家 All Rights Reserved