Biometric Change Detection: Complete Implementation Guide for Android and iOS

🌏 閱讀中文版本

Biometric Change Detection: Complete Implementation Guide for Android and iOS

Introduction

Biometric authentication (fingerprint, Face ID, Touch ID) has become a core security mechanism in modern mobile applications. However, how should apps detect when users add or remove biometric data to ensure security? This article provides an in-depth exploration of biometric change detection implementation methods on Android and iOS platforms, with complete code examples.

Why Detect Biometric Changes?

Security Considerations

When users add new fingerprints or reset Face ID, it may indicate:

  • Device Ownership Transfer: Phone sold or lent to others
  • Security Threats: Unauthorized personnel attempting to add their biometric data
  • Data Access Risks: Newly added biometric data may be used to access sensitive information

Practical Application Scenarios

  • Financial Applications: Banking apps need to require re-authentication when biometric settings change
  • Enterprise Applications: MDM systems need to track device security status changes
  • Health Applications: Medical apps need to ensure sensitive data is only accessible to authorized users

Android Biometric Detection Implementation

Core Concept: BiometricManager

Android provides the BiometricManager API to check biometric availability. The key method is canAuthenticate(), which returns the device’s current biometric status.

canAuthenticate() Return Values Explained

Return Value Description Recommended Action
BIOMETRIC_SUCCESS Biometric authentication available and configured Proceed with biometric authentication
BIOMETRIC_ERROR_NONE_ENROLLED Hardware supported but no biometric data enrolled Prompt user to navigate to settings
BIOMETRIC_ERROR_HW_UNAVAILABLE Hardware temporarily unavailable Provide alternative authentication (PIN/password)
BIOMETRIC_ERROR_NO_HARDWARE Device does not support biometric authentication Use traditional authentication methods only
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED Security update required Prompt user to update system

Implementation Example: Checking Biometric Availability

import android.content.Context;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricManager.Authenticators;

public class BiometricHelper {
    
    public static BiometricStatus checkBiometricAvailability(Context context) {
        BiometricManager biometricManager = BiometricManager.from(context);
        
        int canAuthenticate = biometricManager.canAuthenticate(
            Authenticators.BIOMETRIC_STRONG
        );
        
        switch (canAuthenticate) {
            case BiometricManager.BIOMETRIC_SUCCESS:
                return BiometricStatus.AVAILABLE;
                
            case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
                return BiometricStatus.NOT_ENROLLED;
                
            case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
                return BiometricStatus.TEMPORARILY_UNAVAILABLE;
                
            case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
                return BiometricStatus.NOT_SUPPORTED;
                
            case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
                return BiometricStatus.SECURITY_UPDATE_REQUIRED;
                
            default:
                return BiometricStatus.UNKNOWN;
        }
    }
    
    public enum BiometricStatus {
        AVAILABLE,
        NOT_ENROLLED,
        TEMPORARILY_UNAVAILABLE,
        NOT_SUPPORTED,
        SECURITY_UPDATE_REQUIRED,
        UNKNOWN
    }
}

Biometric Change Detection Strategies

Android does not provide a direct API to detect biometric data changes. In practice, the following strategies are needed:

Method 1: Periodic Status Checks (Recommended)

public class BiometricChangeDetector {
    private static final String PREFS_NAME = "biometric_prefs";
    private static final String KEY_LAST_STATUS = "last_biometric_status";
    
    public static boolean hasBiometricChanged(Context context) {
        SharedPreferences prefs = context.getSharedPreferences(
            PREFS_NAME, 
            Context.MODE_PRIVATE
        );
        
        // Get current status
        BiometricStatus currentStatus = BiometricHelper.checkBiometricAvailability(context);
        
        // Get last recorded status
        String lastStatus = prefs.getString(KEY_LAST_STATUS, null);
        
        // First check
        if (lastStatus == null) {
            saveCurrentStatus(context, currentStatus);
            return false;
        }
        
        // Compare status change
        boolean changed = !currentStatus.name().equals(lastStatus);
        
        if (changed) {
            // Log change and update
            Log.w("BiometricChange", "Biometric status changed from " + 
                  lastStatus + " to " + currentStatus);
            saveCurrentStatus(context, currentStatus);
        }
        
        return changed;
    }
    
    private static void saveCurrentStatus(Context context, BiometricStatus status) {
        SharedPreferences prefs = context.getSharedPreferences(
            PREFS_NAME, 
            Context.MODE_PRIVATE
        );
        prefs.edit()
             .putString(KEY_LAST_STATUS, status.name())
             .apply();
    }
}

Method 2: Combined with Keystore Validation

// Create a key in Android Keystore that requires biometric authentication
// When biometric data changes, the key becomes invalidated

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import javax.crypto.KeyGenerator;
import java.security.KeyStore;

public class KeystoreBiometricDetector {
    private static final String KEY_NAME = "biometric_key";
    
    public static void createBiometricKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES, 
            "AndroidKeyStore"
        );
        
        keyGenerator.init(
            new KeyGenParameterSpec.Builder(
                KEY_NAME,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
            )
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setUserAuthenticationRequired(true)
            .setInvalidatedByBiometricEnrollment(true)  // Key configuration
            .build()
        );
        
        keyGenerator.generateKey();
    }
    
    public static boolean isBiometricKeyValid() {
        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            return keyStore.containsAlias(KEY_NAME);
        } catch (Exception e) {
            return false;
        }
    }
}

iOS Biometric Detection Implementation

Core Concept: evaluatedPolicyDomainState

iOS’s LocalAuthentication framework provides the evaluatedPolicyDomainState property to detect Touch ID or Face ID configuration changes. This property returns a Data object that changes when biometric data is modified.

Implementation Example: Detecting Biometric Changes

import LocalAuthentication

class BiometricChangeDetector {
    private static let kBiometricStateKey = "biometric_domain_state"
    
    // Check if biometric authentication is available
    static func canUseBiometric() -> Bool {
        let context = LAContext()
        var error: NSError?
        
        return context.canEvaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics, 
            error: &error
        )
    }
    
    // Get current biometric state
    static func getCurrentBiometricState() -> Data? {
        let context = LAContext()
        var error: NSError?
        
        guard context.canEvaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics, 
            error: &error
        ) else {
            return nil
        }
        
        return context.evaluatedPolicyDomainState
    }
    
    // Check if biometric configuration has changed
    static func hasBiometricChanged() -> Bool {
        guard let currentState = getCurrentBiometricState() else {
            // Biometric unavailable or not enrolled
            return false
        }
        
        // Get previously saved state
        guard let savedState = UserDefaults.standard.data(
            forKey: kBiometricStateKey
        ) else {
            // First check, save current state
            saveBiometricState(currentState)
            return false
        }
        
        // Compare state changes
        if currentState != savedState {
            print("⚠️ Biometric configuration has changed!")
            saveBiometricState(currentState)
            return true
        }
        
        return false
    }
    
    // Save biometric state
    private static func saveBiometricState(_ state: Data) {
        UserDefaults.standard.set(state, forKey: kBiometricStateKey)
    }
    
    // Clear saved state (when user logs out)
    static func clearBiometricState() {
        UserDefaults.standard.removeObject(forKey: kBiometricStateKey)
    }
}

Practical Application Example

// Check biometric changes on app launch

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func applicationDidBecomeActive(_ application: UIApplication) {
        checkBiometricSecurity()
    }
    
    private func checkBiometricSecurity() {
        if BiometricChangeDetector.hasBiometricChanged() {
            // Biometric configuration changed, execute security measures
            handleBiometricChange()
        }
    }
    
    private func handleBiometricChange() {
        // 1. Log security event
        SecurityLogger.log("Biometric configuration changed")
        
        // 2. Clear sensitive data cache
        clearSensitiveDataCache()
        
        // 3. Require user re-authentication
        presentReauthenticationScreen()
        
        // 4. Optional: Notify backend server
        notifyServerBiometricChanged()
    }
    
    private func clearSensitiveDataCache() {
        // Clear locally cached sensitive data
        KeychainHelper.clearAll()
        UserDefaults.standard.removeObject(forKey: "cached_token")
    }
    
    private func presentReauthenticationScreen() {
        // Display re-authentication screen
        let alert = UIAlertController(
            title: "Security Change Detected",
            message: "Biometric configuration has changed. Please re-authenticate.",
            preferredStyle: .alert
        )
        
        alert.addAction(UIAlertAction(title: "Re-authenticate", style: .default) { _ in
            // Navigate to authentication screen
            self.showAuthenticationScreen()
        })
        
        window?.rootViewController?.present(alert, animated: true)
    }
}

Platform Differences Comparison

Feature Android iOS
Detection Method Status polling + Keystore invalidation evaluatedPolicyDomainState comparison
Real-time Requires active checking Requires active checking
Privacy Protection Does not reveal specific changes Does not reveal specific changes
API Support BiometricManager (API 23+) LocalAuthentication (iOS 8+)
Change Granularity Status change (available/unavailable) Data change (add/remove fingerprint)
Implementation Complexity Medium (requires Keystore integration) Simple (direct state comparison)

Security Best Practices

1. Change Detection Timing

  • App Launch: onCreate() or applicationDidBecomeActive()
  • Before Sensitive Operations: Before accessing financial data or modifying settings
  • Periodic Background Checks: Check at regular intervals (e.g., every 24 hours)

2. Actions After Change Detection

1. Log security event (timestamp, device information)
2. Clear local sensitive data cache
3. Require user re-authentication
4. Notify backend server (optional)
5. Update stored biometric status

3. Privacy Compliance

  • Inform Users: Explain biometric data handling in privacy policy
  • Obtain Consent: Get explicit consent when first using biometric authentication
  • Minimize Storage: Only store necessary status information (e.g., hash values), not raw biometric data
  • Secure Storage: Use Keychain (iOS) or EncryptedSharedPreferences (Android)

Frequently Asked Questions

Q1: Why is it necessary to detect biometric changes?

A: When users add or remove biometric data, it may indicate device ownership transfer or security threats. Sensitive applications such as financial, enterprise, and medical apps need to detect these changes to ensure data security.

Q2: Can Android directly obtain specific change details (e.g., which fingerprint was deleted)?

A: No. For privacy protection, Android only provides overall status (available/unavailable) and does not reveal which specific biometric data was changed.

Q3: When does iOS’s evaluatedPolicyDomainState change?

A: It changes when:

  • Touch ID fingerprints are added or removed
  • Face ID is reset
  • Biometric authentication is disabled

Q4: What actions should be taken after detecting a change?

A: Recommended measures:

  • Log security event
  • Clear sensitive data cache
  • Require user re-authentication
  • Notify backend server (recommended for financial apps)

Q5: How often should biometric changes be checked?

A: Recommended strategy:

  • Check on each app launch
  • Check before entering sensitive features
  • Periodic background checks (every 24 hours)

Q6: How to implement more precise change detection on Android?

A: Combine the following methods:

  • Use Keystore keys with setInvalidatedByBiometricEnrollment(true)
  • Regularly check BiometricManager status
  • Record status change timestamps to analyze abnormal patterns

Q7: Should biometric status be synced to backend server?

A: Depends on application type:

  • Financial Applications: Recommended to sync, aids risk management
  • General Applications: Optional, mainly handle locally
  • Enterprise Applications: Recommended to sync for compliance requirements

Summary

Biometric change detection is a crucial component of mobile application security architecture. While Android and iOS provide different implementation methods, the core concept remains the same: detecting biometric configuration changes while protecting user privacy.

Key Takeaways:

  1. Android: Combine BiometricManager status checks with Keystore key invalidation mechanism
  2. iOS: Use evaluatedPolicyDomainState comparison to detect changes
  3. Privacy Protection: Both platforms do not reveal specific change details
  4. Security Practices: Clear cache and require re-authentication when changes detected
  5. Compliance Requirements: Follow privacy policies and obtain user consent

As biometric technology evolves and privacy regulations strengthen, regularly consult Android and iOS official documentation to ensure implementations meet the latest standards.

Related Articles

Leave a Comment