Wednesday, 13 November 2013

AES Encryption/Decryption in Android using SpongyCastle

At the moment, Android have handicapped the BouncyCastle security library included with Android so that it doesn't support some of the more common and useful encryption algorithms. I will assume this was done for performance reasons but also possibly because older handsets might not support some of the algorithms? Anyway, I don't care and I want AES256 encryption.

I found out that the writers of BouncyCastle had another project called SpongyCastle which is the full library for Android and includes all the features I wanted. It is easy to download from here. Once you have the library installed, it works much the same way as BouncyCastle but with different namespaces. Here is an example of a module that includes encryption and decryption using AES256, a randomly generated key, which is stored in private memory on the device and the ability to use this key to encrypt or decrypt data which can be stored in other files.

Any comments most welcome on whether I have got this correct for Android. Note I can't promise that I have used the most efficient or suitable cipher types but this does work! Also note that the initialization vector, which ensures that encrypting the same data multiple times does not produce the same ciphertext, is added to the encrypted data and stored with it, it must therefore, be stripped off before decryption.

I am unsure as to the best way to read in data from file with an unknown length. The method I have used below is to make a preset-sized buffer (larger than the data I am storing) and then once it is read in, using the length to create a new buffer and copying the data over. This ensures that the buffer length is correct otherwise the decryption gets confused about padding information.

package org.MyCompany.MyApp;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.spongycastle.crypto.engines.AESFastEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import org.spongycastle.util.Arrays;

import android.content.Context;

/**
 * Handles the security aspects of the app such as encryption and offline storage
 * Reference takens from http://android-developers.blogspot.co.uk/2013/02/using-cryptography-to-store-credentials.html 
 * @author Luke
 *
 */
public class SecurityModule 
{
    /**
     * Store the given serialized data onto the local device
     * @param pointsSer
     * @return
     */
    public static Boolean StoreMyData(String myData)
    {
        SecretKey key = CreateOrRetrieveSecretKey();
        if ( key == null )
            return false;
        
        WriteData(encrypt(myData.getBytes(), key.getEncoded()), "mydata.ser");
        
        return true;
    }
    
    /**
     * Get the cryptographically securely stored data for this application
     * @return
     */
    public static String GetMyData()
    {
        SecretKey key = CreateOrRetrieveSecretKey();
        if ( key == null )
            return "";
        
        byte[] data;
        try 
        {
            data = ReadData("mydata.ser");
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
            return "";
        }
        
        byte[] decrypted = decrypt(data, key.getEncoded());
        return new String(decrypted);
    }
    
    /**
     * Encrypt the given plaintext bytes using the given key
     * @param data The plaintext to encrypt
     * @param key The key to use for encryption
     * @return The encrypted bytes
     */
    private static byte[] encrypt(byte[] data, byte[] key) 
    {
        // 16 bytes is the IV size for AES256
        try
        {
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
            // Random iv
            SecureRandom rng = new SecureRandom();
            byte[] ivBytes = new byte[16];
            rng.nextBytes(ivBytes);
            
            cipher.init(true, new ParametersWithIV(new KeyParameter(key), ivBytes));
            byte[] outBuf   = new byte[cipher.getOutputSize(data.length)];
        
            int processed = cipher.processBytes(data, 0, data.length, outBuf, 0);
            processed += cipher.doFinal(outBuf, processed);
            
            byte[] outBuf2 = new byte[processed + 16];        // Make room for iv
            System.arraycopy(ivBytes, 0, outBuf2, 0, 16);    // Add iv
            System.arraycopy(outBuf, 0, outBuf2, 16, processed);    // Then the encrypted data
            
            return outBuf2;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * Decrypt the given data with the given key
     * @param data The data to decrypt
     * @param key The key to decrypt with
     * @return The decrypted bytes
     */
    private static byte[] decrypt(byte[] data, byte[] key) 
    {
        // 16 bytes is the IV size for AES256
        try
        {
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
            byte[] ivBytes = new byte[16];
            System.arraycopy(data, 0, ivBytes, 0, ivBytes.length); // Get iv from data
            byte[] dataonly = new byte[data.length - ivBytes.length];
            System.arraycopy(data, ivBytes.length, dataonly, 0, data.length    - ivBytes.length);
    
            cipher.init(false, new ParametersWithIV(new KeyParameter(key), ivBytes));
            byte[] decrypted = new byte[cipher.getOutputSize(dataonly.length)];
            int len = cipher.processBytes(dataonly, 0, dataonly.length, decrypted,0);
            len += cipher.doFinal(decrypted, len);
    
            return decrypted;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * Check for a currently saved key and if not present, create a new one
     * @return The newly or previously created key
     */
    private static SecretKey CreateOrRetrieveSecretKey()
    {
        try
        {
            byte[] keyBytes = ReadKey();
            SecretKey key;
            if ( keyBytes == null )
            {
                key = GenerateKey();
                WriteKey(key.getEncoded());
            }
            else
            {
                 key = new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
            }
            return key;
        }
        catch( NoSuchAlgorithmException e )
        {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * Generate a key suitable for AES256 encryption
     * @return The generated key
     * @throws NoSuchAlgorithmException
     */
    private static SecretKey GenerateKey() throws NoSuchAlgorithmException {
        // Generate a 256-bit key
        final int outputKeyLength = 256;

        // EDIT - do not need to create SecureRandom, this is done automatically by init() if one is not provided
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(outputKeyLength);
        SecretKey key = keyGenerator.generateKey();
        return key;
    }
    
    /**
     * Write the given data to private storage
     * @param data The data to store
     * @param filename The filename to store the data in
     */
    private static void WriteData(byte[] data, String filename)
    {
        FileOutputStream fOut = null;
        try {
            fOut = MyApp.getAppContext().openFileOutput(filename, Context.MODE_PRIVATE);
            fOut.write(data);
            fOut.flush();
            fOut.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Write the given encryption key to private storage using the hard-coded filename
     * @param key The key to write
     */
    private static void WriteKey(byte[] key)
    {
        WriteData(key, "myappkey");
    }
    
    /**
     * Read data from private storage using the given filename
     * @param filename The filename whose contents to read
     * @return The contents of the file or null
     * @throws IOException
     */
    private static byte[] ReadData(String filename) throws IOException
    {
        byte[] key = new byte[5096];
        Arrays.fill(key, (byte)0);
        FileInputStream fOut = null;
        try 
        {
            fOut = MyApp.getAppContext().openFileInput(filename);
            int length = fOut.read(key);
            byte[] key2 = new byte[length];
            System.arraycopy(key, 0, key2, 0, length);
            fOut.close();
            return key2;
        } 
        catch(FileNotFoundException e)
        {
            return null;
        } 
    }
    
    /**
     * Read the encryption key from private storage
     * @return
     */
    private static byte[] ReadKey()
    {
        try 
        {
            return ReadData("myappkey"); // Hard-coded filename representing the encryption key
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
        return null;
    }
}


Post a Comment