Mã 2FA hoạt động thế nào: tính TOTP từng bước

Tự tính mã xác thực sáu số từ bộ dữ liệu kiểm thử của RFC 6238: bộ đếm thời gian, HMAC-SHA-1, cắt động, và vì sao mã bị từ chối.

Trong bài này

Sáu chữ số trong ứng dụng xác thực đổi sau mỗi 30 giây, chạy được khi không có mạng, mà vẫn khớp với thứ trang web chờ đợi. Không có phép màu hay lời gọi máy chủ nào: điện thoại và trang web cùng giữ một bí mật và cùng làm một phép tính nhỏ trên thời gian hiện tại. Phép tính đó là TOTP, được định nghĩa trong RFC 6238 dựa trên HOTP của RFC 4226. Bài này tính tay một mã bằng bộ dữ liệu kiểm thử chính thức để bạn thấy từng bước, rồi liệt kê các lý do khiến mã bị từ chối.

Mọi giá trị dưới đây đều được kiểm tra hai lần: bằng mã của công cụ tạo mã TOTP và độc lập bằng hàm HMAC có sẵn của Node.js.

Nguyên liệu

  • Một bí mật dùng chung. Khi bạn quét mã QR thiết lập, ứng dụng lưu một khoá ngẫu nhiên, thường hiển thị dạng Base32 như JBSWY3DPEHPK3PXP. Trang web giữ một bản sao.
  • Thời gian. Giờ Unix, tức số giây kể từ 1/1/1970 theo UTC.
  • Ba tham số. Độ dài bước (30 giây), số chữ số (6) và hàm băm (SHA-1). Gần như mọi dịch vụ đều dùng các giá trị mặc định này.

Khoá kiểm thử của RFC là 20 byte ASCII 12345678901234567890, ở dạng Base32 là GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ. Công cụ luôn dùng đồng hồ của thiết bị, nên dán khoá này vào sẽ thấy mã của thời điểm hiện tại; các mốc thời gian kiểm thử trong quá khứ dưới đây được tái hiện trong bộ kiểm thử của công cụ.

Bước 1: đổi thời gian thành bộ đếm

Chia giờ Unix cho độ dài bước rồi bỏ phần lẻ. Mốc kiểm thử đầu tiên của RFC là T = 59 giây, tức một giây trước 00:01:00 UTC ngày 1/1/1970:

59 ÷ 30 = 1,97, vậy bộ đếm là 1.

Mọi giây từ 30 tới 59 đều cho cùng bộ đếm, vì thế mã giữ nguyên trong 30 giây. Bộ đếm được viết thành 8 byte theo thứ tự big-endian: 00 00 00 00 00 00 00 01.

Bước 2: HMAC bộ đếm bằng bí mật

Tính HMAC-SHA-1 với bí mật làm khoá và 8 byte trên làm thông điệp. Kết quả dài 20 byte:

75a48a19d4cbe100644e8ac1397eea747a2d33ab

HMAC là hàm băm có khoá: không có bí mật thì không đoán được kết quả, và nhìn các kết quả cũ cũng không lộ ra bí mật.

Bước 3: cắt động

Hai mươi byte thì quá dài để gõ, nên RFC 4226 chọn ra bốn byte theo cách phụ thuộc vào chính kết quả băm:

  1. Lấy 4 bit thấp của byte cuối cùng. Byte cuối là ab, và 0xab & 0x0f = 11.
  2. Đọc 4 byte bắt đầu từ vị trí 11: c1 39 7e ea.
  3. Xoá bit cao nhất (để số không bao giờ âm): 0x41397eea, tức 1.094.287.082 ở hệ thập phân.

Bước 4: giữ lại mấy chữ số cuối

Lấy số đó chia lấy dư cho 10⁶ để có mã sáu số: 1.094.287.082 mod 1.000.000 = 287082. Với 8 chữ số thì là 94287082, đúng giá trị in trong Phụ lục B của RFC 6238 cho SHA-1 tại T = 59.

Mã nào ngắn hơn sẽ được thêm số 0 ở đầu, vì thế mã có thể bắt đầu bằng 0. Công cụ và mọi ứng dụng phải giữ lại số 0 đó.

Các mốc kiểm thử còn lại

Bốn bước trên tái hiện đúng phần còn lại của bảng SHA-1 trong RFC:

Giờ UnixNgày giờ UTCBộ đếmVị tríMã 8 sốMã 6 số
591970-01-01 00:00:5911194287082287082
11111111092005-03-18 01:58:2937037036407081804081804
12345678902009-02-13 23:31:3041152263389005924005924
20000000002033-05-18 03:33:20666666661569279037279037

RFC cũng đưa ra giá trị cho SHA-256 và SHA-512 với khoá dài hơn, và bộ kiểm thử của công cụ kiểm tra tất cả. Một dòng nữa trong RFC dùng T = 20.000.000.000, một ngày trong năm 2603. Bộ đếm của nó không vừa 32 bit, đó là cách RFC nói rằng phải dùng bộ đếm 64 bit. "Sự cố năm 2038" không ảnh hưởng tới một bản cài đặt TOTP đúng.

Vì sao mã bị từ chối

Đồng hồ sai. Hai bên tính bộ đếm từ đồng hồ của riêng mình. Nếu điện thoại chậm 40 giây, nó lùi hẳn một bước. RFC 6238 khuyến nghị máy chủ chấp nhận độ trễ mạng tối đa một bước, và nhiều máy chủ chấp nhận lệch một bước về cả hai phía, nên lệch vài giây thì không sao còn lệch một phút thì có. Hãy để giờ điện thoại ở chế độ tự động.

Mã hết hạn khi bạn đang gõ. Mã gõ ở giây thứ 29 của khung có thể tới nơi ở giây 31. Chờ mã mới nhanh hơn gõ lại.

Chép sai bí mật. Base32 chỉ dùng A tới Z và 2 tới 7. Các chữ số 0, 1, 8 và 9 không bao giờ xuất hiện, nên bí mật có chứa chúng là đã bị đọc nhầm (thường là O thành 0 hoặc I thành 1). Công cụ của chúng tôi chỉ ra ký tự sai và vị trí của nó. Công cụ cũng chấp nhận chữ thường, dấu cách và gạch nối, vì bí mật thường được hiển thị như vậy. Độ dài mà không bộ mã hoá nào tạo ra được, như 9 ký tự, nghĩa là một phần bí mật đã bị mất.

Tham số không mặc định. Dịch vụ yêu cầu SHA-256, 8 chữ số hay bước 60 giây sẽ ghi các tham số đó trong link thiết lập. Một số ứng dụng xác thực bỏ qua chúng và âm thầm dùng mặc định, cho ra mã không bao giờ khớp. Nếu bạn tự thiết lập dịch vụ, hãy giữ SHA-1, 6 chữ số, 30 giây.

Bí mật có ý nghĩa gì với bảo mật

Ai có bí mật đều tạo được mã của bạn mãi mãi, trên bất kỳ thiết bị nào. Vì vậy mã QR thiết lập nhạy cảm như một mật khẩu: công cụ quét mã QR của chúng tôi mặc định che giá trị secret của mã otpauth:// cũng vì lý do đó. Hãy giữ các mã khôi phục mà dịch vụ cung cấp, và lưu bản sao bí mật ở nơi được mã hoá nếu ứng dụng của bạn không xuất được.

TOTP cũng không chặn được lừa đảo theo thời gian thực. Một trang đăng nhập giả có thể hỏi mã của bạn và dùng nó ngay trong 30 giây đó. TOTP ngăn việc mật khẩu dùng lại hay mật khẩu bị lộ tự nó đủ để đăng nhập, và đó là phần lớn mục đích của nó. Về phần mật khẩu, hãy đọc thế nào là mật khẩu mạnh.

Những lỗi thường gặp

  1. Gõ tay bí mật và nhầm O với 0.
  2. Chỉnh giờ điện thoại thủ công, hoặc đi nước ngoài mà tắt chế độ giờ tự động.
  3. Chụp màn hình mã QR thiết lập "để dùng sau" rồi để nó trong thư viện ảnh đồng bộ lên mây.
  4. Chọn SHA-256 hoặc 8 chữ số cho dịch vụ của mình mà không kiểm tra ứng dụng nào hỗ trợ.

Để xem trực tiếp, hãy thêm cùng một bí mật thử nghiệm vào ứng dụng xác thực và vào công cụ tạo mã TOTP: cả hai phải hiện cùng một mã và cùng đồng hồ đếm ngược. Nếu không, hãy kiểm tra đồng hồ trước tiên.

Công cụ được nhắc trong bài

Về trang blog