infinum / Android-Goldfinger

Android library to simplify Biometric authentication implementation.
Apache License 2.0
653 stars 73 forks source link

Asymmetric Crypto support #51

Closed mezpahlan closed 4 years ago

mezpahlan commented 4 years ago

Hi there, I really like this library and the API is really nice but I'm struggling to understand how I can do the following. The use case is that I want to be able encrypt data without showing the BiometricPrompt but only decrypt after the user has successfully authenticated via the BiometricPrompt.

I believe this is known as Asymmetric crypto (although I may have confused the terms, forgive me). There is a rather outdated sample in the Google Android Samples for this that uses a KeyPairGenerator which is what I want to do but I also want to be able to use your library.

I notice that your CryptoObjectFactory.Default implementation assumes Symmetric crypto and also that the library in general assumes that for all encrypt / decrypt operations we would show a BiometricPrompt. So I'm finding it a little difficult customise the factory for my needs.

Here's a fuller usecase:

  1. User attempts to login, app displays biometric prompt
  2. User successfully authenticates, app unlocks crypto object and decrypts secret
  3. Secret sent to server for verification, server verifies, and sends a different refreshed secret
  4. Upon response user is logged in, app encrypts new secret without asking user to reauthenticate via biometric prompt ready for the next time

What I can't do at the moment (or at least I don't know how to achieve) with your library is step 4 because I need to ask the user to reauthenticate.

Incidently this is how Whorlwind works but the docs are super confusing, they don't supply easy integration to the new BiometricPrompt and your library seems better maintained.

Is this possible / desirable?

domagojkorman commented 4 years ago

Hi,

thanks for the detailed question.

Regarding step 4, why do you need to encrypt received secret? As much as I understand in the asymmetric cryptography, you should already receive the secret that is encrypted with server's public key.

mezpahlan commented 4 years ago

And if it's not 😉? It's also my assumption that everything in Shared Preferences is potentially hackable so you wouldn't want to put anything sensitive there without some form of protection.

If this secret allows elevated privileges without further checks then it needs to be secured on disk, right?

It's possible that I've got this whole authentication thing misunderstood, but I thought I would ask just in case.

domagojkorman commented 4 years ago

I looked how Whorlwind works and it is possible to have the same behavior as them.

You need to implement custom CryptoFactory.

CryptoFactory should create and initialize Cipher with RSA algorithm and use either public key for encryption or private key for decryption operation. Keep in mind you should store public key like they do here.

Crypto should work out of the box in this case as it will use CryptoObject created by your CryptoFactory.

domagojkorman commented 4 years ago

And if it's not 😉? It's also my assumption that everything in Shared Preferences is potentially hackable so you wouldn't want to out anything sensitive there without some form of protection.

If this secret allows elevated privileges without further checks then it needs to be secured on disk, right?

It's possible that I've got this whole authentication thing misunderstood, but I thought I would ask just in case.

IMO this is not Goldfinger's scope. In this case you should create unlocked Cipher using server's public key and encrypt your data before you save it in SharedPrefs.

What Goldfinger tries to achieve is that it takes locked CryptoObject, handles user authentication and then helps with encrypting/decrypting data using the now unlocked CryptoObject.

Seems like in your case you don't need Fingerprint authentication but standard cipher which you want to use to encrypt data received by the API if I understood correctly.

mezpahlan commented 4 years ago

Yes I think I agree too. Can I just confirm my understanding so far if you don't mind?

  1. To register a user, use Goldfinger to do a simple authentication only.
  2. Use my own implementation of creating an asymmetric CryptoObject and use that to encrypt and write data independent of Goldfinger.
  3. When I want the user to read and decrypt data I first authenticate the user with Goldfinger and then use my own implementation of the previously created CrytpoObject to facilitate this.
  4. Because the Rx extensions exists I can flatMap these operations relatively simply.

I think that's the best I can get for my use case until I change my use case.

What I was trying to do over the weekend was create a custom CryptoObjectFactory to create the asymmetric CryptoObject and use that for read / write operations. I could do this with the exception of my own requirement of not having to reauthenticate to write.

My problem was that I have to create either a BiometricPrompt.CryptoObject or return null here. Returning a BiometricPrompt.CryptoObject for my write operation will tell Goldfinger to initiate the prompt to unlock the CryptoObject (which is perfectly fine, just not what I want). And returning null throws an error from the same function.

I was hoping something like this could be implemented using a new Params and a separate branch of the if statement here. Maybe something like this:

@Override
void onCryptoObjectCreated(@Nullable BiometricPrompt.CryptoObject cryptoObject) {
creatingCryptoObject = false;
if (cryptoObject != null) {
    startNativeFingerprintAuthentication(params, callback, cryptoObject);
  } else (if params.useAsymmetricWrite()) {
    log("Using public key for encryption");
    // Do something here
  } else {
    log("Failed to create CryptoObject");
    callback.onError(new CryptoObjectInitException());
  }
}

As you quite rightly say, this isn't the scope of Goldfinger.

domagojkorman commented 4 years ago

Standard asymmetric cryptography uses your public key when you want to encrypt message sent to the server, and private key when you want to decrypt the message received by the server.

In that flow, when user registers using the password, you want to encrypt his password using your public key and send it to the server.

To do that you need to initialize your Cryptography like this. See how they fetch publicKey, initialize Cipher with publickey and Goldfinger will automatically use that Cipher to encrypt the value which you can use.

Afterwards, when you receive new value from the server (it should be encrypted) you should store it into shared prefs.

When user wants to login again, you want to authenticate the user by decrypting the saved value. Here you need to initialize Cipher like this.

You load existing key which is your private key, initialize cipher, and with that you can decrypt the value. When user authenticates himself you will get decrypted value which you can use.

As much as I can understand that is exactly what Whorlwind does.

Encrypting/Decrypting

As much as I can see what you need is to do some encyption/decryption outside of Goldfinger flow.

In that case, separate Cipher initialization in some sort of Cipher Factory and reuse the factory for Goldfinger initialization and for encryption/decryption outside of Goldfinger flow. Here you can see how to use existing cipher to do encryption or decryption operation.

TL;DR: 1) Encrypt password with Goldfinger, send to server 2) Encrypt server response if it is not encrypted using same cipher but without Goldfinger 3) User wants to login, decrypt server secret with Goldfinger from prefs 4) Do whatever is needed with now decrypted secret

domagojkorman commented 4 years ago

This is out of scope so I am closing this issue. Hope I managed to help and clear some things out.

mezpahlan commented 4 years ago

Thanks again, you have. Again, really nice library.