Base64 và URL encoding: Khi nào cần và khi nào dùng sai

Đăng 2026-04-13 7 phút đọc

Tóm tắt (TL;DR)

echo -n "Hi" | base64 trả về đúng bốn ký tự (SGk=). Hai byte đầu vào (16 bit) được cắt theo nhóm 6 bit thành bốn ký tự ngõ ra, và vị trí cuối được lấp một dấu = padding — phép số bit dừng ở đó. Ngay khi bạn mở rộng quy tắc đơn giản này thành “để an toàn cứ bọc tất cả lại”, chi phí đi theo: tỷ lệ ba byte vào thành bốn ký tự ra chính xác là phình khoảng 33%, và trong một luồng vận hành, JPEG 1,2 MB sau khi bọc Base64 ghi lại ở mức khoảng 1,64 MB. Encoding không phải mã hoá, cũng không phải nén — đây chỉ là quy tắc biểu diễn dữ liệu từ một bảng chữ cái qua bảng chữ cái khác để đi qua một kênh không cho phép nó đi thẳng. Base64 (RFC 4648 §4) dành cho kênh chỉ cho phép văn bản như body email, trường JSON string, ảnh inline. Base64URL (cũng RFC 4648 §5) đổi hai ký tự (+-, /_) để lọt an toàn vào URL, tên file và JWT. Percent encoding (RFC 3986) là công cụ riêng, bọc các ký tự ASCII có ý nghĩa ngữ pháp URL (?, &, #, dấu cách, non-ASCII) thành %XX để đánh dấu chúng là dữ liệu. Hãy chọn theo kênh: byte nguyên nếu được, Base64 cho kênh văn bản, Base64URL khi nằm trong ngữ cảnh URL, percent encoding khi đã ở trong URL. Và vì bảng chữ cái công khai, bất kỳ ai cũng có thể giải mã chuỗi Base64 lộ ra — không dùng như biện pháp giữ bí mật.

Bối cảnh và khái niệm

Các kênh chỉ cho văn bản tồn tại từ lâu trước khi người ta cần vận chuyển ảnh qua đó. Email trong lịch sử giả định ASCII 7-bit và thường phá byte có bit cao; header HTTP đến giờ vẫn cấm một số ký tự; trường string JSON là UTF-8 nhưng không chứa an toàn các byte điều khiển thô (0x00–0x1F). Base64, chuẩn hoá trong RFC 4648, giải quyết bằng cách ánh xạ chính xác ba byte (24 bit) đầu vào thành bốn ký tự đầu ra theo cụm 6 bit. Bảng chữ cái có 64 ký tự: A-Z (0–25), a-z (26–51), 0-9 (52–61), + (62), / (63), cộng padding = để đảm bảo chiều dài chia hết cho 4. Một byte vào thành XX== (8 bit → 6+2), hai byte thành XXX= (16 bit → 6+6+4), ba byte thành XXXX (24 bit → 6+6+6+6); khi chia hết ba byte thì không cần padding.

Biến thể URL-safe chỉ đổi hai chỉ số 62 và 63 trong bảng chữ cái — +-, /_. Lý do: trong form submission cũ, + được hiểu thành dấu cách và / là dấu phân cách path. Trong ngữ cảnh URL-safe, padding = cũng thường bị bỏ — đây chính là dạng JWT (RFC 7519) đang dùng, vì cấu trúc ba phần header.payload.signature phải gói gọn trong header Authorization: Bearer ....

Percent encoding (thường gọi là URL encoding) lại là chuyện khác. RFC 3986 chia ASCII thành hai nhóm: ký tự unreserved (A-Z, a-z, 0-9, -, ., _, ~) dùng nguyên, và ký tự reserved có ý nghĩa cấu trúc (:, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =) cần encode khi dùng làm dữ liệu. Các byte ngoài tập này — gồm byte UTF-8 non-ASCII — được biểu diễn dạng hex %XX. encodeURIComponent của JavaScript encode cả ký tự reserved để đặt an toàn vào một component, còn encodeURI giả định xử lý URL toàn phần nên khoan dung hơn.

Dù byte đầu ra trông giống nhau, ba encoding không thay thế được nhau. Tham số query đã percent encoded vẫn chỉ là ASCII biểu diễn ASCII khác, nên đưa vào bộ giải mã Base64 sẽ ra byte vô nghĩa. Dán thẳng chuỗi Base64 chuẩn vào path URL thì dấu / bên trong sẽ cắt path sai chỗ — một đồng nghiệp của tôi từng mất nửa ngày vì đúng một ký tự này. Một nửa lỗi encoding trong vận hành đến từ nhầm lẫn kiểu đó.

So sánh và dữ liệu

MụcBase64 (chuẩn)Base64URLPercent encoding
Khi dùngKênh văn bản từ chối nhị phân (body MIME, byte trong JSON)Cùng mục đích nhưng nằm trong URL, tên file, JWTKý tự reserved và non-ASCII trong một component URL
Bảng chữ cáiA–Z a–z 0–9 + / =A–Z a–z 0–9 - _ (padding tuỳ chọn)Unreserved, còn lại là %XX
OverheadKhoảng 33% (3 byte → 4 ký tự), MIME thêm xuống dòng mỗi 76 ký tựKhoảng 33%, thường không paddingBiến thiên — 1 byte UTF-8 thành 3 ký tự %XX
Trường hợp hỏng+ hoặc / trong URL, thiếu padding ở decoder nghiêm ngặtCho ngược vào decoder chuẩn dựa trên +/=Encode kép (encode lại giá trị đã encoded)
Ứng dụng phổ biếnĐính kèm MIME, data URI, nhị phân inline trong JSONJWT, ID URL-safe, link rút gọnTham số query, đoạn path, body form

Các con số không thể bỏ qua. Ảnh 1 MB encode Base64 thành khoảng 1,37 MB, cộng thêm xuống dòng 76 ký tự của MIME thì thêm khoảng 2% nữa. Trong response HTTP, phình đó ảnh hưởng cả băng thông server lẫn parser client. Percent encoding thường đỡ nặng tay hơn ở mức chuỗi, nhưng tăng mạnh với văn bản CJK. Một ký tự tiếng Việt hoặc Hàn Quốc là 3 byte UTF-8, tức 9 byte ASCII (%XX × 3). “안녕” hai chữ Hàn trong URL trở thành 18 byte %EC%95%88%EB%85%95.

Tình huống thực tế

Tình huống 1 — Đính kèm email. PDF được đặt vào một phần MIME với Content-Transfer-Encoding: base64. Client mail encode file sang Base64, xuống dòng mỗi 76 ký tự theo luật MIME cũ, rồi gửi đi. Người nhận giải mã ngược lại. Giả định “text-only” của SMTP biến Base64 thành mặc định thực tế ở lĩnh vực này, và ngay cả với các mở rộng 8BITMIME và BINARYMIME, nhiều mail server vẫn dùng Base64 cho an toàn. Chi phí 33% được chấp nhận để đổi lấy việc giao nhận chắc chắn.

Tình huống 2 — JSON Web Token. JWT (RFC 7519) gồm header.payload.signature, mỗi đoạn encode bằng Base64URL không padding. Bảng chữ cái URL-safe cho phép token lọt vào header Authorization, tham số query và dòng log mà không cần escape lại. Bỏ padding giữ token ngắn. Một cảnh báo: bất cứ ai decode JWT cũng đọc được claim. Encoding không phải bảo mật; chữ ký HMAC hoặc RSA mới là bảo mật. Đừng đặt thông tin bí mật trong payload.

Tình huống 3 — Data URI cho ảnh nhỏ. background-image: url("data:image/png;base64,iVBORw0K...") nhúng PNG trực tiếp vào CSS. Với icon rất nhỏ, cách này tiết kiệm round-trip, nhưng tính đến overhead 33% của Base64 và việc mất cache trình duyệt và song song hoá request, cách này chỉ có lợi với asset đủ nhỏ để round-trip HTTP đắt hơn. Theo kinh nghiệm tôi, điểm hoà vốn nằm quanh 1–2 KB; trên đó thì file riêng hoặc SVG thường nhanh hơn.

Tình huống 4 — Upload avatar người dùng. Anti-pattern phổ biến là dùng FileReader.readAsDataURL đọc file thành chuỗi data:image/png;base64,... rồi POST qua trường JSON. Nó chạy được nhưng gần như không bao giờ đúng. Payload to hơn 33%, server phải decode Base64 một lần nữa trước khi ghi ra đĩa, và bộ nhớ phía client lẫn server đều giữ bản sao chuỗi. Trong một vụ tôi từng thấy, ảnh 5 MB phình thành JSON khoảng 6,7 MB và timeout trên kết nối di động — sau khi đổi sang multipart/form-data, cùng file up lên đúng 5 MB. Chỉ cân nhắc Base64 khi tầng vận chuyển thực sự yêu cầu văn bản, như API bên thứ ba chỉ chấp nhận chuỗi JSON.

Những hiểu lầm thường gặp

“Base64 là mã hoá.” Không. Bảng ánh xạ công khai trong RFC 4648, bảng chữ cái cố định, và bất kỳ decoder nào cũng trả về byte gốc. Encoding bảo vệ quá trình truyền, không bảo vệ nội dung. Với dữ liệu nhạy cảm, hãy mã hoá trước rồi bọc ciphertext vào Base64 để vận chuyển.

“URL encoding chỉ cần cho ký tự non-ASCII.” Ký tự ASCII reserved cũng cần encode khi dùng làm dữ liệu. Để & nguyên, server hiểu thành tham số mới; để # nguyên, tất cả sau đó bị coi là fragment. Quy tắc dựa trên cấu trúc chứ không phải “tập ký tự”.

“Đi qua HTTP thì Base64 là an toàn nhất.” HTTP chuyển body nhị phân rất sẵn sàng — Content-Type: application/octet-stream, chunked transfer, byte giá trị tuỳ ý. Base64 là đường vòng cho các kênh không làm được điều đó; thêm overhead 33% vào kênh vốn xử lý được byte là đánh thuế vô nghĩa. Upload file thì dùng multipart/form-data hoặc body thô; Base64 chỉ khi kênh thực sự đòi hỏi văn bản như trường JSON, tham số URL, body MIME.

“Base64 và Base64URL đổi chỗ cho nhau được.” Không. Cho chuỗi URL-safe vào decoder Base64 nghiêm ngặt chờ +/ sẽ thất bại hoặc ra giá trị rác. Thư viện thường cung cấp cả hai; hãy khớp đầu vào đầu ra xuyên suốt — Buffer.from(s, 'base64') của Node nhận cả hai bảng, nhưng không phải thư viện chuẩn của ngôn ngữ nào cũng khoan dung như vậy.

Danh sách kiểm tra

  1. Kênh có phải 7-bit hoặc văn bản có cấu trúc không? Body email, byte trong JSON, data URI CSS → Base64.
  2. Giá trị có nằm trong URL, tên file hoặc JWT không? Base64URL, quyết định padding theo consumer.
  3. Bản thân giá trị có là một component URL không? Percent encode phần cần thiết.
  4. Dữ liệu lớn không? Trước tiên kiểm tra kênh có thực sự đòi văn bản hay không. Body nhị phân thô tiết kiệm 33%.
  5. Decoder nghiêm ngặt hay khoan dung? Khớp bảng chữ cái chuẩn hoặc URL-safe từ đầu đến cuối.
  6. Mục đích có phải giữ bí mật không? Mã hoá trước. Encoding không bao giờ là bí mật.

Công cụ liên quan

Base64 encoder/decoder của Patrache Studio xử lý cả biến thể chuẩn và URL-safe cục bộ, nên byte bạn nhập không rời khỏi trình duyệt — hữu ích khi soi token hoặc key nhỏ. Nếu payload là JWT, đọc kèm Khác biệt giữa format JSON, kiểm tra cú pháp và JSON Schema để kiểm claim đã decode một cách gọn gàng. Nếu giá trị Base64URL là ID ngắn phái sinh từ UUID, So sánh UUID v1, v4, v7 và thiết kế khoá chính DB cho biết tại sao đặc tính sắp xếp và index của byte gốc lại quan trọng trực tiếp.

Tài liệu tham khảo