Password-based encryption

One of the larger issues with encryption is the management and secure storage of encryption keys. Until now and in the pervious recipes, we have settled for storing the key in SharedPreferences as recommended on the Google developer's blog; however, this is not ideal for rooted devices. On rooted devices, you cannot rely on the Android system security sandbox as the root user has access to all areas. By that we mean, unlike on a unrooted device, other apps can obtain elevated root privileges.

The potential for an insecure app sandbox is an ideal case for password-based encryption (PBE). It offers the ability to create (or more correctly derive) the encryption key at runtime using a passcode/password that is usually supplied by the user.

Another solution for key management is to use a system keychain; Android's version of this is called the Android KeyStore, which we will review in a later recipe.

Getting ready

PBE is part of the Java Cryptography Extension, and so is already included in Android SDK.

In this recipe, we'll use an initialization vector (IV) and salt as part of the key derivation. We covered the IV in the previous recipe, and it helps create more randomness. So, even the same messages that are encrypted with the same key would produce different cipher texts. Salt is similar to an IV in that it is usually a random data that is added as part of the encryption process to enhance its cryptographic strength.

How to do it...

Let's get started.

  1. First, we define some helper methods to retrieve or create IV and salt. We will use them as part of the key derivation and encryption:
      private static IvParameterSpec iv;
    
      public static IvParameterSpec getIV() {
        if (iv == null) {
          iv = new IvParameterSpec(generateRandomByteArray(32));
        }
        return iv;
      }
      
      private static byte[] salt;
    
      public static byte[] getSalt() {
        if (salt == null) {
          salt = generateRandomByteArray(32);
        }
        return salt;
      }
    
      public static byte[] generateRandomByteArray(int sizeInBytes) {
        byte[] randomNumberByteArray = new byte[sizeInBytes];
        // populate the array with random bytes using non seeded secure random
        new SecureRandom().nextBytes(randomNumberByteArray);
        return randomNumberByteArray;
      }
  2. Generate the PBE key:
    public static SecretKey generatePBEKey(char[] password, byte[] salt)
          throws NoSuchAlgorithmException, InvalidKeySpecException {
    
        final int iterations = 10000;
        final int outputKeyLength = 256;
    
        SecretKeyFactory secretKeyFactory = SecretKeyFactory
            .getInstance("PBKDF2WithHmacSHA1");
        KeySpec keySpec = new PBEKeySpec(password, salt, iterations, outputKeyLength);
        SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
        return secretKey;
      }
  3. Write a sample method showing how to encrypt using a newly derived PBE key:
    public static byte[] encrpytWithPBE(String painText, String userPassword)
          throws GeneralSecurityException, IOException {
    
        SecretKey secretKey = generatePBEKey(userPassword.toCharArray(),getSalt());
    
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, getIV());
        return cipher.doFinal(painText.getBytes("UTF-8"));
      }
  4. Write a sample method showing how to decrypt cipher text using a newly derived PBE key:
    public static String decrpytWithPBE(byte[] cipherText, String userPassword)
          throws GeneralSecurityException, IOException {
    
        SecretKey secretKey = generatePBEKey(userPassword.toCharArray(),getSalt());
    
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, getIV());
        return cipher.doFinal(cipherText).toString();
      }

How it works...

In step 1, we define methods similar to the ones we have used in the previous recipes. Just to reiterate, it's essential for the salt and IV to be consistent to be able to decrypt encrypted data. For example, you could generate a salt per app and store it in SharedPreferences. Also, the size of the salt is typically the same as the key size, which in this example is 32 bytes / 256 bit. Typically, you would save the IV along with cipher text to be retrieved upon decryption.

In step 2, we derive a 256 bit AES SecretKey using PBE with the user's password. PBKDF2 is a commonly used algorithm for deriving a key from a user password; the Android implementation of this algorithm is noted as PBKDF2WithHmacSHA1.

As part of the PBEKeySpec, we define the number iterations used internally within SecretKeyFactory to generate the secret key. The larger the number of iterations, the longer the key derivation takes. To defend against Brute Force attacks, it is recommended that the time to derive the key should be more than 100ms; Android uses 10,000 iterations to generate the encryption key for encrypted backups.

Steps 3 and 4 demonstrate using the secret key with the Cipher object to encrypt and decrypt; you'll notice that these are very similar to the methods noted in an earlier recipe. But of course, for decryption, the IV and salt are not randomly generated but re-used form the encryption step.

There's more…

In Android 4.4, a subtle change was made to the SecretKeyFactory class when dealing with PBKDF2WithHmacSHA1 and Unicode passphrases. Previously, PBKDF2WithHmacSHA1 only looked at the lower eight bits of Java characters in passphrases; the change to the SecretKeyFactory class allowed the use of all the available bits in Unicode characters. To maintain backward compatibility, you can use this new key generation algorithm PBKDF2WithHmacSHA1And8bit. If you are using ASCII, this change will not affect you.

Here's a code sample of how to maintain backward compatibility:

SecretKeyFactory secretKeyFactory;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit");
} else {
secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
}

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.31.156