ChaCha20-Poly1305 là thuật toán mã hoá đứng sau phần lớn kết nối TLS 1.3 hiện đại, đặc biệt trên điện thoại. Nó thường được giới thiệu bằng một câu gọn: "nhanh hơn AES". Câu đó đúng có điều kiện — và điều kiện ấy quan trọng đến mức bỏ qua nó là hiểu sai cả bài toán. Bài này (phần 14 loạt Mật mã) dựng demo thật bằng python cryptography để thấy hai điều: cơ chế AEAD (mã hoá kèm xác thực) khiến nó an toàn hơn "chỉ mã hoá", và con số tốc độ thật khi đo trên một CPU có tăng tốc AES phần cứng — kết quả có thể làm bạn ngạc nhiên.

Hai nửa: ChaCha20 mã hoá, Poly1305 xác thực

Tên ghép của nó phản ánh đúng cấu tạo:

  • ChaCha20 là mã hoá dòng (stream cipher): từ khoá 256-bit, một nonce 96-bit và bộ đếm, nó sinh ra một chuỗi khoá giả ngẫu nhiên rồi XOR với bản rõ. Nhanh, đơn giản, không cần bảng tra như AES.
  • Poly1305 là hàm xác thực (MAC): tính một "tag" 16 byte trên ciphertext (và dữ liệu đi kèm) để bên nhận phát hiện mọi thay đổi.

Gộp lại thành AEAD (Authenticated Encryption with Associated Data): một API vừa giấu nội dung, vừa chống sửa, trong một lời gọi.

aead = ChaCha20Poly1305(key)              # key 32 byte
ct   = aead.encrypt(nonce, plaintext, aad)  # ct = ciphertext + tag 16 byte
pt   = aead.decrypt(nonce, ct, aad)         # tag sai -> ném lỗi, KHÔNG trả rác

aad (associated data) là dữ liệu được xác thực nhưng không mã hoá — ví dụ id giao dịch, header giao thức: bạn cần chúng hiện rõ nhưng vẫn muốn chống giả mạo.

Ảnh chụp đoạn mã nền tối minh hoạ ChaCha20-Poly1305 mã hoá dòng cộng xác thực trong một bước AEAD, hai nửa ChaCha20 mã hoá Poly1305 xác thực ChaCha20 mã hoá dòng sinh chuỗi khoá giả ngẫu nhiên từ khoá 256-bit nonce 96-bit bộ đếm rồi XOR với bản rõ Poly1305 tính tag xác thực 16 byte cho ciphertext cộng AAD gộp lại AEAD vừa giấu nội dung vừa chống sửa, dùng python cryptography aead bằng ChaCha20Poly1305 key ct bằng aead encrypt nonce plaintext aad pt bằng aead decrypt nonce ct aad sai tag ném lỗi aad dữ liệu đi kèm được xác thực nhưng không mã hoá, vì sao AEAD quan trọng hơn chỉ mã hoá mã hoá dòng thuần không tag lật 1 bit ciphertext lật đúng 1 bit bản rõ giải mã vẫn thành công rác AEAD mọi thay đổi 1 bit tag sai từ chối giải mã nonce không được lặp với cùng khoá lặp là vỡ bảo mật

Hình 1: ChaCha20 mã hoá dòng bằng XOR chuỗi khoá; Poly1305 sinh tag 16 byte; gộp thành AEAD. Điểm cốt lõi: mã hoá dòng thuần không phát hiện sửa, còn AEAD từ chối mọi ciphertext bị đổi.

Đo thật: AEAD bắt mọi thay đổi

Đây là lý do AEAD vượt trội "chỉ mã hoá". Với mã hoá dòng thuần (không tag), lật một bit ciphertext sẽ lật đúng một bit bản rõ và bên nhận vẫn "giải mã thành công" ra rác mà không hề biết. Với AEAD, tag Poly1305 bắt ngay. Mình mã hoá "Chuyen 5 trieu cho tai khoan 007" rồi thử phá:

Ảnh chụp bảng kết quả chạy thật python cryptography output thật, một mã hoá AEAD ciphertext dài hơn 16 byte bằng tag Poly1305 plaintext 32 byte ct 48 byte thừa đúng 16 byte tag ct cộng tag hex d7676c19 giải mã đúng Chuyen 5 trieu cho tai khoan 007, hai lật 1 bit ciphertext bị bắt tampered 0 XOR 0x01 từ chối InvalidTag tag không khớp phát hiện sửa, ba đổi AAD id giao dịch cũng bị bắt ct nguyên vẹn đổi tx-id 88123 thành tx-id 99999 từ chối InvalidTag AAD được xác thực, bốn tốc độ 50 MB máy này ARM64 có tăng tốc AES aes pmull ChaCha20-Poly1305 53.2 ms 939 MB trên giây AES-256-GCM 24.9 ms 2004 MB trên giây nhanh hơn ở đây AES thắng vì CPU có lệnh AES phần cứng trên thiết bị không có điện thoại cũ IoT thì ChaCha20 mới nhanh hơn AES

Hình 2: Chạy thật — bản rõ 32 byte → ciphertext 48 byte (thừa đúng 16 byte tag); giải mã đúng cho lại thông điệp gốc; lật 1 bit ciphertext → InvalidTag; đổi AAD → InvalidTag. Tốc độ 50 MB: ChaCha20 939 MB/s, AES-GCM 2004 MB/s trên máy có tăng tốc AES.

Ba kết quả đo thật:

  • Ciphertext dài hơn bản rõ đúng 16 byte — đó chính là tag Poly1305 đính kèm.
  • Lật 1 bit ciphertext → InvalidTag: thư viện ném lỗi thay vì trả về dữ liệu rác. Đây là hành vi đúng của AEAD: thà không giải mã còn hơn trao ra nội dung đã bị can thiệp.
  • Đổi AAD (tx-id:88123 → tx-id:99999) → InvalidTag: dù ciphertext nguyên vẹn, chỉ cần đổi dữ liệu đi kèm là tag không còn khớp. AAD được ràng buộc mật mã vào thông điệp.

Sự thật về tốc độ: "nhanh hơn AES" có điều kiện

Đây là phần dễ bị nói sai nhất. Đo mã hoá 50 MB trên chính máy chạy demo (ARM64):

ChaCha20-Poly1305:  53.2 ms  ( 939 MB/s)
AES-256-GCM:        24.9 ms  (2004 MB/s)   <- nhanh hơn gấp ~2 lần

AES-GCM nhanh hơn ChaCha20 ở đây. Vì sao trái với câu "ChaCha20 nhanh hơn"? Vì CPU này có lệnh AES phần cứng — kiểm /proc/cpuinfo thấy cờ aes và pmull (ARMv8 Crypto Extensions). Khi có mạch AES chuyên dụng, AES-GCM chạy nhanh vượt trội (openssl đo tới ~7,9 GB/s ở khối lớn). ChaCha20 thắng chỉ khi không có tăng tốc đó — trên điện thoại đời cũ, vi điều khiển, thiết bị IoT, hoặc CPU không có AES-NI. ChaCha20 dùng toàn phép cộng/xoay/XOR nên nhanh và ổn định ở mọi phần cứng, không phụ thuộc lệnh đặc biệt.

Nói cách khác: ChaCha20-Poly1305 không phải "nhanh hơn AES tuyệt đối", mà là "nhanh và an toàn ở mọi nơi, kể cả nơi AES chậm". Đó mới là lý do TLS 1.3 giữ cả hai và để client chọn theo phần cứng của nó.

Đánh đổi cần cân nhắc

Nonce không bao giờ được lặp với cùng một khoá. Đây là ràng buộc sống còn của mọi mã hoá dòng, kể cả ChaCha20. Hai thông điệp mã hoá cùng (khoá, nonce) sẽ để lộ XOR của hai bản rõ — vỡ bảo mật. Dùng nonce ngẫu nhiên 96-bit (rủi ro trùng thấp nhưng vẫn nên đếm) hoặc bộ đếm tăng dần được quản lý cẩn thận. Có biến thể XChaCha20 với nonce 192-bit để nonce ngẫu nhiên an toàn hơn hẳn.

Chọn AES-GCM hay ChaCha20 theo phần cứng, đừng theo cảm tính. Trên máy chủ x86/ARM hiện đại có AES-NI, AES-256-GCM thường nhanh hơn (như đo trên). Trên thiết bị yếu không có tăng tốc AES, ChaCha20-Poly1305 nhanh hơn và tránh được cả một lớp tấn công kênh phụ (cache-timing) mà AES phần mềm dễ mắc. Thư viện TLS tốt tự thương lượng — bạn chỉ cần bật cả hai.

AEAD không thay thế trao khoá. ChaCha20-Poly1305 giả định hai bên đã có chung khoá bí mật. Làm sao có khoá đó một cách an toàn là bài toán khác (trao khoá Diffie-Hellman/ECDH — chủ đề khác của loạt này). AEAD lo phần "sau khi đã có khoá".

Ba ý mang về

  1. ChaCha20-Poly1305 là AEAD: ChaCha20 mã hoá dòng (XOR chuỗi khoá), Poly1305 sinh tag 16 byte xác thực — đo thật ciphertext dài hơn bản rõ đúng 16 byte, và giải mã sai tag thì ném lỗi chứ không trả rác.
  2. Mọi thay đổi đều bị bắt: đo thật, lật 1 bit ciphertext → InvalidTag, đổi AAD (id giao dịch) dù ciphertext nguyên vẹn → cũng InvalidTag. Đây là điều mã hoá dòng thuần không làm được.
  3. "Nhanh hơn AES" là có điều kiện: đo thật trên CPU có tăng tốc AES, AES-256-GCM (2004 MB/s) nhanh gấp đôi ChaCha20 (939 MB/s); ChaCha20 chỉ thắng khi không có phần cứng AES — nên nó là lựa chọn "nhanh và an toàn ở mọi nơi", không phải "nhanh nhất tuyệt đối".

Nguồn

Phần sau ta xem xác thực hai chiều bằng chứng chỉ: mTLS (mutual TLS) — không chỉ client xác minh server, mà server cũng đòi client trình chứng chỉ hợp lệ, và cách nó khoá chặt giao tiếp giữa các dịch vụ.