返回文章列表

文章

Base64的原理及实现

目录
  1. Base64 编码/解码原理与示例
  2. 1. Base64 是什么?
  3. 2. 为什么是 6-bit?
  4. 3. 长度公式
  5. decoded_len = (encoded_len / 4) * 3 - padding_count ``` 其中 padding_count 是结尾 = 的个数(0、1 或 2)。
  6. 4. encode(编码)示例
  7. "Man" → "TWFu"
  8. '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"
  9. "Ma" → "TWE="
  10. 'M' = 01001101 'a' = 01100001 拼成:`01001101 01100001 00000000` (补 0) 2. 切分: plain text 010011 010110 000100 000000 ``` → 索引:19, 22, 4, 0 → "TWEA" 3. 由于输入只有 2 字节 → 末尾必须用 = 填充 → "TWE="
  11. "M" → "TQ=="
  12. 010011 010000 000000 000000 ``` → 索引:19, 16, 0, 0 → "TQAA" 3. 输入只有 1 字节 → 末尾两个 = → "TQ=="
  13. 5. decode(解码)示例
  14. "TWFu" → "Man"
  15. '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"
  16. "TWE=" → "Ma"
  17. 'T' = 010011 'W' = 010110 'E' = 000100 '=' = padding → 000000 2. 拼成: plain text 01001101 01100001 00000000 3. 转字节: plain text 01001101 = 'M' 01100001 = 'a' ``` → 最后一个字节丢弃(因为 =) → **结果 **"Ma"
  18. "TQ==" → "M"
  19. 'T' = 010011 'Q' = 010000 '==' = padding → 000000 000000 2. 拼成: plain text 01001101 00000000 00000000 3. 取第一个字节: plain text 01001101 = 'M' ``` → **结果 **"M"
  20. 6. Zig 实现(编码 + 解码)
  21. 常见变体 & 注意事项
  22. 小结 / 实用建议
  23. 📎 参考文章

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"#

  1. 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="#

  1. 二进制:

'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=="#

  1. 'M' = 01001101 → 拼成 01001101 00000000 00000000
  2. 切分:

010011 010000 000000 000000 ``` → 索引:19, 16, 0, 0 → "TQAA" 3. 输入只有 1 字节 → 末尾两个 ="TQ=="#

5. decode(解码)示例#

"TWFu""Man"#

  1. 查表:

'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"#

  1. 查表:

'T' = 010011 'W' = 010110 'E' = 000100 '=' = padding → 000000 2. 拼成: plain text 01001101 01100001 00000000 3. 转字节: plain text 01001101 = 'M' 01100001 = 'a' ``` → 最后一个字节丢弃(因为 =) → **结果 **"Ma"#

"TQ==""M"#

  1. 查表:

'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 可选等),但自己实现很有意义(性能或内嵌需求)。

📎 参考文章#