文章
Base64的原理及实现
目录
- Base64 编码/解码原理与示例
- 1. Base64 是什么?
- 2. 为什么是 6-bit?
- 3. 长度公式
- decoded_len = (encoded_len / 4) * 3 - padding_count ``` 其中 padding_count 是结尾 = 的个数(0、1 或 2)。
- 4. encode(编码)示例
- "Man" → "TWFu"
- 'M' = 77 = 01001101 'a' = 97 = 01100001 'n' = 110 = 01101110 2. 拼成 24 位: plain text 01001101 01100001 01101110 3. 切成 4 个 6-bit: plain text 010011 010110 000101 101110 → 十进制:19, 22, 5, 46 4. 查表: plain text 19 → 'T', 22 → 'W', 5 → 'F', 46 → 'u' ``` → **结果 **"TWFu"
- "Ma" → "TWE="
- 'M' = 01001101 'a' = 01100001 拼成:`01001101 01100001 00000000` (补 0) 2. 切分: plain text 010011 010110 000100 000000 ``` → 索引:19, 22, 4, 0 → "TWEA" 3. 由于输入只有 2 字节 → 末尾必须用 = 填充 → "TWE="
- "M" → "TQ=="
- 010011 010000 000000 000000 ``` → 索引:19, 16, 0, 0 → "TQAA" 3. 输入只有 1 字节 → 末尾两个 = → "TQ=="
- 5. decode(解码)示例
- "TWFu" → "Man"
- 'T' = 19 = 010011 'W' = 22 = 010110 'F' = 5 = 000101 'u' = 46 = 101110 拼成: plain text 010011 010110 000101 101110 2. 组合成 24-bit: plain text 01001101 01100001 01101110 3. 每 8-bit 转字节: plain text 01001101 = 'M' 01100001 = 'a' 01101110 = 'n' ``` → **结果 **"Man"
- "TWE=" → "Ma"
- 'T' = 010011 'W' = 010110 'E' = 000100 '=' = padding → 000000 2. 拼成: plain text 01001101 01100001 00000000 3. 转字节: plain text 01001101 = 'M' 01100001 = 'a' ``` → 最后一个字节丢弃(因为 =) → **结果 **"Ma"
- "TQ==" → "M"
- 'T' = 010011 'Q' = 010000 '==' = padding → 000000 000000 2. 拼成: plain text 01001101 00000000 00000000 3. 取第一个字节: plain text 01001101 = 'M' ``` → **结果 **"M"
- 6. Zig 实现(编码 + 解码)
- 常见变体 & 注意事项
- 小结 / 实用建议
- 📎 参考文章
Base64 编码/解码原理与示例#
1. Base64 是什么?#
- Base64 是一种把任意字节流转为可打印字符的编码方式。
- 常用于:邮件、HTTP、URL 参数、JWT、图片内嵌 (
data:image/...;base64)。 - 特点:
- 每 3 字节(24 bit)数据 → 拆成 4 个 6-bit 值;
- 每个 6-bit 映射到 64 个 ASCII 可打印字符;
- 若输入长度不是 3 的倍数,用
=补齐。
2. 为什么是 6-bit?#
- 6-bit 可以表示 0~63 共 64 种值。
- 64 个字符表:
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ```
3. 长度公式#
- 编码后长度:
encoded_len = ((input_len + 2) / 3) * 4 ```
- 解码后长度:
decoded_len = (encoded_len / 4) * 3 - padding_count
```
其中 padding_count 是结尾 = 的个数(0、1 或 2)。#
4. encode(编码)示例#
"Man" → "TWFu"#
- ASCII:
'M' = 77 = 01001101
'a' = 97 = 01100001
'n' = 110 = 01101110
2. 拼成 24 位: plain text
01001101 01100001 01101110
3. 切成 4 个 6-bit: plain text
010011 010110 000101 101110
→ 十进制:19, 22, 5, 46 4. 查表: plain text
19 → 'T', 22 → 'W', 5 → 'F', 46 → 'u'
```
→ **结果 **"TWFu"#
"Ma" → "TWE="#
- 二进制:
'M' = 01001101
'a' = 01100001
拼成:`01001101 01100001 00000000` (补 0) 2. 切分: plain text
010011 010110 000100 000000
```
→ 索引:19, 22, 4, 0 → "TWEA"
3. 由于输入只有 2 字节 → 末尾必须用 = 填充 → "TWE="#
"M" → "TQ=="#
'M' = 01001101→ 拼成01001101 00000000 00000000- 切分:
010011 010000 000000 000000
```
→ 索引:19, 16, 0, 0 → "TQAA"
3. 输入只有 1 字节 → 末尾两个 = → "TQ=="#
5. decode(解码)示例#
"TWFu" → "Man"#
- 查表:
'T' = 19 = 010011
'W' = 22 = 010110
'F' = 5 = 000101
'u' = 46 = 101110
拼成: plain text
010011 010110 000101 101110
2. 组合成 24-bit: plain text
01001101 01100001 01101110
3. 每 8-bit 转字节: plain text
01001101 = 'M'
01100001 = 'a'
01101110 = 'n'
```
→ **结果 **"Man"#
"TWE=" → "Ma"#
- 查表:
'T' = 010011
'W' = 010110
'E' = 000100
'=' = padding → 000000
2. 拼成: plain text
01001101 01100001 00000000
3. 转字节: plain text
01001101 = 'M'
01100001 = 'a'
```
→ 最后一个字节丢弃(因为 =)
→ **结果 **"Ma"#
"TQ==" → "M"#
- 查表:
'T' = 010011
'Q' = 010000
'==' = padding → 000000 000000
2. 拼成: plain text
01001101 00000000 00000000
3. 取第一个字节: plain text
01001101 = 'M'
```
→ **结果 **"M"#
6. Zig 实现(编码 + 解码)#
const Base64 = struct {
// Base64 encoding table
_table: *const [64]u8,
pub fn init() Base64 {
const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const lower = "abcdefghijklmnopqrstuvwxyz";
const numbers_symb = "0123456789+/";
return Base64{
._table = upper ++ lower ++ numbers_symb,
};
}
// Get character at index from Base64 table
fn _char_at(self: Base64, index: usize) u8 {
return self._table[index];
}
fn _char_index(self: Base64, char: u8) u8 {
if (char == '=')
return 64;
var index: u8 = 0;
for (0..63) |i| {
if (self._char_at(i) == char)
break;
index += 1;
}
return index;
}
// Encode input bytes to Base64 string
pub fn encode(self: Base64, allocator: std.mem.Allocator, input: []const u8) ![]u8 {
if (input.len == 0) {
return "";
}
const n_out = try _calc_encode_length(input);
var out = try allocator.alloc(u8, n_out);
var buf = [3]u8{ 0, 0, 0 };
var count: u8 = 0;
var iout: u64 = 0;
for (input, 0..) |_, i| {
buf[count] = input[i];
count += 1;
if (count == 3) {
out[iout] = self._char_at(buf[0] >> 2);
out[iout + 1] = self._char_at(((buf[0] & 0x03) << 4) + (buf[1] >> 4));
out[iout + 2] = self._char_at(((buf[1] & 0x0f) << 2) + (buf[2] >> 6));
out[iout + 3] = self._char_at(buf[2] & 0x3f);
iout += 4;
count = 0;
}
}
if (count == 1) {
out[iout] = self._char_at(buf[0] >> 2);
out[iout + 1] = self._char_at((buf[0] & 0x03) << 4);
out[iout + 2] = '=';
out[iout + 3] = '=';
}
if (count == 2) {
out[iout] = self._char_at(buf[0] >> 2);
out[iout + 1] = self._char_at(((buf[0] & 0x03) << 4) + (buf[1] >> 4));
out[iout + 2] = self._char_at((buf[1] & 0x0f) << 2);
out[iout + 3] = '=';
iout += 4;
}
return out;
}
// Decode Base64 string to bytes
pub fn decode(self: Base64, allocator: std.mem.Allocator, input: []const u8) ![]u8 {
if (input.len == 0) {
return "";
}
const n_output = try _calc_decode_length(input);
var output = try allocator.alloc(u8, n_output);
var count: u8 = 0;
var iout: u64 = 0;
var buf = [4]u8{ 0, 0, 0, 0 };
for (0..input.len) |i| {
buf[count] = self._char_at(input[i]);
count += 1;
if (count == 4) {
output[iout] = (buf[0] << 2) + (buf[1] >> 4);
if (buf[2] != 64) {
output[iout + 1] = (buf[1] << 4) + (buf[2] >> 2);
}
if (buf[3] != 64) {
output[iout + 2] = (buf[2] << 6) + buf[3];
}
iout += 3;
count = 0;
}
}
return output;
}
};
// Calculate the length of the encoded output
// 每3个字节编码为4个字符 ceil((input_len + 2) / 3) * 4)
fn _calc_encode_length(input: []const u8) !usize {
if (input.len < 3) {
return 4;
}
// 除以3向上取整
// 每3个字节编码为4个字符
const n_groups: usize = try std.math.divCeil(usize, input.len, 3);
return n_groups * 4;
}
// Calculate the length of the decoded output
// 每4个字符解码为3个字节
// ceil(input_len / 4) * 3 - padding_count
fn _calc_decode_length(input: []const u8) !usize {
if (input.len < 4) {
return 3;
}
// 除以4向下取整
// 每4个字符解码为3个字节
// '='填充不计入解码长度
const n_groups: usize = try std.math.divFloor(usize, input.len, 4);
var multiple_groups: usize = n_groups * 3;
var i: usize = input.len - 1;
while (i > 0) : (i -= 1) {
if (input[i] == '=') {
multiple_groups -= 1;
} else {
break;
}
}
return multiple_groups;
}
常见变体 & 注意事项#
- URL-safe Base64:把
+替换为 ,/替换为_,有时省略=。用于 URL/文件名安全。 - MIME:RFC 2045 要求每 76 个字符折行(CRLF)。很多实现自动插入或忽略换行。
- Base64 不是加密:只是可逆的编码,不提供任何安全/机密性。
- 字符 vs 字节:Base64 操作的是 字节流。如果你有 Unicode 字符串,先用 UTF-8(或其他编码)转换为字节,再 Base64 编码。不要直接对
char做位运算(在多字节字符集下会错)。 - 流式处理:处理流式输入(块式读写)时保存 0/1/2 个残留字节,下一块继续拼接。
小结 / 实用建议#
- 记住关键公式
encoded_len = ((input_len + 2) / 3) * 4。你原来写的用std.math.divCeil的写法是对的(等价功能,用来做向上取整)。如果你想避免try/错误分支,也可以用(input_len + 2) / 3的整除方式。 - 始终把“字符”先转换为字节(UTF-8)再做 Base64。
- 用现成库通常更稳健(会处理换行、URL-safe、padding 可选等),但自己实现很有意义(性能或内嵌需求)。