package info.guardianproject.otr;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.FileUtils;
import org.jivesoftware.smack.util.Base64;
/**
* Class created for StackOverflow by owlstead. This is open source, you are
* free to copy and use for any purpose.
* http://stackoverflow.com/questions/11783062
* /how-to-decrypt-an-encrypted-file-in-java-with-openssl-with-aes
*/
public class AES_256_CBC {
private static final int INDEX_KEY = 0;
private static final int INDEX_IV = 1;
private static final int ITERATIONS = 1;
private static final int SALT_OFFSET = 8;
private static final int SALT_SIZE = 8;
private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
private static final int KEY_SIZE_BITS = 256;
/**
* Thanks go to Ola Bini for releasing this source on his blog. The source
* was obtained from <a
* href="http://olabini.com/blog/tag/evp_bytestokey/">here</a> .
*/
public static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt,
byte[] data, int count) {
byte[][] both = new byte[2][];
byte[] key = new byte[key_len];
int key_ix = 0;
byte[] iv = new byte[iv_len];
int iv_ix = 0;
both[0] = key;
both[1] = iv;
byte[] md_buf = null;
int nkey = key_len;
int niv = iv_len;
int i = 0;
if (data == null) {
return both;
}
int addmd = 0;
for (;;) {
md.reset();
if (addmd++ > 0) {
md.update(md_buf);
}
md.update(data);
if (null != salt) {
md.update(salt, 0, 8);
}
md_buf = md.digest();
for (i = 1; i < count; i++) {
md.reset();
md.update(md_buf);
md_buf = md.digest();
}
i = 0;
if (nkey > 0) {
for (;;) {
if (nkey == 0)
break;
if (i == md_buf.length)
break;
key[key_ix++] = md_buf[i];
nkey--;
i++;
}
}
if (niv > 0 && i != md_buf.length) {
for (;;) {
if (niv == 0)
break;
if (i == md_buf.length)
break;
iv[iv_ix++] = md_buf[i];
niv--;
i++;
}
}
if (nkey == 0 && niv == 0) {
break;
}
}
for (i = 0; i < md_buf.length; i++) {
md_buf[i] = 0;
}
return both;
}
public static String decryptAscii(File f, String password) throws IOException {
byte[] headerSaltAndCipherText = null;
String raw = FileUtils.readFileToString(f);
headerSaltAndCipherText = Base64.decode(raw);
return decrypt(headerSaltAndCipherText, password);
}
public static String decryptBin(File f, String password) throws IOException {
return decrypt(FileUtils.readFileToByteArray(f), password);
}
public static String decrypt(File f, String password) throws IOException {
String raw = FileUtils.readFileToString(f);
if (raw.startsWith("Salted__"))
return decryptBin(f, password);
else
return decryptAscii(f, password);
}
// cheap copy of Arrays.copyOfRange() since its not in Android until android-9
private static byte[] copyOfRange(byte[] in, int from, int to) {
int size = to - from;
byte[] out = new byte[size];
for (int i = 0; i < size; i++)
out[i] = in[from + i];
return out;
}
public static String decrypt(byte[] headerSaltAndCipherText, String password) {
try {
// --- extract salt & encrypted ---
// header is "Salted__", ASCII encoded, if salt is being used (the
// default)
byte[] salt = copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
byte[] encrypted = copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET,
headerSaltAndCipherText.length);
// --- specify cipher and digest for EVP_BytesToKey method ---
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
MessageDigest md5 = MessageDigest.getInstance("MD5");
// --- create key and IV ---
// the IV is useless, OpenSSL might as well have use zero's
final byte[][] keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / Byte.SIZE,
aesCBC.getBlockSize(), md5, salt, password.getBytes("ASCII"), ITERATIONS);
SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
// --- initialize cipher instance and decrypt ---
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decrypted = aesCBC.doFinal(encrypted);
return new String(decrypted, "ASCII");
} catch (BadPaddingException e) {
// AKA "something went wrong"
throw new IllegalStateException(
"Bad password, algorithm, mode or padding;"
+ " no salt, wrong number of iterations or corrupted ciphertext.");
} catch (IllegalBlockSizeException e) {
throw new IllegalStateException(
"Bad algorithm, mode or corrupted (resized) ciphertext.");
} catch (GeneralSecurityException e) {
throw new IllegalStateException(e);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
}