Mật mã học nghe hàn lâm, nhưng lập trình viên gặp nó mỗi ngày: lưu mật khẩu, ký token, kiểm tra file tải về, và — như sê-ri Git đã thấy — định danh nội dung. Sê-ri này tiếp cận mật mã theo cách thực dụng: mỗi khái niệm demo bằng openssl chạy được, không sa vào toán. Bắt đầu từ viên gạch nền tảng: hàm băm mật mã. Nó biến dữ liệu bất kỳ thành một chuỗi cố định như "dấu vân tay". Điểm cần khắc ngay: băm KHÔNG phải mã hóa — không có khóa, không giải ngược được. Đo thật bằng openssl 3.0.
Bốn tính chất của hàm băm mật mã
Một hàm băm mật mã tốt (như SHA-256) có bốn tính chất:
- Xác định (deterministic): cùng một đầu vào luôn cho cùng một digest. Nhờ vậy digest dùng để định danh dữ liệu.
- Kích thước cố định: đầu vào 1 byte hay 10 GB đều cho digest cùng độ dài (SHA-256 luôn 256 bit = 64 ký tự hex).
- Một chiều (pre-image resistance): tính digest từ đầu vào thì dễ; tìm lại đầu vào từ digest thì bất khả thi trên thực tế.
- Chống va chạm (collision resistance): khó tìm được hai đầu vào khác nhau cho cùng một digest.
Kèm theo là hiệu ứng avalanche: đổi một bit ở đầu vào làm khoảng một nửa số bit của digest đổi theo — digest mới trông hoàn toàn không liên quan digest cũ.
echo -n "xin chao" | openssl dgst -sha256 # băm một chuỗi
openssl dgst -sha256 tenfile # băm nội dung file
sha256sum tenfile # tương đương (coreutils)

Hình 1: Bốn tính chất của hàm băm mật mã và hiệu ứng avalanche. Hash không phải mã hóa — nó không giấu dữ liệu, dùng để kiểm toàn vẹn, định danh nội dung, và làm nền cho HMAC/chữ ký/lưu mật khẩu. Đo bằng openssl dgst.
Đo thật bốn tính chất

Hình 2: Cùng "xin chao" luôn cho f9b197ff...decd0b27. Đổi một ký tự (o→a): f9b197ff... thành f1a981ca..., chỉ 2/64 ký tự hex trùng — avalanche. 1 ký tự và 10 MB đều ra 64 hex. MD5 128 bit vs SHA-256 256 bit.
Kết quả xác nhận từng tính chất:
- Xác định: băm "xin chao" hai lần đều ra
f9b197ffdaa45e4fb217f865fe3a2bd8...decd0b27. Đây là lý do digest định danh được dữ liệu — Git dùng chính điều này (bài git-01). - Avalanche: đổi một ký tự (
xin chao→xin chaa), digest từf9b197ff...thànhf1a981ca...— chỉ 2/64 ký tự hex trùng nhau (kỳ vọng ngẫu nhiên là ~4). Không hề có "gần giống" — đổi tí xíu là khác hẳn. Nhờ vậy không suy được đầu vào bằng cách "dò dần". - Kích thước cố định: 1 ký tự và 10 MB đều cho 64 ký tự hex. Digest không tiết lộ độ dài đầu vào.
- SHA-256 vs MD5: MD5 cho 128 bit, SHA-256 cho 256 bit. MD5 (và SHA-1) đã bị tìm ra va chạm thực tế — kẻ tấn công tạo được hai file khác nhau cùng digest MD5, nên tuyệt đối không dùng MD5/SHA-1 cho mục đích bảo mật. Dùng SHA-256 (hoặc SHA-3, BLAKE2/3).
Hash dùng để làm gì — và KHÔNG để làm gì
Đúng: kiểm toàn vẹn (tải file kèm digest, băm lại để so — khớp là chưa bị sửa/hỏng); định danh nội dung (Git, dedupe, cache key); làm nền cho HMAC (bài sau), chữ ký số, và lưu mật khẩu (nhưng không dùng SHA trần — bài sau).
Sai: đừng tưởng hash "giấu" dữ liệu. Hash một dữ liệu có không gian nhỏ (số điện thoại, số CMND, "yes/no") thì kẻ tấn công băm hết mọi khả năng rồi so — lộ ngay. Hash không có khóa nên không bí mật; muốn giấu dữ liệu phải mã hóa (bài AES). Và đừng dùng hash trần để lưu mật khẩu (bài kế giải thích vì sao).
Đánh đổi và lưu ý
Chống va chạm phụ thuộc độ dài digest và thuật toán. SHA-256 hiện an toàn; MD5/SHA-1 thì không. Nhưng "an toàn hôm nay" không vĩnh viễn — thiết kế hệ thống nên cho phép đổi thuật toán băm (agility), đừng hardcode MD5 khắp nơi.
Hash nhanh là con dao hai lưỡi. SHA-256 được tối ưu để băm nhanh — tốt cho kiểm toàn vẹn (băm 10 MB tức thì), nhưng chính vì nhanh mà nó tệ cho lưu mật khẩu: kẻ tấn công cũng băm nhanh, thử hàng tỷ mật khẩu/giây. Lưu mật khẩu cần hàm cố ý chậm (bcrypt/argon2 — bài sau).
Encode ≠ hash ≠ mã hóa. Base64 là mã hóa biểu diễn (giải ngược được, không bí mật). Hash là một chiều. Mã hóa cần khóa và giải ngược được với khóa. Ba thứ khác nhau hoàn toàn — nhầm lẫn là nguồn của vô số lỗi bảo mật.
Ba ý mang về
- Hàm băm mật mã là dấu vân tay một chiều, kích thước cố định: đo thật, cùng đầu vào luôn cùng digest (định danh), đổi một ký tự cho digest khác hoàn toàn (avalanche, chỉ 2/64 hex trùng), 1 byte hay 10 MB đều ra 64 hex.
- Hash KHÔNG phải mã hóa: không khóa, không giải ngược, không giấu được dữ liệu không gian nhỏ — dùng cho toàn vẹn, định danh, nền của HMAC/chữ ký; muốn giấu dữ liệu phải mã hóa.
- Chọn đúng thuật toán và ngữ cảnh: MD5/SHA-1 đã vỡ va chạm — dùng SHA-256/SHA-3; và hash nhanh tốt cho toàn vẹn nhưng tệ cho mật khẩu (cần hàm cố ý chậm — bài sau).
Nguồn
- OpenSSL docs — openssl-dgst: https://docs.openssl.org/3.0/man1/openssl-dgst/
- NIST FIPS 180-4 (Secure Hash Standard) và ghi chú về va chạm MD5/SHA-1: https://csrc.nist.gov/pubs/fips/180-4/upd1/final
Phần sau ta trả lời câu hỏi vừa gợi: vì sao KHÔNG được lưu mật khẩu bằng SHA trần — salt, hàm cố ý chậm (bcrypt/argon2), và tấn công từ điển, đo thật chi phí băm.