/*
* Copyright (c) 2014. The Trustees of Indiana University.
*
* This version of the code is licensed under the MPL 2.0 Open Source license with additional
* healthcare disclaimer. If the user is an entity intending to commercialize any application
* that uses this code in a for-profit venture, please contact the copyright holder.
*/
package com.muzima.utils;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
public class EnDeCrypt {
private static String TAG = "EnDeCrypt";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String KEY_ALGORITHM = "AES";
// This is the best algorithm but its not implemented in 2.2 and below
private static final String PASSWORD_HASH_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String PASSWORD_HASH_ALGORITHM_FROYO = "PBEWITHSHAAND256BITAES-CBC-BC";
private static final String TEMP_FOLDER = Environment.getExternalStorageDirectory().getPath() + "/muzima/tmp/";
private static final int SALT_LENGTH = 8;
private static SecureRandom random = new SecureRandom();
private static String DELIMITER = "]";
public static void encrypt(File plainFile, String password) {
try {
File tmpFolder = new File(TEMP_FOLDER);
if (!tmpFolder.exists())
tmpFolder.mkdirs();
File tempFile = new File(TEMP_FOLDER + plainFile.getName());
FileInputStream fis = new FileInputStream(plainFile);
FileOutputStream fos = new FileOutputStream(tempFile);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
byte[] iv;
byte[] salt;
salt = generateSalt();
iv = generateIv(cipher.getBlockSize());
IvParameterSpec ivParams = new IvParameterSpec(iv);
SecretKey key = deriveKey(password, salt);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] plainFileBytes = new byte[(int) plainFile.length()];
fis.read(plainFileBytes);
byte[] encryptedStream = cipher.doFinal(plainFileBytes);
String cipherText;
if (salt != null)
cipherText = String.format("%s%s%s%s%s", MediaUtils.toBase64(salt), DELIMITER,
MediaUtils.toBase64(iv), DELIMITER, MediaUtils.toBase64(encryptedStream));
else
cipherText = MediaUtils.toBase64(encryptedStream);
// write the encrypted stream to file together with salt and IV
fos.write(cipherText.getBytes());
// and clean up
fos.flush();
fos.close();
// remove the temporary file by transferring the file as appropriate
tempFile.renameTo(new File(plainFile.getAbsolutePath()));
Log.i(TAG, "Encrypted " + plainFile.getAbsolutePath());
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
public static void decrypt(File encryptedFile, String password) {
try {
File tmpFolder = new File(TEMP_FOLDER);
if (!tmpFolder.exists())
tmpFolder.mkdirs();
File tempFile = new File(TEMP_FOLDER + encryptedFile.getName());
FileOutputStream fos = new FileOutputStream(tempFile);
BufferedReader bufferedReader = new BufferedReader(new FileReader(encryptedFile));
StringBuilder builder = new StringBuilder();
String cipherText;
String line;
while ( (line = bufferedReader.readLine()) != null ) {
builder.append(line);
}
cipherText = builder.toString();
String[] fields = cipherText.split(DELIMITER);
if (fields.length != 3) {
throw new IllegalArgumentException("Invalid encrypted text format");
}
byte[] salt = MediaUtils.fromBase64(fields[0]);
byte[] iv = MediaUtils.fromBase64(fields[1]);
byte[] cipherBytes = MediaUtils.fromBase64(fields[2]);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, deriveKey(password, salt), ivParams);
byte[] plainText = cipher.doFinal(cipherBytes);
// write the decrypted stream to file
fos.write(plainText);
fos.flush();
fos.close();
tempFile.renameTo(encryptedFile);
Log.i(TAG, "Decrypted " + encryptedFile.getAbsolutePath());
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
private static SecretKey deriveKey(String password, byte[] salt) throws Exception {
// minimum values recommended by PKCS#5
int ITERATION_COUNT = 1000;
int KEY_LENGTH = 256;
/* Use this to securely derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
SecretKeyFactory keyFactory;
keyFactory = SecretKeyFactory.getInstance(PASSWORD_HASH_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, KEY_ALGORITHM);
}
private static byte[] generateIv(int length) {
byte[] ivBytes = new byte[length];
random.nextBytes(ivBytes);
return ivBytes;
}
private static byte[] generateSalt() {
byte[] saltBytes = new byte[SALT_LENGTH];
random.nextBytes(saltBytes);
return saltBytes;
}
}