Bỏ qua đến nội dung
LeedyBox
Ủng hộ

Giải thích Base64, mã hóa URL và JWT

Ba thứ này xuất hiện ở bất cứ nơi nào dữ liệu phải đi dưới dạng văn bản: trong URL, header HTTP, JSON và token đăng nhập. Không cái nào che giấu được gì. Hướng dẫn này giải thích cách từng cái hoạt động, nơi bạn gặp chúng và những lỗi gây ra phần lớn sự cố.

Mã hóa (encoding) không phải là mật mã hóa

Encoding thay đổi cách viết dữ liệu để nó đi qua được một kênh chỉ chấp nhận một số ký tự nhất định. Ai cũng có thể đảo ngược, vì không có khóa nào. Mật mã hóa (encryption) cũng biến đổi dữ liệu, nhưng chỉ người có khóa mới khôi phục được. Hàm băm lại là thứ khác: một dấu vân tay một chiều không thể đảo ngược, như giải thích trong Giải thích về hàm băm.

Base64 và mã hóa URL là encoding. JWT thường được ký, chứng minh ai đã phát hành và nội dung không bị sửa, nhưng bản thân nội dung chỉ được encode. Nếu một giá trị cần giữ bí mật, nó cần mật mã hóa chứ không phải một trong các định dạng này.

Base64 hoạt động thế nào

Base64 đọc dữ liệu theo từng nhóm ba byte (24 bit) và chia thành bốn nhóm sáu bit. Mỗi nhóm là một số từ 0 đến 63, được viết thành một ký tự trong bảng chữ cái 64 ký tự: A–Z cho 0–25, a–z cho 26–51, 0–9 cho 52–61, rồi đến + và /. Từ “Man” cho thấy các bước:

BướcMan
Giá trị byte7797110
Bit010011010110000101101110
Bốn nhóm 6 bit010011 · 010110 · 000101 · 101110 = 19 · 22 · 5 · 46
Base64T W F u → TWFu

Khi độ dài đầu vào không chia hết cho ba, nhóm cuối được bù thêm và đánh dấu bằng ký tự đệm: dư một byte cho hai ký tự và ==, dư hai byte cho ba ký tự và =. “Ma” thành TWE= và “M” thành TQ==.

Vì cứ ba byte thành bốn ký tự, Base64 làm dữ liệu lớn thêm khoảng một phần ba, cộng thêm dấu xuống dòng mà một số định dạng chèn vào. Một bức ảnh 3 MB gửi làm tệp đính kèm email sẽ truyền đi dưới dạng khoảng 4 MB văn bản, và một ảnh nhúng vào stylesheet dưới dạng data URL nặng hơn tệp gốc khoảng một phần ba.

Bạn gặp Base64 ở đâu

  • Tệp đính kèm email (MIME) là văn bản Base64, ngắt dòng ở 76 ký tự.
  • Data URL như data:image/png;base64,iVBORw0… đưa một tệp nhỏ thẳng vào HTML hoặc CSS.
  • Xác thực HTTP Basic gửi user:password dưới dạng Base64 trong header Authorization. Việc này chỉ che mật khẩu khỏi ánh nhìn lướt qua, nên chỉ an toàn khi dùng HTTPS.
  • Chứng chỉ và khóa trong tệp PEM là Base64 nằm giữa các dòng -----BEGIN …----- và -----END …-----, ngắt ở 64 ký tự.
  • JSON và XML mang các giá trị nhị phân, như chữ ký hay ảnh thu nhỏ, dưới dạng chuỗi Base64 vì chúng không có kiểu nhị phân.

Base64URL và ký tự đệm

Dấu + và / của Base64 chuẩn có ý nghĩa trong URL và đường dẫn tệp, nên bảng chữ cái thứ hai là Base64URL dùng - và _ thay thế. Nó cũng thường bỏ ký tự đệm =, vì bộ giải mã có thể tự tính độ dài. JWT, nhiều token API và một số tên tệp dùng Base64URL. Để đổi Base64 chuẩn thành Base64URL, thay + bằng -, / bằng _ và bỏ các dấu = ở cuối; để đổi ngược, hoàn tác các thay thế và thêm = cho đến khi độ dài chia hết cho bốn.

Mã hóa phần trăm trong URL

URL chỉ được chứa một tập ký tự giới hạn, và một số ký tự tạo nên cấu trúc địa chỉ: / và ? tách đường dẫn và truy vấn, & và = tách các tham số, # bắt đầu phân đoạn. Khi một giá trị chứa các ký tự này, hoặc bất cứ thứ gì ngoài ASCII, nó được mã hóa phần trăm: mỗi byte trong dạng UTF-8 của nó được viết thành % kèm hai chữ số thập lục phân. “café” thành caf%C3%A9, vì é là hai byte C3 A9.

Chữ cái, chữ số và - . _ ~ không bao giờ cần mã hóa. Đây là những ký tự hay gây rắc rối nhất:

Ký tựSau khi mã hóaVì sao quan trọng
dấu cách%20Không được phép trong URL
&%26Sẽ bắt đầu một tham số mới
=%3DSẽ kết thúc tên tham số
?%3FSẽ bắt đầu phần truy vấn
#%23Sẽ bắt đầu phân đoạn, phần không được gửi lên máy chủ
/%2FSẽ thêm một cấp đường dẫn
+%2BĐược đọc là dấu cách trong dữ liệu biểu mẫu
%%25Sẽ bắt đầu một chuỗi thoát

Hãy mã hóa từng giá trị riêng rồi mới ghép URL. Trong JavaScript, encodeURIComponent làm việc này cho một giá trị và mã hóa cả / ? & = #, còn encodeURI dành cho cả địa chỉ nên giữ nguyên các ký tự đó. Dùng encodeURI cho một giá trị đơn lẻ là nguyên nhân phổ biến khiến chuỗi truy vấn bị hỏng.

Dấu cộng, dấu cách và mã hóa hai lần

Biểu mẫu HTML gửi các trường dưới dạng application/x-www-form-urlencoded, trong đó dấu cách thành +. Trong cú pháp URL chung, + chỉ là dấu cộng. Vì vậy, một tìm kiếm “C++” gửi dưới dạng q=C++ sẽ đến máy chủ giải mã dữ liệu biểu mẫu thành “C” kèm hai dấu cách; đúng ra phải gửi là q=C%2B%2B. Khi tự giải mã một chuỗi truy vấn, hãy tìm hiểu xem nó được tạo theo quy tắc nào trước.

Mã hóa hai lần xảy ra khi một giá trị đã được mã hóa lại bị mã hóa lần nữa: %20 thành %2520, vì chính dấu % thành %25. Nếu thấy %25 theo sau là hai chữ số thập lục phân trong URL, thì đâu đó trong chuỗi xử lý đã mã hóa hai lần; hãy giải mã một lần và tìm bước thừa nằm ở đâu.

Bên trong JWT có gì

JSON Web Token (JWT) mang các khẳng định (claim), như người dùng là ai và token có hiệu lực đến khi nào, từ hệ thống phát hành đến các hệ thống chấp nhận nó. Dạng có chữ ký phổ biến gồm ba phần Base64URL ngăn cách bằng dấu chấm: header, payload và chữ ký. Sau khi giải mã, hai phần đầu là JSON bình thường:

Header:  {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "user-42", "role": "editor", "iat": 1767225600, "exp": 1767229200}

Tiêu chuẩn (RFC 7519) định nghĩa một số claim đăng ký sẵn; ứng dụng tự thêm claim riêng, như role ở trên.

ClaimÝ nghĩa
issBên phát hành: ai tạo ra token
subChủ thể: thường là ID người dùng hoặc tài khoản
audĐối tượng: dịch vụ mà token dành cho
exp, nbf, iatHết hạn, không trước thời điểm, thời điểm phát hành: thời gian Unix tính bằng giây
jtiID của token, dùng để phát hiện dùng lại hoặc thu hồi một token cụ thể

Vì payload chỉ được encode, ai có token cũng đọc được nó. Đừng đưa dữ liệu cá nhân hay bí mật vào, và giữ token gọn nhẹ: token đi kèm mọi yêu cầu, và nhiều máy chủ giới hạn kích thước header.

Có chữ ký không có nghĩa là bí mật: cách kiểm tra JWT

Chữ ký được tính trên header và payload. Với HS256, bên phát hành và bên nhận dùng chung một khóa bí mật và dùng HMAC với SHA-256. Với RS256 hoặc ES256, bên phát hành ký bằng khóa riêng và bên nhận kiểm tra bằng khóa công khai tương ứng, nên họ không thể tự tạo token.

Máy chủ nhận token phải xác minh chữ ký bằng thuật toán và khóa mong đợi, và kiểm tra exp, nbf, iss, aud, trước khi tin bất kỳ claim nào. Chỉ giải mã, như công cụ LeedyBox làm, bỏ qua tất cả các bước đó: nó cho thấy token ghi gì, chứ không cho biết token có thật hay không. Thư viện cũng phải từ chối thuật toán “none”, đánh dấu token không ký, trừ khi đã dự kiến dùng token không ký. Khi chính nội dung cần được giấu, token được mật mã hóa (JWE); khi đó nó có năm phần và không thể đọc nếu không có khóa.

Sự cố thường gặp và cách khắc phục

Triệu chứngNguyên nhân có thểCách khắc phục
Giải mã Base64 ra chữ lộn xộnDữ liệu là nhị phân, hoặc văn bản ở bảng mã khácKiểm tra thứ đã được mã hóa; lưu dữ liệu nhị phân thành tệp
Báo “ký tự không hợp lệ” khi giải mãTiền tố data:, dấu ngoặc kép hoặc bảng chữ cái Base64URLXóa mọi thứ trước dấu phẩy; dùng bộ giải mã chấp nhận - và _
Tham số đến nơi bị cắt cụtCó & hoặc # chưa mã hóa trong giá trịMã hóa từng giá trị bằng encodeURIComponent
Có %2520 trong URLGiá trị bị mã hóa hai lầnChỉ mã hóa một lần, tại chỗ ghép URL
Token bị từ chối dù exp còn ở tương laiSai đối tượng hoặc bên phát hành, lệch đồng hồ, hoặc khác khóaSo aud, iss và khóa ký với những gì máy chủ mong đợi
Ngày hết hạn ở hàng nghìn năm sauexp ghi bằng mili giây thay vì giâyLưu exp, nbf và iat theo giây

Câu hỏi thường gặp

Base64 có bảo vệ được mật khẩu hay khóa API không?

Không. Ai cũng đảo ngược được Base64 trong một giây. Thông tin đăng nhập mã hóa Base64, chẳng hạn trong tệp cấu hình hoặc URL, cũng lộ như văn bản thường.

Tôi có thể đưa dữ liệu cá nhân vào payload của JWT không?

Nên tránh. Ai có token cũng đọc được payload, kể cả tiện ích trình duyệt, nhật ký và proxy nhìn thấy yêu cầu. Hãy đặt một ID vào token và tra cứu chi tiết trên máy chủ.

Mã hóa URL có chống được tấn công injection không?

Chỉ theo nghĩa giá trị không thể phá vỡ cấu trúc URL. Máy chủ vẫn phải kiểm tra và thoát giá trị đã giải mã theo nơi sử dụng nó, như SQL, HTML hay lệnh shell.

Vì sao chuỗi Base64 của tôi dài hơn tệp gốc?

Cứ ba byte thành bốn ký tự, nên văn bản lớn hơn khoảng 33%, và dấu xuống dòng thêm một chút nữa. Với tệp lớn, gửi trực tiếp các byte sẽ hiệu quả hơn dùng Base64.