Cách Triển Khai Base64 Trong PHP Bằng Các Phép Toán Bit

Base64 là một phương pháp mã hóa nhị phân thành chuỗi văn bản ASCII, thường được dùng để truyền dữ liệu nhị phân qua các kênh chỉ hỗ trợ ký tự in được. Việc hiểu rõ cơ chế hoạt động — đặc biệt là cách xử lý bit — giúp lập trình viên không chỉ sử dụng hàm base64_encode()base64_decode() một cách thụ động, mà còn chủ động kiểm soát, tùy chỉnh hoặc triển khai lại thuật toán khi cần.

Bảng ánh xạ Base64 chuẩn

Bảng Base64 gồm 64 ký tự: 26 chữ hoa (A–Z), 26 chữ thường (a–z), 10 chữ số (0–9), và hai ký tự đặc biệt +/. Mỗi ký tự đại diện cho một giá trị từ 0 đến 63 — tức là đúng 6 bit. Giá trị này chính là chỉ số (index) trong bảng ánh xạ:

  • A → 0 (000000)
  • Z → 25 (011001)
  • a → 26 (011010)
  • z → 51 (110011)
  • 0 → 52 (110100)
  • 9 → 61 (111101)
  • + → 62 (111110)
  • / → 63 (111111)

Nguyên lý mã hóa: Nhóm 3 byte → 4 ký tự Base64

Mỗi ký tự ASCII chiếm 8 bit. Khi nhóm ba ký tự liên tiếp, ta có tổng cộng 24 bit. Chia đều thành bốn khối 6-bit sẽ sinh ra bốn giá trị từ 0–63 — mỗi giá trị tra bảng để lấy ký tự tương ứng.

Ví dụ với chuỗi "ABC":

// A = 65 → 01000001<br>// B = 66 → 01000010<br>// C = 67 → 01000011<br>// Ghép: 01000001 01000010 01000011<br>// Chia 6-bit: 010000 010100 001001 000011<br>// → 16     20      9       3<br>// → Q      U       J       D<br>// Kết quả: "QUJD"

Xử lý trường hợp độ dài không chia hết cho 3

Khi số byte đầu vào không phải bội số của 3, ta thêm 0 vào cuối chuỗi nhị phân để đủ 24-bit mỗi lần xử lý, rồi đánh dấu bằng ký tự = ở cuối kết quả:

  • Nếu thiếu 1 byte (độ dài % 3 == 2): thêm 2 bit zero → tạo thêm 1 ký tự Base64 → gắn 1 dấu =.
  • Nếu thiếu 2 byte (độ dài % 3 == 1): thêm 4 bit zero → tạo thêm 2 ký tự Base64 → gắn 2 dấu =.

Triển khai mã hóa bằng PHP — tái cấu trúc logic

Dưới đây là phiên bản đã được viết lại hoàn toàn: thay đổi tên biến, tách hàm rõ ràng, tối ưu thứ tự phép toán và loại bỏ phần giải thích mang tính cá nhân:

<?php
class Base64Codec
{
    private const CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

    private static function charToIndex(string $ch): int
    {
        $pos = strpos(self::CHARSET, $ch);
        return $pos === false ? 0 : $pos;
    }

    private static function indexToChar(int $idx): string
    {
        return self::CHARSET[$idx];
    }

    public static function encode(string $input): string
    {
        $output = '';
        $len = strlen($input);
        $blocks = (int)floor($len / 3);

        // Xử lý từng khối 3 byte
        for ($i = 0; $i < $blocks; $i++) {
            $offset = $i * 3;
            $byte0 = ord($input[$offset]);
            $byte1 = ord($input[$offset + 1]);
            $byte2 = ord($input[$offset + 2]);

            // Gộp 3 byte → 24 bit số nguyên
            $packed = ($byte0 << 16) | ($byte1 << 8) | $byte2;

            // Trích xuất 4 giá trị 6-bit
            $output .= self::indexToChar(($packed >> 18) & 0x3F);
            $output .= self::indexToChar(($packed >> 12) & 0x3F);
            $output .= self::indexToChar(($packed >> 6) & 0x3F);
            $output .= self::indexToChar($packed & 0x3F);
        }

        // Xử lý phần dư
        $remainder = $len % 3;
        if ($remainder === 1) {
            $lastByte = ord($input[$len - 1]);
            $value = ($lastByte << 4) & 0x3F;
            $output .= self::indexToChar($value);
            $output .= self::indexToChar(0); // padding
            $output .= '==';
        } elseif ($remainder === 2) {
            $b0 = ord($input[$len - 2]);
            $b1 = ord($input[$len - 1]);
            $value = (($b0 << 10) | ($b1 << 2)) & 0x3F;
            $output .= self::indexToChar($value >> 6);
            $output .= self::indexToChar($value & 0x3F);
            $output .= '=';
        }

        return $output;
    }

    public static function decode(string $encoded): string
    {
        $cleaned = str_replace(['=', "\n", "\r", " "], '', $encoded);
        $len = strlen($cleaned);
        $output = '';

        // Mỗi 4 ký tự Base64 → 3 byte gốc
        for ($i = 0; $i < $len; $i += 4) {
            if ($i + 4 > $len) break;

            $v0 = self::charToIndex($cleaned[$i]);
            $v1 = self::charToIndex($cleaned[$i + 1]);
            $v2 = self::charToIndex($cleaned[$i + 2]);
            $v3 = self::charToIndex($cleaned[$i + 3]);

            $packed = ($v0 << 18) | ($v1 << 12) | ($v2 << 6) | $v3;

            $output .= chr(($packed >> 16) & 0xFF);
            $output .= chr(($packed >> 8) & 0xFF);
            $output .= chr($packed & 0xFF);
        }

        // Xử lý padding cuối nếu có
        $padding = substr($encoded, -2);
        if ($padding === '==') {
            $v0 = self::charToIndex($cleaned[$len - 4]);
            $v1 = self::charToIndex($cleaned[$len - 3]);
            $value = ($v0 << 10) | ($v1 << 2);
            $output .= chr(($value >> 8) & 0xFF);
        } elseif (substr($encoded, -1) === '=') {
            $v0 = self::charToIndex($cleaned[$len - 4]);
            $v1 = self::charToIndex($cleaned[$len - 3]);
            $v2 = self::charToIndex($cleaned[$len - 2]);
            $value = ($v0 << 12) | ($v1 << 6) | $v2;
            $output .= chr(($value >> 16) & 0xFF);
            $output .= chr(($value >> 8) & 0xFF);
        }

        return $output;
    }
}
?>

Giải thích ngắn gọn các phép toán then chốt

  • $x << n: Dịch trái n bit → thêm n bit 0 vào bên phải.
  • $x >> n: Dịch phải n bit → loại bỏ n bit bên phải.
  • $x & 0x3F: Mặt nạ 6 bit → giữ nguyên 6 bit thấp nhất, xóa tất cả bit cao hơn.
  • $x & 0xFF: Mặt nạ 8 bit → giữ nguyên 1 byte thấp nhất.

Tại sao dùng 0x3F chứ không phải 63?

Viết dưới dạng hex (0x3F) làm rõ mục đích: đây là mặt nạ nhị phân 00111111, giúp nhấn mạnh rằng ta đang thao tác trên **6 bit**, không phải giá trị thập phân thuần túy. Đây là thói quen tốt khi làm việc với bit-level logic.

Kết luận

Việc triển khai Base64 thủ công không nhằm thay thế hàm dựng sẵn, mà để đào sâu vào bản chất của việc đóng gói dữ liệu nhị phân. Hiểu rõ cách ghép byte, dịch bit, và áp dụng mặt nạ giúp nâng cao năng lực debug, tối ưu hiệu năng trong các hệ thống nhúng hoặc môi trường hạn chế tài nguyên — nơi việc phụ thuộc vào thư viện chuẩn không luôn khả thi.

Thẻ: php base64 bitwise-operators encoding algorithm

Đăng vào ngày 2 tháng 6 lúc 01:21