- Trong Go 1.22, gói
math/rand hiện có và gói math/rand/v2 mới được giới thiệu đã được thay đổi để sử dụng bộ tạo số ngẫu nhiên an toàn về mặt mật mã. Nhờ đó cung cấp tính ngẫu nhiên tốt hơn, đồng thời có thể giảm đáng kể thiệt hại có thể xảy ra khi lập trình viên vô tình dùng math/rand thay vì crypto/rand.
Sự khác biệt giữa tính ngẫu nhiên thống kê và tính ngẫu nhiên mật mã
- Tính ngẫu nhiên thống kê phù hợp cho mô phỏng, lấy mẫu, phân tích số, các thuật toán ngẫu nhiên không mã hóa, kiểm thử ngẫu nhiên, xáo trộn đầu vào, random exponential backoff, v.v.
- Ngay cả các công thức toán học rất cơ bản và dễ tính toán cũng hoạt động đủ tốt cho những mục đích này. Tuy nhiên, một người quan sát biết thuật toán được dùng có thể dự đoán chuỗi tiếp theo sau khi xem một số lượng giá trị nhất định.
- Tính ngẫu nhiên mật mã phải thực sự hoàn toàn không thể dự đoán được, ngay cả khi đã quan sát các giá trị được tạo ra trước đó.
- Các giao thức mã hóa an toàn, khóa bí mật, thương mại hiện đại và quyền riêng tư trực tuyến đều phụ thuộc rất nhiều vào tính ngẫu nhiên mật mã.
Bộ tạo math/rand của Go 1
- Sử dụng phương pháp Linear-feedback shift register (LFSR).
- Có vấn đề là trạng thái nội bộ bị lộ hoàn toàn dưới dạng một vector gồm 607
uint64.
- Nếu đọc 607 giá trị từ bộ tạo, toàn bộ trạng thái sẽ bị lộ và có thể dự đoán các giá trị tiếp theo.
Bộ tạo PCG của math/rand/v2
- Sử dụng thuật toán PCG của Melissa O'Neill. Đây là LCG 128 bit có áp dụng hậu xử lý.
- Toàn bộ trạng thái là một số 128 bit duy nhất, và việc cập nhật được thực hiện bằng phép nhân và phép cộng 128 bit.
- Trong Go, theo đề xuất của O'Neill, hàm scramble dựa trên phép nhân được dùng thay cho kiểu dựa trên XOR để trộn bit mạnh hơn.
- Dù tính toán nhiều hơn bộ tạo Go 1, nó cần ít bộ nhớ hơn nhiều để lưu trạng thái, ít nhạy cảm hơn với giá trị trạng thái khởi tạo, và vượt qua cả những bài kiểm tra thống kê mà các bộ tạo khác không vượt qua được.
- Tuy vậy, PCG vẫn không phải là không thể dự đoán.
Tính ngẫu nhiên mật mã
- Về bản chất, hệ điều hành phải thu thập tính ngẫu nhiên thực từ nhiễu của các thiết bị vật lý.
- Khi thu thập đủ độ ngẫu nhiên (từ 256 bit trở lên), có thể mở rộng nó bằng hàm băm mật mã hoặc thuật toán mã hóa để tạo ra chuỗi số ngẫu nhiên có độ dài tùy ý.
- Gói
crypto/rand của Go trừu tượng hóa sự khác biệt giữa các giao diện hệ điều hành này và cung cấp cùng một giao diện là rand.Read.
Bộ tạo ChaCha8Rand
- Một bộ tạo mới được tạo ra bằng cách biến thể mật mã dòng ChaCha của DJB.
- Sử dụng ChaCha8, phiên bản 8 vòng. Nó nhanh hơn 2,5 lần so với ChaCha20 mà vẫn an toàn.
- Dùng seed 32 byte làm khóa ChaCha8. Cứ mỗi 16 block, 32 byte cuối của các block được tạo sẽ được dùng làm khóa cho 16 block tiếp theo để cung cấp forward secrecy.
rand.Float64, rand.N của math/rand/v2 luôn dùng bộ tạo này.
math/rand cũng dùng bộ tạo này. Tuy nhiên, nếu rand.Seed được gọi thì sẽ dùng bộ tạo Go 1.
- Runtime cũng dùng ChaCha8Rand khi chọn hash seed cho map mới.
Khắc phục sai sót bảo mật
- Go 1.22 giúp chương trình an toàn hơn mà không cần thay đổi mã bằng cách tăng cường
math/rand.
- Ví dụ, nếu vô tình dùng sai
math/rand Read để tạo khóa, v.v., thì trong Go 1.20 đó là một vấn đề bảo mật nghiêm trọng, còn trong Go 1.22 nó chỉ còn là một sai sót.
- Ngay cả với các mục đích trông không giống “mật mã” như tạo UUID hay cân bằng tải cho máy chủ frontend, việc dùng ChaCha8Rand cũng mang lại độ vững chắc cao hơn nhiều so với bộ tạo Go 1.
Hiệu năng
- ChaCha8Rand cho hiệu năng ở mức tương đương với bộ tạo Go 1 hoặc PCG.
- Trên mã 32 bit, ChaCha8Rand nhanh hơn PCG vì PCG cần phép nhân 128 bit.
- Nhờ thuật toán trong
math/rand/v2 tránh phép chia 64 bit, với thao tác N(1000) thì ChaCha8Rand hoặc PCG đôi khi còn nhanh hơn bộ tạo Go 1.
- Nhìn chung, ChaCha8Rand chậm hơn bộ tạo Go 1 nhưng không trường hợp nào chậm hơn quá 2 lần, và trên các máy chủ thông thường chênh lệch không vượt quá 3ns.
Ý kiến của GN⁺
- Việc áp dụng ChaCha8Rand trong Go 1.22 có thể xem là một ví dụ cải tiến rất mẫu mực ở cấp độ ngôn ngữ: tăng mạnh tính bảo mật trong khi mức suy giảm hiệu năng là tối thiểu. Điều ấn tượng là họ đã chặn tận gốc ở cấp độ ngôn ngữ những sai lầm mà lập trình viên thường xuyên mắc phải.
- Như bài viết cũng đề cập, những sai lầm kiểu này không chỉ giới hạn ở Go mà còn thường thấy ở các ngôn ngữ khác. Vì bảo mật hệ thống không nên phụ thuộc vào sai sót của lập trình viên, các ngôn ngữ khác cũng nên tiến tới hướng dùng bộ tạo số giả ngẫu nhiên mạnh về mặt mật mã ngay cả cho số ngẫu nhiên “toán học” như Go.
- Tuy nhiên, ChaCha8Rand không phù hợp để dùng cho các cryptographic primitive như
crypto_box hay xchacha20poly1305. Với các mục đích như vậy, vẫn phải trực tiếp dùng crypto/rand.
- Việc Go runtime chuyển sang dùng ChaCha8Rand cả trong khâu chọn hash seed cho map cũng hơi bất ngờ. Chưa rõ hash seed có nhất thiết cần số ngẫu nhiên mật mã hay không, nhưng điều đó cho thấy rõ ý thức bảo mật của nhóm phát triển trong việc chặn từ gốc các khả năng tấn công rắc rối.
- Khi chất lượng của
math/rand, vốn là gói mặc định ở cấp ngôn ngữ, đã được nâng cao, có lẽ trong tương lai các ứng dụng sẽ dùng trực tiếp math/rand thường xuyên hơn. Nếu trước đây một dự án phải dùng thư viện tạo số ngẫu nhiên riêng vì tính dễ đoán của math/rand, thì thay đổi lần này có thể mang lại lợi ích rõ rệt.
1 bình luận
Ý kiến trên Hacker News
Tóm tắt như sau:
Readcủa góimath/randbị deprecated, và đã xuất hiện các trường hợp sử dụng nhầm nó thay chocrypto/rand. Điều này dẫn đến sai lầm là dùng bộ sinh số ngẫu nhiên tất định, vốn dễ bị tấn công về mặt bảo mật.gosechoặcgolangci-lintđưa ra cảnh báo khi dùngmath/rand.math/rand/v2dùng mật mã ChaCha8 và được seed bằng entropy hệ thống nên tạo cảm giác là “an toàn”, nhưng vẫn không phù hợp cho các tác vụ nhạy cảm về bảo mật. Trong những trường hợp đó phải dùngcrypto/rand.math/randcủa Go 1, nói chính xác hơn, có thể được xem là một additive lagged Fibonacci generator.math/randmới, ngay cả trong trường hợp xấu nhất, cũng đạt khoảng một nửa tốc độ của bộ sinh số ngẫu nhiên không an toàn trước đây, và trong đa số benchmark thì gần như không có khác biệt. Go đang giữ được sự cân bằng hợp lý giữa an toàn và hiệu năng trong thư viện chuẩn.java.util.Randomcủa Java.