Series pragmarx/google2fa Nâng cao #1: Bí ẩn tham số Window – Cứu rỗi những chiếc đồng hồ "lệch nhịp"
1. Nỗi oan của "Mã không hợp lệ" (Clock Drift)
Khi vận hành hệ thống AFC Metro, chắc chắn bạn sẽ gặp kịch bản này: Một Quản lý ga gọi điện chửi ầm lên là: "Anh vừa nhìn mã trên app Google Authenticator, gõ đúng y chang 6 số mà hệ thống cứ báo sai!".
Bạn check log, thấy User gõ đúng thật. Vậy lỗi do đâu? Lỗi là do Đồng hồ của điện thoại User bị chạy sai giờ (Clock Drift) so với đồng hồ trên máy chủ Server của bạn.
Thuật toán TOTP sinh ra một mã mới mỗi 30 giây dựa trên mốc thời gian thực (UTC). Nếu điện thoại của User chạy nhanh hơn hoặc chậm hơn Server khoảng 1 phút, thì mã 6 số họ nhìn thấy trên màn hình lúc đó thực chất là mã của tương lai hoặc mã của quá khứ. Hệ thống kiểm tra bằng thời gian hiện tại của Server tất nhiên sẽ báo sai.
2. Thuốc giải từ PragmaRX: Tham số $window
Nếu bạn mò vào mã nguồn trên GitHub của pragmarx/google2fa, bạn sẽ thấy hàm verifyKey thực chất nhận vào tới 3 tham số, chứ không chỉ 2 tham số như chúng ta thường dùng.
// Cách dùng cơ bản (Chỉ check đúng chu kỳ 30s hiện tại)
Google2FA::verifyKey($secret, $otp);
// Cách dùng nâng cao (Nguyên mẫu hàm)
Google2FA::verifyKey($secret, $otp, $window = null);
Tham số $window chính là "độ bao dung" của hệ thống đối với sự chậm trễ.
- Mặc định, $window = 1. Nghĩa là thư viện sẽ chấp nhận mã OTP của chu kỳ 30s hiện tại, VÀ 1 chu kỳ ngay trước đó (tổng cộng 60 giây). Điều này giúp cứu những user gõ chậm (nhìn mã lúc giây thứ 29, gõ xong thì đồng hồ nhảy sang giây 32).
3. Mở rộng Cửa sổ thời gian (Window)
Nếu bạn xác định tập khách hàng của mình thường xuyên bị sai giờ điện thoại, hoặc mạng lưới 4G/Wifi từ nhà ga truyền gói tin HTTP lên Server bị delay, bạn có thể chủ động nới rộng cửa sổ này.
Hãy mở file config/google2fa.php (file mà chúng ta đã publish ở Bài 1 của series trước) và tìm đến dòng window:
/*
* Mở rộng cửa sổ kiểm tra mã OTP.
* Ví dụ: window = 2
* Nghĩa là chấp nhận: 2 chu kỳ quá khứ + 1 chu kỳ hiện tại + 2 chu kỳ tương lai.
* Tổng cộng: (2 + 1 + 2) * 30s = 150 giây (2.5 phút).
*/
'window' => 2,
Hoặc bạn có thể can thiệp trực tiếp bằng code trong Controller để chỉ mở rộng window cho những User VIP (Ví dụ: Giám đốc thường xuyên đi công tác nước ngoài, điện thoại hay nhảy múi giờ lung tung):
use PragmaRX\Google2FALaravel\Facade as Google2FA;
public function verify(Request $request)
{
$secret = decrypt($user->google2fa_secret);
// Set window linh hoạt bằng code thay vì dùng config
Google2FA::setWindow(4); // Chấp nhận độ lệch lên tới 2 phút
$isValid = Google2FA::verifyKey($secret, $request->otp_code);
// ...
}
4. Đánh đổi (Trade-off) giữa Bảo mật và Tiện dụng
Việc tăng $window lên cao (ví dụ lên 4 hoặc 5) sẽ giải quyết triệt để bài toán User chửi rủa vì gõ đúng mà báo sai. Nhưng bạn phải hiểu kiến trúc Backend luôn là sự đánh đổi:
Khi bạn nới rộng $window, nghĩa là ở bất kỳ thời điểm nào, cũng có tới 9 mã OTP hợp lệ cùng lúc (4 quá khứ + 1 hiện tại + 4 tương lai). Điều này làm tăng xác suất Hacker có thể đoán mò (Brute-force) trúng mã 6 số. Do đó, với hệ thống lõi như trạm Metro, mức $window khuyên dùng chỉ nên dừng lại ở 1 hoặc tối đa là 2. Không nên chiều hư User quá mức!
Bằng việc hiểu sâu cấu trúc mã nguồn của pragmarx/google2fa, bạn không chỉ là người "gọi thư viện", mà đã thực sự làm chủ luồng thuật toán của nó.
All rights reserved