Ba bài trước là hash và HMAC — các hàm một chiều để kiểm chứ không giấu dữ liệu. Giờ ta sang mã hóa đối xứng để thật sự giấu nội dung: AES, tiêu chuẩn mã hóa được dùng ở khắp nơi (TLS, mã hóa ổ đĩa, file...). Nhưng có một cạm bẫy chết người: AES một mình chưa đủ — bạn phải chọn đúng chế độ (mode). Chọn sai chế độ (điển hình là ECB) thì dữ liệu vẫn được "mã hóa" nhưng lộ nguyên mẫu. Bài này đo tận byte để thấy vì sao. Chạy thật bằng openssl 3.0 AES-256.
AES mã hóa từng khối — chế độ quyết định cách ghép
AES là block cipher: nó mã hóa đúng một khối 16 byte mỗi lần. Dữ liệu dài hơn 16 byte phải chia thành nhiều khối, và chế độ quyết định cách xử lý các khối đó. Đây là chỗ khác biệt an toàn nằm:
- ECB (Electronic Codebook): mã hóa mỗi khối độc lập bằng cùng khóa. Hệ quả tai hại: hai khối bản rõ giống nhau cho ra hai khối bản mã giống hệt nhau → lộ mẫu dữ liệu. Đây là nguồn của "ảnh chim cánh cụt" kinh điển (mã hóa ảnh bằng ECB vẫn nhìn rõ hình). Đừng bao giờ dùng ECB.
- CBC (Cipher Block Chaining): mỗi khối được XOR với khối mã của khối trước rồi mới mã hóa (khối đầu XOR với IV). Nhờ chuỗi hóa này, khối giống nhau cho ra mã khác nhau — không lộ mẫu. Nhưng CBC không tự xác thực (không phát hiện sửa đổi).
- GCM (Galois/Counter Mode): vừa mã hóa vừa tạo tag xác thực (authenticated encryption) — phát hiện được sửa đổi. Đây là khuyến nghị mặc định ngày nay (bài sau).
openssl enc -aes-256-cbc -K <key> -iv <iv> -in f -out f.enc
openssl enc -aes-256-ecb -K <key> -in f -out f.enc # chỉ để MINH HỌA cái sai

Hình 1: AES mã hóa từng khối 16 byte; chế độ quyết định cách ghép. ECB độc lập từng khối (lộ mẫu, đừng dùng), CBC chuỗi hóa với IV, GCM có xác thực. IV ngẫu nhiên khiến cùng bản rõ ra bản mã khác.
Đo thật: ECB lộ mẫu, CBC thì không
Tạo dữ liệu có mẫu lặp — bốn khối 16 byte giống hệt nhau — rồi mã hóa bằng ECB và CBC, xem từng khối bản mã:

Hình 2: Bốn khối bản rõ giống hệt (59455543...). ECB cho bốn khối mã giống hệt (2b1d0fc6...) — lộ ngay có 4 khối trùng. CBC cho bốn khối mã khác nhau. Đổi IV: khối 1 từ c9f513e5... thành 2cc2348a....
Kết quả phơi bày đúng vấn đề:
- Đầu vào: 4 khối
59455543415543485559454e5449454egiống hệt. - ECB: 4 khối mã
2b1d0fc6e743786db106e5460fcf2d64giống hệt nhau. Kẻ tấn công nhìn bản mã biết ngay "có 4 khối bản rõ trùng nhau" — thông tin bị rò rỉ dù đã "mã hóa". Với ảnh, cấu trúc hình vẫn hiện; với dữ liệu có cấu trúc (bản ghi, template), mẫu lặp lộ hết. - CBC: 4 khối mã hoàn toàn khác nhau (
c9f513e5...,403d0d6d...,c801b1e2...,3d5fe72d...) dù đầu vào giống hệt — nhờ mỗi khối phụ thuộc khối trước. Không lộ mẫu. - Vai trò IV: mã hóa cùng dữ liệu + cùng khóa hai lần với IV khác nhau, khối 1 ra
c9f513e5...(IV #1) vs2cc2348a...(IV #2). IV ngẫu nhiên mỗi lần đảm bảo cùng một bản rõ mã hóa nhiều lần ra bản mã khác nhau — không lộ việc "hai lần này gửi cùng nội dung".
Đánh đổi và lưu ý
IV không bí mật nhưng phải duy nhất và không đoán được. IV được gửi kèm bản mã (thường ghép ở đầu) — nó không cần giấu. Nhưng dùng lại IV với cùng khóa là lỗi nghiêm trọng: CBC dùng lại IV làm lộ khối đầu giống nhau; GCM dùng lại nonce thì phá vỡ hoàn toàn (lộ khóa xác thực). Luôn sinh IV/nonce ngẫu nhiên mật mã mỗi lần mã hóa (bài random).
CBC cần padding và không tự xác thực → dễ dính padding oracle. CBC phải đệm (padding) cho đủ khối, và nếu bạn để lộ lỗi padding khác với lỗi khác, kẻ tấn công giải mã được (padding oracle attack — từng hạ nhiều hệ thống thật). Vì CBC không xác thực, phải kèm HMAC (encrypt-then-MAC) hoặc — tốt hơn — dùng thẳng GCM vốn gộp cả mã hóa và xác thực. Đó là lý do bài sau chuyển hẳn sang GCM.
Đừng tự ghép mật mã. openssl enc tiện để học, nhưng trong ứng dụng thật hãy dùng thư viện mật mã cấp cao (libsodium, cryptography của Python, Tink) — chúng chọn chế độ an toàn, quản IV/nonce, và gắn xác thực giúp bạn, tránh đúng những bẫy trên.
Ba ý mang về
- AES mã hóa từng khối 16 byte; chế độ quyết định an toàn: ECB mã mỗi khối độc lập nên khối giống nhau ra mã giống hệt — đo thật, 4 khối trùng ra 4 mã
2b1d0fc6...y hệt, lộ mẫu (ảnh chim cánh cụt). Đừng dùng ECB. - CBC chuỗi hóa với IV nên cùng đầu vào ra mã khác nhau (đo thật 4 khối mã khác biệt); IV ngẫu nhiên mỗi lần khiến cùng bản rõ mã ra bản mã khác — IV không bí mật nhưng phải duy nhất, không đoán được.
- CBC không tự xác thực (dễ padding oracle) → ưu tiên GCM: dùng authenticated encryption gộp mã hóa + xác thực; và trong ứng dụng thật dùng thư viện mật mã cấp cao thay vì tự ghép.
Nguồn
- NIST SP 800-38A (chế độ block cipher: ECB/CBC...) và minh họa "ECB penguin": https://csrc.nist.gov/pubs/sp/800/38/a/final
- OpenSSL docs — openssl-enc: https://docs.openssl.org/3.0/man1/openssl-enc/
Phần sau ta đi vào chế độ khuyến nghị: AES-GCM — authenticated encryption vừa giấu vừa chống sửa, vai trò của tag và nonce, và vì sao nonce không bao giờ được lặp.