Skip to content

Instantly share code, notes, and snippets.

@lbalmaceda
Created August 23, 2017 18:08
Show Gist options
  • Save lbalmaceda/499bfca68b1d999fd5d756948a864f84 to your computer and use it in GitHub Desktop.
Save lbalmaceda/499bfca68b1d999fd5d756948a864f84 to your computer and use it in GitHub Desktop.
Credentials Manager v2

Secure CredentialsManager (v2)

We can enhance the existing manager to make it encrypt/decrypt the content before persisting it. Then as an extra improvement, we can ask for user authentication before allowing them to obtain the credentials.

APIs

  • API 18 introduces the algorithm I'm using to encrypt/decrypt.
  • API 21 introduces the Intent to call the LockScreen and wait for a cancel/pass signal.
  • API 23 introduces the protected keys, which means that in every key use we can ask for the user to be authenticated first.

The CredentialsManager usage can be divided in two:

  • Those users who just need encryption/decryption and
  • Those who also need the user to be authenticated before obtaining the credentials.

Definitions

Encryption/decryption

The process to encrypt and decrypt data is available since API 1, but on API 18 they added a new "Android KeyStore" provider which is safer as it makes use of the "Secure hardware" to store and make crypto operations. Later on API 19 a big stack overflow issue regarding cryptography was fixed. Next step is to choose the algorithm/blocks/padding combination that provides the best security from that API on. I decided to go with RSA/ECB/PKCS1Padding and AES/GCM/NOPADDING. (Check here for the reasons).

Because RSA can't process more than 256 bytes of data, a large credentials JSON is not the best candidate for this algorithm. I decided to encrypt the JSON using AES (which also happens to be faster) and persist that along with the RSA encrypted AES key in the storage.

  • Encryption: Credentials -> JSON -> AES encryption -> Base64 encode -> Store.
  • Decryption: Retrieve -> Base64 decode -> AES decryption -> JSON -> Credentials.

User Authentication

With API 21 a method is available to call the LockScreen and ask the user to input his PIN/Password/Pattern or Fingerprint. This call expects a result back, either RESULT_OK or something else. Beginning with API 23, we can also protect Keys stored in the KeyStore from using them if the user hasn't authenticated in the past X seconds. This just means that if you try to use it without authenticating first, then a UserNotAuthenticatedException will raise.

To keep things simple and provide support for API 21 and up instead of API 23 and up, we should request the authentication manually every time we ask for stored credentials. There's no need to protect keys behind that check as we'll authenticate all operations anyway.

Use Cases

Has credentials?

As a use case, it would be nice if I can check whether a credentials pair is available and hasn't expired without needing to authenticate first. The first method is: boolean hasValidCredentials() which checks in the Storage if the credentials exist and hasn't expired or can be renewed.

Save credentials!

Another use case would be storing a new pair of credentials. Storing shouldn't require authentication either, so it can be sync. The second method is: void saveCredentials(Credentials) which validates the input and stores the credentials in the Storage. If encryption is available it will encrypt them first.

Get credentials!

Finally, we'd like to obtain the existing credentials. If authentication is required before decryption, we need to show the LockScreen and pause awaiting for the result. When a successful result arrives we can go on and decrypt the stored credentials. The third method is: void getCredentials(Callback<Credentials, Exception>) if they are OK we return them, if they have expired and we have a refresh_token, we refresh them and return the new ones. On any other case, we throw an error

Decisions to make

  • Minimum API level: After the above explanation, the minimum API should be set to 21 (75% of the market) to support invoking the LockScreen with the user configured security from the code.
  • When to require authentication: Authentication for reading is needed, yes. But we should be able to check for existing credentials (without getting them) and save/delete credentials without authenticating first.
  • Improve the existing CredentialsManager by coding this features in that class, keeping the public API as it was. If not, we might be able to extract a "Credentials manager" interface with this 3 methods, and keep the 2 implementations separate.
  • Allow the user to decide whether to require authentication or not (if possible). Which value should be the default?
  • Allow to set a custom request_code for the LockScreen intent. This prevents using an already used code.
  • Allow to change the LockScreen title/description resources.
  • Problem: Current CredentialsManager implementation will refresh the credentials when they expire, but it won't save the new ones. The next time getCredentials is called, another network request will be made to refresh them. Should the manager store the new ones?? Or in case it shouldn't, it should somehow tell the user that the credentials have been just refreshed and should be stored again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment