Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 30 — Biometric Authentication Testing

Đã đọc 0/14 mục · còn khoảng 13 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là QA của một ứng dụng ngân hàng số ở Việt Nam. Sáng thứ Hai, bộ phận chăm sóc khách hàng chuyển lên hàng loạt phản ánh: "Tôi đăng nhập bằng vân tay được, nhưng khi chuyển tiền thì app bắt nhập lại mật khẩu", "Face ID nhận diện luôn cả khi tôi đưa ảnh chụp mặt mình lên", "Đổi điện thoại xong app vẫn cho tôi vào bằng vân tay của người chủ cũ". Ba câu phàn nàn đó, nếu là thật, tương ứng với ba mức độ nghiêm trọng khác nhau — từ trải nghiệm khó chịu cho đến lỗ hổng bảo mật có thể khiến ngân hàng mất tiền và mất uy tín.

Biometric authentication (xác thực sinh trắc học) — vân tay, khuôn mặt, mống mắt — đã trở thành lớp bảo mật mặc định của gần như mọi app tài chính, ví điện tử, sức khỏe hay bất kỳ app nào lưu dữ liệu nhạy cảm. Nhưng khác với việc test một form đăng nhập username/password, sinh trắc học đụng vào phần cứng chuyên biệt (secure enclave, TEE), vào các API hệ điều hành khắt khe, và vào những kịch bản mà bạn không thể "gõ đại" một giá trị để kiểm tra. Đây là dạng test dễ bị làm hời hợt nhất — nhiều đội chỉ kiểm tra "vân tay đúng thì vào được" rồi bỏ qua hàng chục nhánh thất bại quan trọng hơn nhiều.

Bài này sẽ giúp bạn xây dựng một tư duy test biometric đầy đủ: hiểu bản chất từng loại trên iOS và Android, biết cách giả lập trên simulator/emulator lẫn máy thật, và quan trọng nhất là bao phủ được các nhánh mà lập trình viên hay quên. Đây là kỹ năng phân biệt một QA mobile "biết click" với một QA mobile thực thụ.

Khái niệm cốt lõi

Các loại biometric theo nền tảng

Trước hết cần nắm bản đồ các phương thức sinh trắc học mà bạn sẽ gặp:

Nền tảngPhương thứcGhi chú
iOSTouch ID (vân tay, legacy)Trên các máy có nút Home vật lý
iOSFace ID (khuôn mặt)Từ iPhone X trở đi, dùng camera TrueDepth
AndroidFingerprint (vân tay)Phổ biến nhất, cảm biến sau/trong màn hình
AndroidFace Unlock (khuôn mặt)Chất lượng phụ thuộc nhà sản xuất
AndroidIris (mống mắt)Chỉ một số dòng Samsung cũ, đang bị khai tử
Điểm mấu chốt bạn phải hiểu: app không bao giờ nhìn thấy dữ liệu sinh trắc học của người dùng. Vân tay và khuôn mặt được xử lý trong vùng phần cứng bảo mật riêng — Secure Enclave trên iOS, Trusted Execution Environment (TEE) hoặc Titan/StrongBox trên Android. App chỉ nhận về một tín hiệu nhị phân: "xác thực thành công" hoặc "thất bại", cùng với một khóa (key) được mở trong keystore. Vì vậy khi test, bạn không test độ chính xác của cảm biến (đó là việc của Apple/Google), mà test cách app phản ứng với từng kết quả mà hệ điều hành trả về.

Hai vai trò của biometric trong app

Cần phân biệt rõ hai cách một app dùng sinh trắc học, vì kịch bản test khác nhau hoàn toàn:

  • Xác thực đơn thuần (authentication only): App chỉ hỏi hệ điều hành "người này có phải chủ máy không?" và nhận về true/false. Phù hợp cho khóa app đơn giản, xem lại lịch sử. Rủi ro: nếu app tin tưởng mù quáng vào kết quả này mà không có khóa mã hóa đứng sau, kẻ tấn công có thể bypass bằng cách hook API ở tầng ứng dụng.
  • Mã hóa gắn khóa (crypto-bound / key-backed): App yêu cầu mở một khóa mật mã lưu trong keystore, và khóa đó chỉ được giải phóng khi sinh trắc học đúng. Đây là cách làm đúng cho giao dịch tài chính, vì kết quả không thể giả mạo ở tầng phần mềm. Trên Android là setUserAuthenticationRequired(true), trên iOS là dùng SecAccessControl với cờ biometryCurrentSet.
Là QA, bạn phải hỏi dev: "Ta đang dùng kiểu nào?" Câu trả lời quyết định độ sâu test bảo mật của bạn.

Vòng đời và các trạng thái cần bao phủ

Một luồng biometric không chỉ có "đúng/sai". Hãy hình dung ma trận trạng thái:

  • Thiết bị không có cảm biến (máy cũ, máy giá rẻ).
  • Có cảm biến nhưng chưa đăng ký vân tay/khuôn mặt nào.
  • Đã đăng ký nhưng người dùng chưa bật biometric trong app.
  • Xác thực thành công.
  • Xác thực thất bại (vân tay sai) — thất bại 1 lần, nhiều lần liên tiếp dẫn tới khóa tạm thời (lockout).
  • Người dùng hủy (bấm Cancel) hoặc chọn fallback (dùng mật khẩu/PIN).
  • Thay đổi tập sinh trắc học: thêm/xóa vân tay, đăng ký lại Face ID sau khi test — đây là kịch bản bảo mật cực quan trọng.

Khái niệm "biometric invalidation"

Đây là phần tinh tế nhất và bị bỏ sót nhiều nhất. Khi người dùng thêm một vân tay mới hoặc đăng ký lại khuôn mặt, hệ điều hành coi như "tập sinh trắc học đã thay đổi". Nếu khóa được gắn cờ biometryCurrentSet (iOS) hoặc setInvalidatedByBiometricEnrollment(true) (Android), khóa cũ sẽ bị vô hiệu hóa. App bắt buộc phải bắt người dùng xác thực lại bằng mật khẩu server và tạo khóa mới. Kịch bản này chống lại trường hợp: kẻ gian lấy được điện thoại đã mở khóa, thêm vân tay của chính mình vào, rồi dùng vân tay đó vào app ngân hàng. Nếu app không invalidate, kẻ gian vào được — đây là lỗ hổng thật, không phải lý thuyết.

Tình huống thực tế

Tình huống 1 — Ví điện tử và cái bẫy "thêm vân tay"

Một ví điện tử lớn tại Việt Nam (gọi là VíPay cho gọn) ra tính năng đăng nhập bằng vân tay để giảm ma sát. Đội dev triển khai theo kiểu authentication-only: gọi API vân tay, nếu trả về success thì cho vào thẳng, đọc token đăng nhập từ SharedPreferences.

Trong sprint test, QA chỉ kiểm "vân tay đúng thì vào, sai thì báo lỗi" — pass. App lên production. Ba tuần sau, một khách hàng cho em họ mượn điện tại nhà. Em họ vào phần cài đặt hệ thống, thêm vân tay của mình (điện thoại đã mở khóa nên thêm được), rồi mở VíPay — và vào thẳng tài khoản của anh mình, chuyển 5 triệu đồng.

Diễn giải: Lỗ hổng nằm ở chỗ token vẫn nằm nguyên trong storage và app không hề gắn token đó vào khóa sinh trắc học. Khi tập vân tay thay đổi, không có gì bị vô hiệu hóa cả. QA đã bỏ sót toàn bộ nhánh "biometric enrollment change".

Bài học: Với mọi app tài chính, phải có một test case cố định: đăng nhập bằng biometric → vào phần cài đặt hệ thống thêm/xóa một vân tay → quay lại app → app PHẢI bắt xác thực lại bằng mật khẩu, không được cho vào bằng token cũ. Đồng thời phải yêu cầu dev dùng khóa gắn cờ invalidate-on-enrollment.

Tình huống 2 — App ngân hàng và lỗi fallback trên máy chưa đăng ký

Một ngân hàng số triển khai xác nhận giao dịch bằng Face ID trên iOS. Kịch bản "khuôn mặt đúng → duyệt giao dịch" chạy mượt trên máy test của QA (một chiếc iPhone 13 đã đăng ký Face ID đầy đủ).

Nhưng khi lên production, bộ phận hỗ trợ nhận báo: một nhóm khách hàng lớn tuổi dùng iPhone SE (nút Home, chỉ có Touch ID, và nhiều người chưa đăng ký vân tay nào) không thể chuyển tiền. Màn hình xác nhận giao dịch hiện lên rồi... đứng im. App gọi API biometric, nhận về lỗi LAErrorBiometryNotEnrolled, nhưng code chỉ xử lý nhánh success và nhánh "sai vân tay", quên hẳn nhánh "chưa đăng ký gì".

Diễn giải: QA đã không test trên cấu hình thiết bị "có cảm biến nhưng chưa enroll". Vì máy test luôn được cấu hình đầy đủ, cả một nhánh code chết không bao giờ được kích hoạt trong phòng lab.

Bài học: Ma trận thiết bị test phải cố tình chứa các cấu hình "thiếu": máy chưa đăng ký biometric, máy không có cảm biến. Và mọi màn hình biometric bắt buộc có đường fallback rõ ràng (nhập PIN/mật khẩu) khi sinh trắc học không khả dụng — không bao giờ để người dùng kẹt.

Tình huống 3 — App sức khỏe và lockout gây mất dữ liệu nhập dở

Một startup Đông Nam Á làm app theo dõi sức khỏe, khóa màn hình nhật ký bằng vân tay. Người dùng đang nhập một ghi chú dài về triệu chứng thì app tự khóa (do timeout nền). Khi quay lại, họ đặt sai vân tay 5 lần liên tiếp (tay ướt sau khi rửa), Android trả về lỗi lockout tạm thời 30 giây và app... khởi động lại toàn bộ, xóa sạch ghi chú đang nhập dở.

Diễn giải: Nhánh "lockout" (khóa tạm thời sau nhiều lần sai) không được xử lý duyên dáng. App coi lockout như một lỗi nghiêm trọng và reset state, thay vì hiện thông báo "vui lòng thử lại sau 30 giây hoặc dùng PIN" và giữ nguyên dữ liệu.

Bài học: Phải test cụ thể trạng thái lockout — cả lockout tạm thời (Android: BIOMETRIC_ERROR_LOCKOUT) lẫn lockout vĩnh viễn (BIOMETRIC_ERROR_LOCKOUT_PERMANENT, chỉ mở lại được bằng khóa màn hình thiết bị). Và kiểm tra app không mất dữ liệu người dùng khi rơi vào các trạng thái này.

Hướng dẫn từng bước

Dưới đây là quy trình test biometric có hệ thống, áp dụng cho cả iOS và Android.

Bước 1 — Làm rõ thiết kế với dev. Trước khi viết case, hỏi rõ: App dùng authentication-only hay crypto-bound key? Khóa có gắn cờ invalidate-on-enrollment không? Có fallback về PIN/mật khẩu không? Những câu trả lời này định hình toàn bộ bộ test của bạn.

Bước 2 — Test trên simulator/emulator cho các nhánh cơ bản.

  • Trên iOS Simulator: menu Features → Face ID / Touch ID → Enrolled để bật, rồi Matching Face / Non-matching Face để giả lập đúng/sai. Đây là cách nhanh để cover luồng success và luồng "sai khuôn mặt".
  • Trên Android Emulator: mở Extended Controls (dấu ...) → Fingerprint, hoặc dùng lệnh adb: adb -e emu finger touch <finger_id> để giả lập một lần chạm vân tay đã đăng ký. Đăng ký vân tay ảo trước qua Settings → Security.
Bước 3 — Bao phủ ma trận trạng thái. Với mỗi trạng thái sau, viết một test case riêng và kết quả kỳ vọng:
  • Không có cảm biến → app ẩn nút biometric, chỉ hiện PIN.
  • Có cảm biến, chưa enroll → app hiện gợi ý bật/dẫn tới Settings, không crash.
  • Success → vào đúng màn hình, khóa được mở.
  • Sai 1 lần → báo lỗi, cho thử lại.
  • Sai nhiều lần → lockout tạm thời, hiện fallback.
  • Cancel / chọn "dùng mật khẩu" → chuyển sang luồng PIN mượt mà.
Bước 4 — Test kịch bản bảo mật cốt lõi trên máy thật. Simulator không mô phỏng được thay đổi enrollment một cách trung thực, nên dùng máy thật:
  • Đăng nhập bằng biometric thành công.
  • Vào cài đặt hệ thống, thêm một vân tay/khuôn mặt mới.
  • Quay lại app → xác nhận app buộc xác thực lại bằng mật khẩu (nếu là app nhạy cảm).
  • Lặp lại với thao tác xóa toàn bộ enrollment.
Bước 5 — Test luồng chuyển tiếp thiết bị và OS. Khôi phục cài đặt gốc, cập nhật OS lớn, đổi máy — sau các sự kiện này khóa keystore thường bị xóa. App phải xử lý được (bắt đăng nhập lại), không được treo hay hiển thị màn hình trắng.

Bước 6 — Kiểm tra hành vi khi app vào nền/quay lại. Nhiều app yêu cầu xác thực lại sau khi rời app quá X giây. Test đúng ngưỡng thời gian, và test trường hợp có cuộc gọi/thông báo cắt ngang giữa lúc quét biometric.

Bước 7 — Test bypass ở tầng bảo mật (nếu có quyền). Với thiết bị đã root/jailbreak, kiểm tra app có phát hiện và từ chối biometric-only trên môi trường không tin cậy không. Đây là ranh giới sang bài Security Testing, nên chỉ dừng ở mức xác nhận app không tin tưởng mù quáng kết quả sinh trắc học.

Lỗi thường gặp & mẹo

Chỉ test "happy path". Đây là lỗi số một. "Vân tay đúng thì vào được" chỉ là một trong hơn mười nhánh. Giá trị của QA nằm ở các nhánh thất bại: lockout, chưa enroll, thay đổi enrollment, cancel, fallback. Hãy dành 80% công sức cho các nhánh không-happy.

Nhầm giữa authentication-only và key-backed. Nếu không hỏi dev, bạn sẽ bỏ lọt lỗ hổng như Tình huống 1. Luôn xác định cơ chế trước khi test bảo mật.

Máy test luôn được cấu hình "hoàn hảo". QA hay dùng một chiếc máy đã đăng ký biometric đầy đủ, nên vô tình không bao giờ chạm vào nhánh "chưa enroll" hay "không có cảm biến". Mẹo: giữ ít nhất một thiết bị test cố tình để trống enrollment.

Bỏ qua khác biệt chất lượng cảm biến giữa các máy Android. Face Unlock trên máy tầm trung nhiều khi chỉ dùng camera 2D, dễ bị qua mặt bằng ảnh. Nếu app cho phép Face Unlock cấp thấp cho giao dịch tài chính, đó là rủi ro. Mẹo: yêu cầu app chỉ chấp nhận biometric cấp Strong (BIOMETRIC_STRONG trên Android) cho hành vi nhạy cảm, và test rằng biometric Weak bị từ chối.

Không test khi tay ướt / khẩu trang / thiếu sáng. Đây là điều kiện thật của người dùng Việt Nam (trời nắng gắt, đeo khẩu trang). Face ID/Face Unlock thất bại thường xuyên trong các điều kiện này — app phải fallback êm, không được để người dùng bực bội.

Mẹo về log: Đừng để app ghi kết quả biometric ra log dạng plaintext hay lưu token cạnh cờ "đã xác thực". Kiểm tra logcat/Console không rò rỉ thông tin, và không có cách bật cờ "đã xác thực" bằng cách chỉnh SharedPreferences/UserDefaults.

Bài tập thực hành

  • Xây ma trận test case. Chọn một app thật có khóa sinh trắc học (ví dụ một app ngân hàng bạn dùng). Lập bảng gồm ít nhất 12 test case phủ đủ các trạng thái ở phần Khái niệm cốt lõi, mỗi dòng ghi: điều kiện tiền đề, thao tác, kết quả kỳ vọng, mức độ nghiêm trọng.
  • Giả lập trên emulator. Trên Android Emulator, đăng ký một vân tay ảo, dùng lệnh adb -e emu finger touch 1 để giả lập chạm đúng, và thử một finger id chưa đăng ký để giả lập chạm sai. Ghi lại phản ứng của một app mẫu (có thể tự viết một app demo dùng BiometricPrompt).
  • Tái hiện lỗ hổng enrollment. Trên một thiết bị thật (hoặc emulator hỗ trợ), đăng nhập app bằng biometric, sau đó vào Settings thêm một vân tay mới. Ghi lại: app có bắt xác thực lại không? Viết bug report giả định nếu app cho vào thẳng, kèm severity và bước tái hiện.
  • Thiết kế test lockout. Viết kịch bản test cho cả lockout tạm thời và lockout vĩnh viễn, mô tả cách kích hoạt (số lần sai liên tiếp), kết quả kỳ vọng về thông báo, đường fallback, và kiểm tra dữ liệu người dùng đang nhập dở không bị mất.

Tóm tắt

Test xác thực sinh trắc học không phải là "quét vân tay xem có vào được không". Bản chất công việc của bạn là kiểm tra cách app phản ứng với mọi kết quả mà hệ điều hành trả về — trong đó các nhánh thất bại quan trọng hơn nhánh thành công rất nhiều.

Những điểm cần khắc cốt: nắm rõ các phương thức trên iOS (Touch ID, Face ID) và Android (Fingerprint, Face Unlock, Iris) nhưng nhớ rằng app không thấy dữ liệu sinh trắc, chỉ thấy kết quả và khóa keystore. Phân biệt authentication-only với crypto-bound key để biết độ sâu test bảo mật. Bao phủ đủ ma trận trạng thái: không cảm biến, chưa enroll, success, sai, lockout tạm/vĩnh viễn, cancel, fallback. Và đừng bao giờ bỏ qua kịch bản vàng: thay đổi tập enrollment phải vô hiệu hóa khóa cũ — đó là ranh giới giữa một app an toàn và một vụ mất tiền như VíPay trong tình huống đầu bài.

Cuối cùng, hãy test trong điều kiện thật của người dùng Việt: tay ướt, đeo khẩu trang, máy tầm trung, người lớn tuổi chưa đăng ký gì. Một luồng biometric tốt là luồng mà khi sinh trắc học thất bại, người dùng vẫn có đường đi tiếp mượt mà và an toàn.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi