0

Khai tử rand(): Làm chủ random_int() để sinh số ngẫu nhiên an toàn tuyệt đối trong PHP

Chào anh em!

Trong quá trình phát triển các hệ thống Backend, bài toán "tạo ra một con số ngẫu nhiên" xuất hiện ở khắp mọi nơi: từ việc sinh mã xác thực OTP gửi qua SMS, tạo chuỗi token reset mật khẩu, cho đến việc bốc thăm trúng thưởng.

Theo thói quen từ những ngày đầu học lập trình, rất nhiều anh em sẽ gõ ngay hàm rand() hoặc mt_rand() vào source code. Tuy nhiên, nếu bạn đang dùng chúng cho các tính năng liên quan đến bảo mật, bạn đang vô tình mở toang cánh cửa hệ thống cho hacker bước vào.

Để giải quyết bài toán sinh số ngẫu nhiên một cách an toàn nhất, từ phiên bản 7.0, PHP đã cung cấp một hàm tiêu chuẩn cực kỳ mạnh mẽ: random_int(). Hãy cùng mổ xẻ xem tại sao nó lại được coi là tiêu chuẩn vàng nhé!

1. random_int() thực chất là gì?

Dựa vào tài liệu mã nguồn bạn cung cấp, hàm này có định nghĩa như sau:

function random_int(int $min, int $max): int {}

Nhiệm vụ của random_int() là trả về một số nguyên ngẫu nhiên nằm trong khoảng từ $min đến $max (bao gồm cả giá trị min và max).

Điểm "ăn tiền" nhất của hàm này nằm ở dòng chú thích: "Generates cryptographically secure pseudo-random integers". Tức là nó sinh ra các con số giả ngẫu nhiên an toàn về mặt mật mã học (CSPRNG). Nó sử dụng trực tiếp các bộ sinh số ngẫu nhiên ở tầng lõi của Hệ điều hành (như /dev/urandom trên Linux hoặc CryptGenRandom trên Windows) để đảm bảo độ ngẫu nhiên là cao nhất.

2. Tại sao phải "tẩy chay" rand() và mt_rand()?

Các hàm cũ như rand() hay mt_rand() tuy chạy rất nhanh nhưng chúng là những bộ sinh số có thể đoán trước được (Predictable).

Bản chất của máy tính là không thể tự nghĩ ra một con số ngẫu nhiên. Các hàm cũ dựa vào một giá trị "hạt giống" (seed) – thường là thời gian hiện tại của máy chủ (timestamp). Nếu một hacker biết được thuật toán và thời gian gần đúng mà máy chủ của bạn sinh ra mã OTP, họ hoàn toàn có thể tính toán ngược lại và đoán trúng chính xác 100% mã OTP hoặc token reset mật khẩu tiếp theo sẽ là gì.

Ngược lại, random_int() lấy dữ liệu từ những luồng nhiễu hỗn loạn của hệ điều hành (như nhiệt độ CPU, thời gian nhấn phím, chuyển động của ổ cứng...), khiến việc đoán trước kết quả là bất khả thi.

3. Cách sử dụng và bẫy ngoại lệ (Exception)

Cách sử dụng hàm này cực kỳ đơn giản. Ví dụ bạn muốn sinh một mã OTP 6 số:

// Sinh ra một số ngẫu nhiên từ 100000 đến 999999
$otp = random_int(100000, 999999);

Tuy nhiên, hãy nhìn kỹ vào dòng docblock này:

@throws Random\RandomException if an appropriate source of randomness cannot be found.

Bởi vì random_int() phụ thuộc vào hệ điều hành, nên trong một số trường hợp cực kỳ hiếm hoi (như server bị lỗi phần cứng, cạn kiệt tài nguyên bộ nhớ, hoặc hệ điều hành quá cũ không hỗ trợ CSPRNG), hàm này sẽ không thể sinh ra số an toàn. Thay vì trả về một con số kém an toàn để "chữa cháy", PHP chọn cách chủ động ném ra ngoại lệ (throw Exception) để báo động cho lập trình viên.

Do đó, cách viết code "chuẩn Senior" khi dùng random_int() cho các hệ thống tài chính là phải bọc nó trong try...catch:

try {
    $secureOtp = random_int(100000, 999999);
    // Gửi OTP cho người dùng...
} catch (\Random\RandomException $e) {
    // Ghi log lỗi nghiêm trọng
    report($e);
    
    // Báo lỗi cho người dùng thay vì tiếp tục chạy
    return response()->json(['error' => 'Không thể tạo mã xác thực an toàn lúc này!'], 500);
}

Lời kết

Trong lĩnh vực an toàn thông tin (Security), nguyên tắc tối thượng là: Không bao giờ tự chế thuật toán mã hóa và không bao giờ dùng các hàm ngẫu nhiên thông thường để bảo mật.

Bắt đầu từ dự án tiếp theo, mỗi khi bạn định gõ rand() hay mt_rand() để tạo token, voucher, password hay OTP, hãy khựng lại một nhịp và đổi nó thành random_int(). Sự thay đổi nhỏ này sẽ giúp hệ thống của bạn đứng vững trước những cuộc tấn công tinh vi nhất!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí