Android và IOS đều có cơ chế sandbox, giúp cho một ứng dụng có thể lưu trữ dữ liệu ở nơi riêng tư, không thể truy cập bởi các ứng dụng khác. Tuy nhiên nếu không được mã hoá, những dữ liệu này hoàn toàn có thể bị xâm phạm.
Tuỳ thuộc vào mức độ bảo mật của ứng dụng, attacker có thể sử dụng những cách khác nhau để truy cập trái phép dữ liệu được lưu trữ không an toàn. Lưu ý, việc lưu trữ dữ liệu nhạy cảm tại external storage mã không mã hoá là một việc làm không được khuyến khích, vì các app cài trên máy sẽ có thể đọc dữ liệu lưu trữ ở đây mà không cần bypass sandbox hay máy phải root/jailbreak.

Trên Android, nếu trong file Manifest của ứng dụng có thuộc tính android:debuggable=true, attacker có thể sử dụng adb run-as để đọc dữ liệu từ internal (private) storage của app, backup dữ liệu rồi đọc dữ liệu từ file backup này nếu trong file Manifest có thuộc tính android:allowBackup=true hoặc bypass cơ chế sandbox để đọc dữ liệu của app khác nếu hệ điều hành có lỗi bảo mật. Đối với thiết bị đã root, rủi ro còn đến nhanh và dễ dàng hơn nhiều do nếu được cấp quyền root, app độc hại và adb có thể đọc dữ liệu từ internal storage của bất kỳ app nào.

Trên IOS, việc jailbreak máy không xoá các app cũng như dữ liệu của chúng, do đó nếu mua lại máy iphone của một ai đó, attacker có thể jailbreak điện thoại và đọc dữ liệu của app nếu app chưa bị xoá hoặc thậm chí là dump data trong keychain ngay cả khi ứng dụng đã bị xoá. Tương tự như Android, attacker cũng có thể backup app để đọc dữ liệu hoặc bypass cơ chế sandbox để đọc dữ liệu của app khác nếu hệ điều hành có lỗi bảo mật.

Khi triển khai các tính năng như đăng nhập bằng sinh trắc học, các app mobile sẽ phải lưu trữ một “secret” nào đó (ví dụ như password, authen token) ở local để khi người dùng xác thực thành công, app sẽ gửi secret đó lên để xác thưc với backend. Vậy, làm thế nào để lưu trữ các dữ liệu nhạy cảm một cách an toàn trên Android và IOS?

1. Android

Hệ điều hành Android cung cấp Keystore – một hệ thống lưu trữ key mã hoá an toàn cho phép lưu trữ key mà ngay cả trên thiết bị đã root, key cũng khó có thể bị lộ.

Ý tưởng là tạo key sử dụng Keystore, mã hoá dữ liệu sử dụng key này sau đó lưu trữ dữ liệu đã được mã hoá vào internal storage, ví dụ như sau (code):

và giải mã dữ liệu sau khi user nhập thành công mã PIN hoặc xác thực biometrics thành công…

Một cách đơn giản hơn là sử dụng EncryptedSharedPreferences, cơ chế hoạt động cũng tương tự như trên, sử dụng key tạo bằng Keystore và mã hoá dữ liệu trước khi lưu vào SharedPreferences. Cách sử dụng thì cũng đơn giản, ví dụ:

Lưu ý là nếu ứng dụng của bạn cho phép sao lưu dữ liệu ở shared preference, khi restore ở máy khác sẽ không thể giải mã dữ liệu do key chỉ tồn tại ở máy cũ

Cách làm này khá an toàn, nhưng nếu ứng dụng chạy trên thiết bị đã root, attacker có thể sử dụng một số công cụ (ví dụ như frida) để can thiệp vào process của ứng dụng, gọi hàm decryptData và nhận được secret mà không cần xác thực biometric hay biết mã PIN.

Để an toàn hơn, có thể yêu cầu người dùng xác thực biometrics để giải mã dữ liệu, sử dụng phương thức authenticate(PromptInfo info, CryptoObject crypto) của class androidx.biometric.BiometricPrompt thay vì authenticate(PromptInfo info). Tham khảo code tại đây
Lý do cho việc này là vì trên thiết bị đã root, attacker có thể sử dụng frida để bypass xác thực biometric, nếu truyền thêm CryptoObject vào phương thức authenticate thì khi bypass bằng frida, result.getCryptoObject().getCipher() trong callback onAuthenticationSucceeded sẽ trả về null và do đó dù có bypass được biometric cũng không giải mã được dữ liệu 🤭
Điều quan trọng là phải thực sự sử dụng crypto object trong quá trình giải mã, mã hoá dữ liệu, nếu không khả năng cao sẽ bị bypass 😀

Tóm lại, để an toàn nhất thì không nên lưu dữ liệu nhạy cảm ở client.
Trong trường hợp thật cần thiết phải lưu thì cần mã hoá dữ liệu bằng key tạo từ KeyStore hoặc dùng EncryptedSharedPreferences. Đối với ứng dụng cần bảo vệ dữ liệu người dùng ngay cả trên máy đã root, cần yêu cầu người dùng xác thực biometrics để giải mã dữ liệu (các triển khai như code mẫu ở trên), thêm nữa là phải kiểm tra vân tay, khuôn mặt trên thiết bị có bị thay đổi không, nếu có thì yêu cầu người dùng nhập lại mật khẩu.

2. IOS

Vì data lưu vào Keychain sẽ không bị mất khi xoá app, do đó ý tưởng sẽ là tạo một key ngẫu nhiên, lưu vào Keychain rồi dùng key này để mã hoá dữ liệu. Dữ liệu được mã hoá lưu vào Internal Storage, như vậy khi xoá app dữ liệu mã xoá sẽ bị xoá theo, attacker có dump được keychain cũng chỉ có key chứ không có secret data để giải mã 😀

Thông thường, để lưu dữ liệu vào Keychain có thể dùng đoạn code sau

Sau đó lấy dữ liệu

Cách làm này phổ biến và an toàn (nếu không tính trường hợp máy đã jailbreak). Trong trường hợp máy đã jailbreak, attacker có thể dùng cách công cụ hooking để gọi hàm getKeychainItem để lấy key, có thể bypass xác thực biometrics để đăng nhập mà không cần biết mật khẩu nếu cách triển khai tính năng này không chính xác, ví dụ như sau:

Việc lưu dữ liệu nhạy cảm mà không mà hoá là điều không nên, vì sẽ có nguy cơ lộ lọt dữ liệu thông qua vài trường hợp như backup máy, máy cài mã độc..v..v…

Categorized in: