Suốt sê-ri này, mọi thứ đều dựa vào một thứ tưởng chừng tầm thường: số ngẫu nhiên không đoán được. Khóa AES, salt mật khẩu, IV/nonce, token phiên, mã OTP, khóa riêng — tất cả chỉ an toàn khi được sinh từ nguồn ngẫu nhiên thật sự khó đoán. Đây là nơi vô số lỗ hổng nghiêm trọng bắt nguồn: lập trình viên dùng random() thường (vốn tất định) để sinh giá trị bảo mật. Bài này đo thật khác biệt giữa PRNG thường và CSPRNG, và vì sao chọn sai là sập cả hệ mật mã. Chạy thật bằng python3 và openssl.
Hai loại "ngẫu nhiên" khác nhau hoàn toàn
- PRNG thường (
random,Math.random,rand): tất định — sinh dãy số từ một seed. Cùng seed cho cùng dãy. Nhanh, phân bố đẹp, hoàn hảo cho game, mô phỏng, xáo trộn dữ liệu. Nhưng vì tất định và (với nhiều thuật toán như Mersenne Twister) đảo ngược được nếu quan sát đủ output → tuyệt đối không dùng cho bảo mật. - CSPRNG (Cryptographically Secure PRNG:
secrets,os.urandom,openssl rand,SecureRandom): lấy entropy từ hệ điều hành (/dev/urandom), không seed thủ công, output không đoán được kể cả khi biết mọi output trước đó → dùng cho mọi giá trị bảo mật.
# CSPRNG:
python3 -c 'import secrets; print(secrets.token_hex(16))'
openssl rand -hex 16
head -c 16 /dev/urandom | xxd -p

Hình 1: PRNG thường tất định từ seed (đoán được → cấm dùng bảo mật); CSPRNG lấy entropy HĐH, không đoán được. Mọi giá trị bí mật (token, salt, IV, nonce, khóa) phải từ CSPRNG.
Đo thật: tất định vs không đoán được

Hình 2: random.seed(42) cho [2824, 1409, 5506, 5012] hai lần giống hệt. Token sinh bằng random với seed=timestamp tái tạo được (5e7982e4d51b9992 cả hai lần). CSPRNG (secrets, os.urandom, openssl rand) cho giá trị khác mỗi lần, không đoán được.
Kết quả phơi bày rủi ro:
- PRNG tất định:
random.seed(42)rồi lấy 4 số cho[2824, 1409, 5506, 5012]— chạy lại với cùng seed ra y hệt. Đây là tính năng (tái lập được cho test/mô phỏng), nhưng là lỗ hổng nếu dùng sinh giá trị bí mật. - Token tái tạo được: sinh một token 16 ký tự bằng
randomvớiseed = timestamp— hai lần cùng seed ra5e7982e4d51b9992giống hệt. Nếu app seed bằng thời gian (rất phổ biến), kẻ tấn công đoán khoảng thời gian bạn sinh token, thử vài nghìn seed, tái tạo đúng token/OTP/mã reset mật khẩu. Đã có nhiều sự cố thật kiểu này. - CSPRNG không đoán được:
secrets.token_hex(16)chod81e68f1...rồi356fd156...— khác nhau mỗi lần, không seed, không tái tạo.os.urandomvàopenssl randtương tự. Đây là nguồn đúng cho mọi giá trị bảo mật.
Đánh đổi và lưu ý
Dùng đúng hàm cho đúng việc. PRNG thường không xấu — nó nhanh và tái lập được, hoàn hảo cho mô phỏng, game, chia batch, xáo trộn dữ liệu huấn luyện. CSPRNG chậm hơn chút và không tái lập, dành cho bảo mật. Đừng dùng CSPRNG cho mô phỏng cần tái lập, và tuyệt đối đừng dùng PRNG cho token/khóa. Quy tắc: hỏi "nếu kẻ tấn công đoán được giá trị này thì có hại không?" — có thì CSPRNG.
Biết hàm nào là CSPRNG trong ngôn ngữ của bạn. Python: secrets / os.urandom (không phải random). Java: SecureRandom (không phải Random / Math.random). Node: crypto.randomBytes / crypto.randomUUID (không phải Math.random). Go: crypto/rand (không phải math/rand). Nhầm hai họ này là lỗi kinh điển — tên rất giống nhau nên dễ import nhầm.
Đủ độ dài và đủ entropy. Token/khóa phải đủ dài để không brute-force được: token phiên ≥ 128 bit (16 byte), khóa AES 256 bit (32 byte). /dev/urandom trên hệ hiện đại luôn đủ entropy sau khi khởi động; đừng dùng /dev/random (có thể block) trừ nhu cầu đặc biệt. Trong container/VM mới tinh, chú ý entropy lúc boot sớm.
Ba ý mang về
- PRNG thường tất định — cấm dùng cho bảo mật: đo thật,
random.seed(42)ra dãy y hệt hai lần, token seed=timestamp tái tạo được (5e7982e4d51b9992) — kẻ tấn công đoán seed là đoán ra token/OTP/mã reset. - CSPRNG không đoán được, dùng cho mọi giá trị bí mật:
secrets/os.urandom/openssl randcho giá trị khác mỗi lần, không seed — dùng cho token, salt, IV/nonce, khóa, mã OTP. - Chọn đúng hàm và đủ độ dài: biết hàm CSPRNG trong ngôn ngữ (Python
secrets, JavaSecureRandom, Nodecrypto, Gocrypto/rand— không nhầm với họrandom/math); token ≥16 byte, khóa AES 32 byte; hỏi "kẻ tấn công đoán được có hại không?" để chọn.
Nguồn
- Python docs — secrets — Generate secure random numbers: https://docs.python.org/3/library/secrets.html
- OWASP — Cryptographic Storage / Insecure Randomness: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
Phần sau ta ghép mọi mảnh lại thành việc thực tế: mã hóa một file bằng mật khẩu — AES với khóa dẫn từ mật khẩu qua PBKDF2, đo thật bằng openssl enc.