Using Android Keystore for Encrypting User Credentials in Organizers App

Using Android Keystore for Encrypting User Credentials in Organizers App

In the open event orga app the user credentials need to be stored in a secured manner so that they are safe and are not an easy target for hackers. Currently they are not stored in a secure manner. The credentials cannot be stored in the shared preference because if someone gets root access to the phone then it is easy to retrieve the user credentials. So as to prevent such a disaster from happening we are using Android Keystore in the orga app.

Introduction

Android Keystore actually allows users to store the cryptographic keys in a container so that it becomes more difficult to extract them from the device. Once keys are in the keystore, they can be used for cryptographic operations with key-material remaining non-exportable. So the keystore are primarily has 2 main functions :

  1. Random generation of keys.
  2. Securely storing the keys in its container.

Following are primarily the main steps followed for encryption and decryption of data in android.

  1. When the app runs for the first time, there is random generation of keys.
  2. Suppose a secret needs to be encrypted then the key generated in the first step need to be obtained from the keystore and the data is encrypted with it and the data is stored in the shared preference .
  3. When the data needs to be decrypted the required key is obtained from the keystore and then the data is decrypted with its help.  

In the Open Event Orga App, the following methods have been followed to implement the Android Keystore:

  • A separate module is created in the data package by the name encryption which would handle the encryption and consist of an interface EncryptionService and its implementation class EncryptionServiceImpl.

 

  • The EncryptionService interface consist of the following code. This interface is implemented in the EncryptionServiceImpl and is accessed from the LoginViewmodel.
public interface EncryptionService {

  String encrypt(String credential);

  String decrypt(String encryptedCredentials);

}
  • Now the implementation of the EncryptionServiceImpl is shown below. It mainly consist of 3 main methods.

createKeys( )

This method is specifically used for generating keys. We use the KeyPairGeneratorSpec for doing this. This method was added in API> 18.This method generates public key and private key pairs such RSA. So firstly we create a start and end time for the validity range of the key pair that will be created. After this a new KeyPairGeneratorSpec object is created where we pass on the context. We also set the ALIAS here which is later used to retrieve the key from the Android key store. The start time and the end time are also set here and finally it is build.  

In the next step the KeyPairGenerator is initialized with the intended Algorithm. In this project we are using TYPE_RSA.

private void createKeys() throws NoSuchProviderException,
  NoSuchAlgorithmException, InvalidAlgorithmParameterException {

  Calendar start = new GregorianCalendar();
  Calendar end = new GregorianCalendar();
  end.add(Calendar.YEAR, 25);

  KeyPairGeneratorSpec spec =
      new KeyPairGeneratorSpec.Builder(context)
          .setAlias(ALIAS)
          .setSubject(new X500Principal(“CN=” + ALIAS))
          .setSerialNumber(BigInteger.valueOf(1337))
          .setStartDate(start.getTime())
          .setEndDate(end.getTime())
          .build();

  final KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(TYPE_RSA, KEYSTORE);
  kpGenerator.initialize(spec);

  final KeyPair kp = kpGenerator.generateKeyPair();
  Timber.d(“Public Key is: “ + kp.getPublic().toString());
}

 getPrivateKeys ( )

Now that we have generated and stored the keys in the Android Keystore.But before using the keys we need obtain them from the Android Keystore. The following code is written for obtaining them from the Keystore.

WIth the help of getEntry( ) , we actually load the key pair from the Android Keystore.

private KeyStore.PrivateKeyEntry getPrivateKey() throws KeyStoreException,
  CertificateException, NoSuchAlgorithmException,
  IOException, UnrecoverableEntryException {

  KeyStore ks = KeyStore.getInstance(KEYSTORE);

  ks.load(null);
  KeyStore.Entry entry = ks.getEntry(ALIAS, null);
  if (entry == null) {
      Timber.w(“No key found under alias: “ + ALIAS);
      Timber.w(“Generating new key…”);
      try {
          createKeys();
          ks = KeyStore.getInstance(KEYSTORE);
          ks.load(null);

          entry = ks.getEntry(ALIAS, null);

          if (entry == null) {
              Timber.w(“Generating new key failed…”);
              return null;
          }
      } catch (NoSuchProviderException e) {
          Timber.w(“Generating new key failed…”);
          e.printStackTrace();
          return null;
      } catch (InvalidAlgorithmParameterException e) {
          Timber.w(“Generating new key failed…”);
          e.printStackTrace();
          return null;
      }
  }

  if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
      Timber.w(“Not an instance of a PrivateKeyEntry”);
      Timber.w(“Exiting signData()…”);
      return null;
  }

  return (KeyStore.PrivateKeyEntry) entry;
}

  encryptString(String toEncrypt)

Finally after the keys have been obtained from the keystore the data can be easily encrypted with the help of Cipher. This returns the encrpyted string which is then stored in the shared preference.

private String encryptString(String toEncrypt) {
  if (toEncrypt != null) {
      try {
          final KeyStore.PrivateKeyEntry privateKeyEntry = getPrivateKey();
          if (privateKeyEntry != null) {
              final PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey();
              Cipher input = Cipher.getInstance(CYPHER);
              input.init(Cipher.ENCRYPT_MODE, publicKey);
              ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
              CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, input);
              cipherOutputStream.write(toEncrypt.getBytes(ENCODING));
              cipherOutputStream.close();
              byte[] vals = outputStream.toByteArray();
              return Base64.encodeToString(vals, Base64.DEFAULT);
          }
      } catch (Exception e) {
          Timber.e(Log.getStackTraceString(e));
          return null;
      }
  }
  return null;
}
  • After the encrypted values are stored in the keystore, they need to decrypted as well so that when the user logs in to the app, the login and password fields are already populated. The following procedure is followed for the same.

–The encrypted values are passed to the decrypt(  ) of the interface from the LoginViewModel.

–The decrypt method does the decryption after the ALIAS is provide to it and return the decrypted credentials.

Resources:

  • Securely Storing secrets in Android application

https://medium.com/@ericfu/securely-storing-secrets-in-an-android-application-501f030ae5a3

  • Shared preference and Android Keystore

https://medium.com/fw-engineering/sharedpreferences-and-android-keystore-c4eac3373ac7

  • Official Android Documentation

https://developer.android.com/training/articles/keystore

Close Menu