Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encrypted save and decrypted load of an ArrayList of serializable objects

I save and load in sd card a file that contains an ArrayList of serializable object with these two methods

save method

public static void saveUserList(ArrayList<User> userList) {
        if (storageAvailable()) {
            try {
                createFolder();

                FileOutputStream userList = new FileOutputStream(
                        baseDir + File.separator + baseAppDir + File.separator
                                + fileName);

                ObjectOutputStream oos = new ObjectOutputStream(
                        userList);
                oos.writeObject(userList);

                oos.close();
            } catch (Exception exc) {
                exc.printStackTrace();
            }
        }

    }

load method

 public static ArrayList<User> loadUserList() {
        if (storageAvailable()) {
            ArrayList<User> userList = new ArrayList<User>();
            try {
                FileInputStream userList = new FileInputStream(baseDir
                        + File.separator + baseAppDir + File.separator
                        + fileName);

                ObjectInputStream oos = new ObjectInputStream(
                        userList);

                userList = (ArrayList<User>) oos.readObject();
                oos.close();

            } catch (Exception exc) {
                exc.printStackTrace();
            }

            return userList;
        } else {
            return null;
        }

    }

Now I want that the method saveUserList encrypts the content of the file during the save according a specific String keyword and the method loadUserList decrypts the file with the same keyword to return the arrayList.

How could I do this? I have given a look to CipherOutputStream but I haven't understood how should I use this.

The method proposed to use Conceal library

public static void saveUserListCrypted(ArrayList<User> userList) {
    if (storageAvailable()) {
        try {
            createFolder();
            Crypto crypto = new Crypto(
                new SharedPrefsBackedKeyChain(context),
                new SystemNativeCryptoLibrary());

            FileOutputStream userList = new FileOutputStream(
                    baseDir + File.separator + baseAppDir + File.separator
                            + fileName);

            OutputStream cryptedStream = crypto.getCipherOutputStream(
                userList, new Entity("UserList");


            ObjectOutputStream oos = new ObjectOutputStream(
                    cryptedStream);
            oos.writeObject(userList);

            oos.close();
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

}

cause this error

 this error java.lang.UnsupportedOperationException 02-12 21:29:05.026 2051-2051/com.myapp W/System.err﹕ at com.facebook.crypto.streams.NativeGCMCipherOutputStream.write
like image 327
AndreaF Avatar asked Feb 11 '14 02:02

AndreaF


3 Answers

Try (adding the appropriate checks and try blocks that I have omitted to make the code more readable) something like this to save

public static void AESObjectEncoder(Serializable object, String password, String path) {
        try {
            Cipher cipher = null;
            cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, fromStringToAESkey(password));
            SealedObject sealedObject = null;
            sealedObject = new SealedObject(object, cipher);
            CipherOutputStream cipherOutputStream = null;
            cipherOutputStream = new CipherOutputStream(new BufferedOutputStream(new FileOutputStream(path)), cipher);
            ObjectOutputStream outputStream = null;
            outputStream = new ObjectOutputStream(cipherOutputStream);
            outputStream.writeObject(sealedObject);
            outputStream.close();    
    }

and this to load

 public static Serializable AESObjectDedcoder(String password, String path) {
        Cipher cipher = null;
        Serializable userList = null;
        cipher = Cipher.getInstance("AES/CBC/PKCS7Pdding");

        //Code to write your object to file
        cipher.init(Cipher.DECRYPT_MODE, fromStringToAESkey(password));         
        CipherInputStream cipherInputStream = null;
        cipherInputStream = new CipherInputStream(new BufferedInputStream(new FileInputStream(path)), cipher);

        ObjectInputStream inputStream = null;
        inputStream = new ObjectInputStream(cipherInputStream);
        SealedObject sealedObject = null;
        sealedObject = (SealedObject) inputStream.readObject();
        userList = (Serializable) sealedObject.getObject(ciper);  
        return userList;
    }

to create a SecretKey from a String you can use this

public static SecretKey fromStringToAESkey(String s) {
        //256bit key need 32 byte
        byte[] rawKey = new byte[32];
        // if you don't specify the encoding you might get weird results
        byte[] keyBytes = new byte[0];
        try {
            keyBytes = s.getBytes("ASCII");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.arraycopy(keyBytes, 0, rawKey, 0, keyBytes.length);
        SecretKey key = new SecretKeySpec(rawKey, "AES");
        return key;
    }

NOTE:

this code encrypts and decrypts twice to show the way of use of both sealed object and Cipher streams

like image 175
Silverstorm Avatar answered Oct 29 '22 07:10

Silverstorm


I suggest taking a look at Conceal, recently released by facebook: http://facebook.github.io/conceal/

This should be a trivial modification to wrap a Conceal output stream with an ObjectOutputStream used in your current code:

public static void saveUserList(ArrayList<User> userList) {
    if (storageAvailable()) {
        try {
            createFolder();
            Crypto crypto = new Crypto(
                new SharedPrefsBackedKeyChain(context),
                new SystemNativeCryptoLibrary());

            FileOutputStream userList = new FileOutputStream(
                    baseDir + File.separator + baseAppDir + File.separator
                            + fileName);

            OutputStream cryptedStream = crypto.getCipherOutputStream(
                userList, new Entity("UserList");


            ObjectOutputStream oos = new ObjectOutputStream(
                    cryptedStream);
            oos.writeObject(userList);

            oos.close();
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

}

I'll leave the restore as an exercise for the reader. ;)

like image 23
Nick Palmer Avatar answered Oct 29 '22 09:10

Nick Palmer


You could simply use AES Encoding:

private static byte[] getEncrypt(final String key, final String message) throws GeneralSecurityException {
  final byte[] rawData = key.getBytes(Charset.forName("US-ASCII"));
  if (rawData.length != 16) {
    // If this is not 16 in length, there's a problem with the key size, nothing to do here
    throw new IllegalArgumentException("You've provided an invalid key size");
  }

  final SecretKeySpec seckeySpec = new SecretKeySpec(rawData, "AES");
  final Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");

  ciph.init(Cipher.ENCRYPT_MODE, seckeySpec, new IvParameterSpec(new byte[16]));
  return ciph.doFinal(message.getBytes(Charset.forName("US-ASCII")));
}

private static String getDecrypt(String key, byte[] encrypted) throws GeneralSecurityException {
  final byte[] rawData = key.getBytes(Charset.forName("US-ASCII"));
  if (rawData.length != 16) {
    // If this is not 16 in length, there's a problem with the key size, nothing to do here
    throw new IllegalArgumentException("Invalid key size.");
  }

  final SecretKeySpec seckeySpec = new SecretKeySpec(rawData, "AES");

  final Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
  ciph.init(Cipher.DECRYPT_MODE, seckeySpec, new IvParameterSpec(new byte[16]));
  final byte[] decryptedmess = ciph.doFinal(encrypted);

  return new String(decryptedmess, Charset.forName("US-ASCII"));
}

Then, for trying it, this example may be valid for you:

final String encrypt = "My16inLengthKey5";
final byte[] docrypt = getEncrypt(encrypt, "This is a phrase to be encrypted!");
final String douncrypt = getDecrypt(encrypt.toString(), docrypt);
Log.d("Decryption", "Decrypted phrase: " + douncrypt);

Of course, douncrypt must match This is a phrase to be encrypted!

like image 2
nKn Avatar answered Oct 29 '22 09:10

nKn