2

I am using EncryptedSharedPreferences to store encrypted data.

val biometricManager = BiometricManager.from(this)
val hasFingerprint = biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS

val advanceSpec = KeyGenParameterSpec.Builder(
    "master_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
    setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    setKeySize(256)
    if(hasFingerprint){
        setUserAuthenticationRequired(true)
        setUserAuthenticationValidityDurationSeconds(1)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
            setInvalidatedByBiometricEnrollment(false)
        }
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            setIsStrongBoxBacked(true)
            setUserConfirmationRequired(true)
        }
    }
}.build()

val masterKey = MasterKeys.getOrCreate(advanceSpec)
val preferences = EncryptedSharedPreferences.create(
    "TestPreferences",
    masterKey,
    applicationContext,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

Actually, I was missing the BiometricPrompt part. I thought that calling setUserAuthenticationRequired(true) would automatically handle authenticating user. but we have to show the BiometricPrompt ourselves. isUserAuthenticationRequired only ensures that the key would only be activated when a user is authorized.

val biometricPrompt = BiometricPrompt(
            activity,
            ContextCompat.getMainExecutor(activity),
            object: BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    super.onAuthenticationSucceeded(result)
                    createSharedPreferences()
                }
            }
        )

        biometricPrompt.authenticate(promptInfo)

But, there is a problem. It only throws the UserNotAuthenticatedException while creating the EncryptedSharedPreferences. After that we can perform read and write operations for as long as we need. It isn't taking into consideration the setUserAuthenticationValidityDurationSeconds(1).

Ismail Shaikh
  • 482
  • 4
  • 13
  • Are you able to store and fetch the data in the created encrypted shared preference? – Swapnil Jun 03 '20 at 16:10
  • yes, I'm able to store and fetch the data. – Ismail Shaikh Jun 04 '20 at 09:54
  • Did you check if `hasFingerprint` is returning true? – Swapnil Jun 04 '20 at 14:07
  • yes it's returning true – Ismail Shaikh Jun 04 '20 at 16:09
  • For me, the code you wrote is throwing `UserNotAuthenticatedException` while trying to create the encryptedsharedpreference object, if I don't explicitly ask for a user to authenticate using a `BiometricPrompt` https://developer.android.com/training/sign-in/biometric-auth . I am using a Smasung Galaxy M20 running android 10. But I am a little confused that it allows me to edit the sharedpreference once I have created its object even if I do the read and write operation beyond 1 second time of authentication. – Swapnil Jun 07 '20 at 11:09
  • yes I found that too – Ismail Shaikh Jun 11 '20 at 11:26
  • I don't know what was the issue before but It wasn't throwing any exception before – Ismail Shaikh Jun 11 '20 at 11:26
  • I found the reason why it does not require authentication post 1 second. It is because, once the encrypted shared preference is intiated, it loads the keys used to encrypt and decrypt the data in the memory and then those are used to access the data from the files. You can read the code inside Encrypted shared preference class. This is pretty evident. – Swapnil Jun 11 '20 at 12:12
  • yes I got that too. thanks – Ismail Shaikh Jun 12 '20 at 14:01
  • @Swapnil you can post this comment as the answer so that I can accept it and close this question – Ismail Shaikh Jun 12 '20 at 14:02
  • @IsmailShaikh, I was trying to use the same code as above ( using Builder now as MasterKeys is deprecated) , as you mentioned "It only throws the UserNotAuthenticatedException while creating the EncryptedSharedPreferences" , i'm facing it too, so are you just catching the exception and proceeding with using encryptedsharedpreference, for creating new keys and values in encryptedsharedpreference ? – Jitendar M Jun 24 '20 at 11:21
  • No, when it throws an exception it means that the user is not authenticated. So, after catching the exception, just show a ```BiometricPrompt``` and create the ```EncryptedSharedPreferences``` in the ```onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result)```callback. – Ismail Shaikh Jun 25 '20 at 07:35

1 Answers1

2

I found the reason why it does not require authentication post 1 second. It is because, once the encrypted shared preference is intiated, it loads the keys used to encrypt and decrypt the data in the memory and then those are used to access the data from the files. You can read the code inside EncryptedSharedPreference class. This is pretty evident.

KeysetHandle daeadKeysetHandle = new AndroidKeysetManager.Builder()
                .withKeyTemplate(prefKeyEncryptionScheme.getKeyTemplate())
                .withSharedPref(context, KEY_KEYSET_ALIAS, fileName)
                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
                .build().getKeysetHandle();
        KeysetHandle aeadKeysetHandle = new AndroidKeysetManager.Builder()
                .withKeyTemplate(prefValueEncryptionScheme.getKeyTemplate())
                .withSharedPref(context, VALUE_KEYSET_ALIAS, fileName)
                .withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
                .build().getKeysetHandle();

        DeterministicAead daead = daeadKeysetHandle.getPrimitive(DeterministicAead.class);
        Aead aead = aeadKeysetHandle.getPrimitive(Aead.class);

        return new EncryptedSharedPreferences(fileName, masterKeyAlias,
                context.getSharedPreferences(fileName, Context.MODE_PRIVATE), aead, daead);
Swapnil
  • 1,870
  • 2
  • 23
  • 48