Skip to content

Implement virtual challenge response with biometric authentication

Virtual Challenge Response

It can be quite tedious to insert your HardwareKey into your phone every time you want to access your Database. Instead, why not use strong biometric authentication instead for the challenge response? Your finger should always be with you ;)

This PR does just that. You only have to use your HardwareKey once after a database change (when a new challenge is generated), and the app will save the challenge with its corresponding response for you (when enabled).

Safety: Of course, the response needs to be protected; otherwise, 2FA would be useless. The challenge (key) along with its response (value) are saved in the SharedPreferences of the application. But even if an attacker compromised your phone and extracted those SharedProferences, they wouldn't be able to do anything with them. Only a SHA-256 hash of the challenge (not reversible) and aAES/GCM/NoPadding encrypted response is stored. Without the decryption key, the data in the SharedPreferences is basically useless. The decryption key is managed by Android's Secret-KeyStore.

These are the flags set when creating the secret key:

.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(true)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true) [[SDK Version >= N]]
.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG) [[SDK Version >= R]]
  1. setRandomizedEncryptionRequired means that a new IV is generated every time data is encrypted with the key
  2. setUserAuthenticationRequired means that the user has to somehow authenticate
  3. setInvalidatedByBiometricEnrollment means that the key is no longer usable when someone configures new biometric credentials (this is set to true even on sdk versions below N)
  4. setUserAuthenticationParameters(... STRONG) means that only a strong (as defined by the Android CDD) authentication method can be used to retrieve the key (this is set to STRONG even on SDK versions below R)
  5. setUserAuthenticationParameters(0, ...) means that the user has to authenticate every time the secret key is used (for every en-/decrypt operation)

This secret key is used to en-/decrypt the response stored in the SharedPreferences. It can only be accessed by the application after the user authenticates themself. It is invalidated when new beiometric credentials are enrolled and it requires strong authentication. Multiple challenge-response pairs are safed so that you can unlock multiple databases with different challenges. The secret key and the saved challenge-response pairs can be invalidated in the settings.

Gradle migration to .kts gradle files

This PR's branch is based on this other branch which upgrades the entire gradle structure to use .kts files and a gradle/libs.versions.toml file for managing dependencies. This follows the suggested structure of a new 'Android Studio Example Project'.

.gitlab-ci.yml changes

The newer gradle versions require Java 17 (the project will still build using JavaVersion.VERSION_1_8though). That's why I had to upgrade the .gitlab-ci.yml file in order for the pipeline to work again. I went directly for a openjdk:21-jdk docker image, since the Java 17 ones are marked with multiple vulnerabilities.

TL;DR

This PR allows users to complete the challenge using a previously stored response using biometrics.

Sorry if my english is not perfect. Hope you like what I did. Cheers!

Merge request reports