diff --git a/.gitignore b/.gitignore
index 9574e38..7ad06a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,10 @@
.gradle
-/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.idea/*
+local.properties
+.idea
.DS_Store
-/build
+build
+*.iml
+.gradle/
+local.properties
+.externalNativeBuild
+
diff --git a/aes-crypto-jre/build.gradle b/aes-crypto-jre/build.gradle
new file mode 100644
index 0000000..e3c32da
--- /dev/null
+++ b/aes-crypto-jre/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'java'
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
+
+sourceCompatibility = "1.7"
+targetCompatibility = "1.7"
+
+dependencies {
+ compile "commons-codec:commons-codec:1.10"
+ testCompile "junit:junit:4.12"
+}
\ No newline at end of file
diff --git a/aes-crypto-jre/src/main/java/com/tozny/crypto/android/AesCbcWithIntegrity.java b/aes-crypto-jre/src/main/java/com/tozny/crypto/android/AesCbcWithIntegrity.java
new file mode 100644
index 0000000..5fdd580
--- /dev/null
+++ b/aes-crypto-jre/src/main/java/com/tozny/crypto/android/AesCbcWithIntegrity.java
@@ -0,0 +1,898 @@
+/*
+ * Copyright (c) 2014-2015 Tozny LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Created by Isaac Potoczny-Jones on 11/12/14.
+ */
+
+package com.tozny.crypto.android;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
+
+
+/**
+ * Simple library for the "right" defaults for AES key generation, encryption,
+ * and decryption using 128-bit AES, CBC, PKCS5 padding, and a random 16-byte IV
+ * with SHA1PRNG. Integrity with HmacSHA256.
+ */
+public class AesCbcWithIntegrity {
+ // If the PRNG fix would not succeed for some reason, we normally will throw an exception.
+ // If ALLOW_BROKEN_PRNG is true, however, we will simply log instead.
+ private static final boolean ALLOW_BROKEN_PRNG = false;
+
+ private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
+ private static final String CIPHER = "AES";
+ private static final int AES_KEY_LENGTH_BITS = 128;
+ private static final int IV_LENGTH_BYTES = 16;
+ private static final int PBE_ITERATION_COUNT = 10000;
+ private static final int PBE_SALT_LENGTH_BITS = AES_KEY_LENGTH_BITS; // same size as key output
+ private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
+
+
+ //default for testing
+ static final AtomicBoolean prngFixed = new AtomicBoolean(false);
+
+ private static final String HMAC_ALGORITHM = "HmacSHA256";
+ private static final int HMAC_KEY_LENGTH_BITS = 256;
+
+ /**
+ * Converts the given AES/HMAC keys into a base64 encoded string suitable for
+ * storage. Sister function of keys.
+ *
+ * @param keys The combined aes and hmac keys
+ * @return a base 64 encoded AES string & hmac key as base64(aesKey) : base64(hmacKey)
+ */
+ public static String keyString(SecretKeys keys) {
+ return keys.toString();
+ }
+
+ /**
+ * An aes key derived from a base64 encoded key. This does not generate the
+ * key. It's not random or a PBE key.
+ *
+ * @param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey).
+ * @return an AES & HMAC key set suitable for other functions.
+ */
+ public static SecretKeys keys(String keysStr) throws InvalidKeyException {
+ String[] keysArr = keysStr.split(":");
+
+ if (keysArr.length != 2) {
+ throw new IllegalArgumentException("Cannot parse aesKey:hmacKey");
+
+ } else {
+ byte[] confidentialityKey = Base64.decodeBase64(keysArr[0]);
+ if (confidentialityKey.length != AES_KEY_LENGTH_BITS /8) {
+ throw new InvalidKeyException("Base64 decoded key is not " + AES_KEY_LENGTH_BITS + " bytes");
+ }
+ byte[] integrityKey = Base64.decodeBase64(keysArr[1]);
+ if (integrityKey.length != HMAC_KEY_LENGTH_BITS /8) {
+ throw new InvalidKeyException("Base64 decoded key is not " + HMAC_KEY_LENGTH_BITS + " bytes");
+ }
+
+ return new SecretKeys(
+ new SecretKeySpec(confidentialityKey, 0, confidentialityKey.length, CIPHER),
+ new SecretKeySpec(integrityKey, HMAC_ALGORITHM));
+ }
+ }
+
+ /**
+ * A function that generates random AES & HMAC keys and prints out exceptions but
+ * doesn't throw them since none should be encountered. If they are
+ * encountered, the return value is null.
+ *
+ * @return The AES & HMAC keys.
+ * @throws GeneralSecurityException if AES is not implemented on this system,
+ * or a suitable RNG is not available
+ */
+ public static SecretKeys generateKey() throws GeneralSecurityException {
+ fixPrng();
+ KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
+ // No need to provide a SecureRandom or set a seed since that will
+ // happen automatically.
+ keyGen.init(AES_KEY_LENGTH_BITS);
+ SecretKey confidentialityKey = keyGen.generateKey();
+
+ //Now make the HMAC key
+ byte[] integrityKeyBytes = randomBytes(HMAC_KEY_LENGTH_BITS / 8);//to get bytes
+ SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
+
+ return new SecretKeys(confidentialityKey, integrityKey);
+ }
+
+ /**
+ * A function that generates password-based AES & HMAC keys. It prints out exceptions but
+ * doesn't throw them since none should be encountered. If they are
+ * encountered, the return value is null.
+ *
+ * @param password The password to derive the keys from.
+ * @return The AES & HMAC keys.
+ * @throws GeneralSecurityException if AES is not implemented on this system,
+ * or a suitable RNG is not available
+ */
+ public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
+ fixPrng();
+ //Get enough random bytes for both the AES key and the HMAC key:
+ KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
+ PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
+ SecretKeyFactory keyFactory = SecretKeyFactory
+ .getInstance(PBE_ALGORITHM);
+ byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
+
+ // Split the random bytes into two parts:
+ byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS /8);
+ byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS /8, AES_KEY_LENGTH_BITS /8 + HMAC_KEY_LENGTH_BITS /8);
+
+ //Generate the AES key
+ SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
+
+ //Generate the HMAC key
+ SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
+
+ return new SecretKeys(confidentialityKey, integrityKey);
+ }
+
+ /**
+ * A function that generates password-based AES & HMAC keys. See generateKeyFromPassword.
+ * @param password The password to derive the AES/HMAC keys from
+ * @param salt A string version of the salt; base64 encoded.
+ * @return The AES & HMAC keys.
+ * @throws GeneralSecurityException
+ */
+ public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException {
+ return generateKeyFromPassword(password, Base64.decodeBase64(salt));
+ }
+
+ /**
+ * Generates a random salt.
+ * @return The random salt suitable for generateKeyFromPassword.
+ */
+ public static byte[] generateSalt() throws GeneralSecurityException {
+ return randomBytes(PBE_SALT_LENGTH_BITS);
+ }
+
+ /**
+ * Converts the given salt into a base64 encoded string suitable for
+ * storage.
+ *
+ * @param salt
+ * @return a base 64 encoded salt string suitable to pass into generateKeyFromPassword.
+ */
+ public static String saltString(byte[] salt) {
+ return Base64.encodeBase64String(salt);
+ }
+
+
+ /**
+ * Creates a random Initialization Vector (IV) of IV_LENGTH_BYTES.
+ *
+ * @return The byte array of this IV
+ * @throws GeneralSecurityException if a suitable RNG is not available
+ */
+ public static byte[] generateIv() throws GeneralSecurityException {
+ return randomBytes(IV_LENGTH_BYTES);
+ }
+
+ private static byte[] randomBytes(int length) throws GeneralSecurityException {
+ fixPrng();
+ SecureRandom random = new SecureRandom();
+ byte[] b = new byte[length];
+ random.nextBytes(b);
+ return b;
+ }
+
+ /*
+ * -----------------------------------------------------------------
+ * Encryption
+ * -----------------------------------------------------------------
+ */
+
+ /**
+ * Generates a random IV and encrypts this plain text with the given key. Then attaches
+ * a hashed MAC, which is contained in the CipherTextIvMac class.
+ *
+ * @param plaintext The text that will be encrypted, which
+ * will be serialized with UTF-8
+ * @param secretKeys The AES & HMAC keys with which to encrypt
+ * @return a tuple of the IV, ciphertext, mac
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if UTF-8 is not supported in this system
+ */
+ public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return encrypt(plaintext, secretKeys, "UTF-8");
+ }
+
+ /**
+ * Generates a random IV and encrypts this plain text with the given key. Then attaches
+ * a hashed MAC, which is contained in the CipherTextIvMac class.
+ *
+ * @param plaintext The bytes that will be encrypted
+ * @param secretKeys The AES & HMAC keys with which to encrypt
+ * @return a tuple of the IV, ciphertext, mac
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if the specified encoding is invalid
+ */
+ public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys, String encoding)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return encrypt(plaintext.getBytes(encoding), secretKeys);
+ }
+
+ /**
+ * Generates a random IV and encrypts this plain text with the given key. Then attaches
+ * a hashed MAC, which is contained in the CipherTextIvMac class.
+ *
+ * @param plaintext The text that will be encrypted
+ * @param secretKeys The combined AES & HMAC keys with which to encrypt
+ * @return a tuple of the IV, ciphertext, mac
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ */
+ public static CipherTextIvMac encrypt(byte[] plaintext, SecretKeys secretKeys)
+ throws GeneralSecurityException {
+ byte[] iv = generateIv();
+ Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
+ aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
+
+ /*
+ * Now we get back the IV that will actually be used. Some Android
+ * versions do funny stuff w/ the IV, so this is to work around bugs:
+ */
+ iv = aesCipherForEncryption.getIV();
+ byte[] byteCipherText = aesCipherForEncryption.doFinal(plaintext);
+ byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(iv, byteCipherText);
+
+ byte[] integrityMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
+ return new CipherTextIvMac(byteCipherText, iv, integrityMac);
+ }
+
+ /**
+ * Ensures that the PRNG is fixed. Should be used before generating any keys.
+ * Will only run once, and every subsequent call should return immediately.
+ */
+ private static void fixPrng() {
+ if (!prngFixed.get()) {
+ synchronized (PrngFixes.class) {
+ if (!prngFixed.get()) {
+ PrngFixes.apply();
+ prngFixed.set(true);
+ }
+ }
+ }
+ }
+
+ /*
+ * -----------------------------------------------------------------
+ * Decryption
+ * -----------------------------------------------------------------
+ */
+
+ /**
+ * AES CBC decrypt.
+ *
+ * @param civ The cipher text, IV, and mac
+ * @param secretKeys The AES & HMAC keys
+ * @param encoding The string encoding to use to decode the bytes after decryption
+ * @return A string derived from the decrypted bytes (not base64 encoded)
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if the encoding is unsupported
+ */
+ public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys, String encoding)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return new String(decrypt(civ, secretKeys), encoding);
+ }
+
+ /**
+ * AES CBC decrypt.
+ *
+ * @param civ The cipher text, IV, and mac
+ * @param secretKeys The AES & HMAC keys
+ * @return A string derived from the decrypted bytes, which are interpreted
+ * as a UTF-8 String
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if UTF-8 is not supported
+ */
+ public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return decryptString(civ, secretKeys, "UTF-8");
+ }
+
+ /**
+ * AES CBC decrypt.
+ *
+ * @param civ the cipher text, iv, and mac
+ * @param secretKeys the AES & HMAC keys
+ * @return The raw decrypted bytes
+ * @throws GeneralSecurityException if MACs don't match or AES is not implemented
+ */
+ public static byte[] decrypt(CipherTextIvMac civ, SecretKeys secretKeys)
+ throws GeneralSecurityException {
+
+ byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(civ.getIv(), civ.getCipherText());
+ byte[] computedMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
+ if (constantTimeEq(computedMac, civ.getMac())) {
+ Cipher aesCipherForDecryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
+ aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(),
+ new IvParameterSpec(civ.getIv()));
+ return aesCipherForDecryption.doFinal(civ.getCipherText());
+ } else {
+ throw new GeneralSecurityException("MAC stored in civ does not match computed MAC.");
+ }
+ }
+
+ /*
+ * -----------------------------------------------------------------
+ * Helper Code
+ * -----------------------------------------------------------------
+ */
+
+ /**
+ * Generate the mac based on HMAC_ALGORITHM
+ * @param integrityKey The key used for hmac
+ * @param byteCipherText the cipher text
+ * @return A byte array of the HMAC for the given key & ciphertext
+ * @throws NoSuchAlgorithmException
+ * @throws InvalidKeyException
+ */
+ public static byte[] generateMac(byte[] byteCipherText, SecretKey integrityKey) throws NoSuchAlgorithmException, InvalidKeyException {
+ //Now compute the mac for later integrity checking
+ Mac sha256_HMAC = Mac.getInstance(HMAC_ALGORITHM);
+ sha256_HMAC.init(integrityKey);
+ return sha256_HMAC.doFinal(byteCipherText);
+ }
+ /**
+ * Holder class that has both the secret AES key for encryption (confidentiality)
+ * and the secret HMAC key for integrity.
+ */
+
+ public static class SecretKeys {
+ private SecretKey confidentialityKey;
+ private SecretKey integrityKey;
+
+ /**
+ * Construct the secret keys container.
+ * @param confidentialityKeyIn The AES key
+ * @param integrityKeyIn the HMAC key
+ */
+ public SecretKeys(SecretKey confidentialityKeyIn, SecretKey integrityKeyIn) {
+ setConfidentialityKey(confidentialityKeyIn);
+ setIntegrityKey(integrityKeyIn);
+ }
+
+ public SecretKey getConfidentialityKey() {
+ return confidentialityKey;
+ }
+
+ public void setConfidentialityKey(SecretKey confidentialityKey) {
+ this.confidentialityKey = confidentialityKey;
+ }
+
+ public SecretKey getIntegrityKey() {
+ return integrityKey;
+ }
+
+ public void setIntegrityKey(SecretKey integrityKey) {
+ this.integrityKey = integrityKey;
+ }
+
+ /**
+ * Encodes the two keys as a string
+ * @return base64(confidentialityKey):base64(integrityKey)
+ */
+ @Override
+ public String toString () {
+ return Base64.encodeBase64String(getConfidentialityKey().getEncoded())
+ + ":" + Base64.encodeBase64String(getIntegrityKey().getEncoded());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + confidentialityKey.hashCode();
+ result = prime * result + integrityKey.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SecretKeys other = (SecretKeys) obj;
+ if (!integrityKey.equals(other.integrityKey))
+ return false;
+ if (!confidentialityKey.equals(other.confidentialityKey))
+ return false;
+ return true;
+ }
+ }
+
+
+ /**
+ * Simple constant-time equality of two byte arrays. Used for security to avoid timing attacks.
+ * @param a
+ * @param b
+ * @return true iff the arrays are exactly equal.
+ */
+ public static boolean constantTimeEq(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ int result = 0;
+ for (int i = 0; i < a.length; i++) {
+ result |= a[i] ^ b[i];
+ }
+ return result == 0;
+ }
+
+ /**
+ * Holder class that allows us to bundle ciphertext and IV together.
+ */
+ public static class CipherTextIvMac {
+ private final byte[] cipherText;
+ private final byte[] iv;
+ private final byte[] mac;
+
+ public byte[] getCipherText() {
+ return cipherText;
+ }
+
+ public byte[] getIv() {
+ return iv;
+ }
+
+ public byte[] getMac() {
+ return mac;
+ }
+
+ /**
+ * Construct a new bundle of ciphertext and IV.
+ * @param c The ciphertext
+ * @param i The IV
+ * @param h The mac
+ */
+ public CipherTextIvMac(byte[] c, byte[] i, byte[] h) {
+ cipherText = new byte[c.length];
+ System.arraycopy(c, 0, cipherText, 0, c.length);
+ iv = new byte[i.length];
+ System.arraycopy(i, 0, iv, 0, i.length);
+ mac = new byte[h.length];
+ System.arraycopy(h, 0, mac, 0, h.length);
+ }
+
+ /**
+ * Constructs a new bundle of ciphertext and IV from a string of the
+ * format base64(iv):base64(ciphertext).
+ *
+ * @param base64IvAndCiphertext A string of the format
+ * iv:ciphertext The IV and ciphertext must each
+ * be base64-encoded.
+ */
+ public CipherTextIvMac(String base64IvAndCiphertext) {
+ String[] civArray = base64IvAndCiphertext.split(":");
+ if (civArray.length != 3) {
+ throw new IllegalArgumentException("Cannot parse iv:ciphertext:mac");
+ } else {
+ iv = Base64.decodeBase64(civArray[0]);
+ mac = Base64.decodeBase64(civArray[1]);
+ cipherText = Base64.decodeBase64(civArray[2]);
+ }
+ }
+
+ /**
+ * Concatinate the IV to the cipherText using array copy.
+ * This is used e.g. before computing mac.
+ * @param iv The IV to prepend
+ * @param cipherText the cipherText to append
+ * @return iv:cipherText, a new byte array.
+ */
+ public static byte[] ivCipherConcat(byte[] iv, byte[] cipherText) {
+ byte[] combined = new byte[iv.length + cipherText.length];
+ System.arraycopy(iv, 0, combined, 0, iv.length);
+ System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
+ return combined;
+ }
+
+ /**
+ * Encodes this ciphertext, IV, mac as a string.
+ *
+ * @return base64(iv) : base64(mac) : base64(ciphertext).
+ * The iv and mac go first because they're fixed length.
+ */
+ @Override
+ public String toString() {
+ String ivString = Base64.encodeBase64String(iv);
+ String cipherTextString = Base64.encodeBase64String(cipherText);
+ String macString = Base64.encodeBase64String(mac);
+ return String.format(ivString + ":" + macString + ":" + cipherTextString);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(cipherText);
+ result = prime * result + Arrays.hashCode(iv);
+ result = prime * result + Arrays.hashCode(mac);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CipherTextIvMac other = (CipherTextIvMac) obj;
+ if (!Arrays.equals(cipherText, other.cipherText))
+ return false;
+ if (!Arrays.equals(iv, other.iv))
+ return false;
+ if (!Arrays.equals(mac, other.mac))
+ return false;
+ return true;
+ }
+ }
+
+ /**
+ * Copy the elements from the start to the end
+ *
+ * @param from the source
+ * @param start the start index to copy
+ * @param end the end index to finish
+ * @return the new buffer
+ */
+ private static byte[] copyOfRange(byte[] from, int start, int end) {
+ int length = end - start;
+ byte[] result = new byte[length];
+ System.arraycopy(from, start, result, 0, length);
+ return result;
+ }
+
+ /**
+ * Fixes for the RNG as per
+ * http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will Google be held liable for any damages arising
+ * from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ *
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in
+ * the application's {@code onCreate}.
+ */
+ public static final class PrngFixes {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+
+ /** Hidden constructor to prevent instantiation. */
+ private PrngFixes() {
+ }
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be
+ * applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ //installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if
+ * the fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be
+ * applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+
+/*
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class
+ .forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException("Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ if (ALLOW_BROKEN_PRNG) {
+ Logger.getLogger(PrngFixes.class.getSimpleName()).warning("Failed to seed OpenSSL PRNG" + e.getMessage());
+ } else {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }*/
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as
+ * the default. Does nothing if the implementation is already the
+ * default or if there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be
+ * applied.
+ */
+ private static void installLinuxPRNGSecureRandom() throws SecurityException {
+
+
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
+
+ // Insert and check the provider atomically.
+ // The official Android Java libraries use synchronized methods for
+ // insertProviderAt, etc., so synchronizing on the class should
+ // make things more stable, and prevent race conditions with other
+ // versions of this code.
+ synchronized (java.security.Security.class) {
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!secureRandomProviders[0].getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider"))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!rng1.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
+ if (ALLOW_BROKEN_PRNG) {
+ Logger.getLogger(PrngFixes.class.getSimpleName()).warning(
+ "new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
+ return;
+ } else {
+ throw new SecurityException("new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+ }
+
+ SecureRandom rng2 = null;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ if (ALLOW_BROKEN_PRNG) {
+ Logger.getLogger(PrngFixes.class.getSimpleName()).warning("SHA1PRNG not available"+ e.getMessage());
+ return;
+ } else {
+ new SecurityException("SHA1PRNG not available", e);
+ }
+ }
+ if (!rng2.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
+ if (ALLOW_BROKEN_PRNG) {
+ Logger.getLogger(PrngFixes.class.getSimpleName()).warning(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ + rng2.getProvider().getClass());
+ return;
+ } else {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ + rng2.getProvider().getClass());
+ }
+ }
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need
+ // to prevent them from getting the default implementation whose
+ // output may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG (
+ * {@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a
+ * seed are passed through to the Linux PRNG (/dev/urandom).
+ * Instances of this class seed themselves by mixing in the current
+ * time, PID, UID, build fingerprint, and hardware serial number
+ * (where available) into Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not
+ * yet opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not
+ * yet opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed
+ * because each instance needs to seed itself if the client does not
+ * explicitly seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not
+ // writable Log and ignore.
+ Logger.getLogger(PrngFixes.class.getSimpleName()).warning( "Failed to mix seed into "
+ + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream
+ // between DataInputStream and FileInputStream if you need
+ // higher PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open " + URANDOM_FILE
+ + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ }
+}
diff --git a/aes-crypto-jre/src/test/java/com/tozny/crypto/android/AesCbcWithIntegrityTest.java b/aes-crypto-jre/src/test/java/com/tozny/crypto/android/AesCbcWithIntegrityTest.java
new file mode 100644
index 0000000..e61c905
--- /dev/null
+++ b/aes-crypto-jre/src/test/java/com/tozny/crypto/android/AesCbcWithIntegrityTest.java
@@ -0,0 +1,32 @@
+package com.tozny.crypto.android;
+
+import com.tozny.crypto.android.AesCbcWithIntegrity;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ */
+public class AesCbcWithIntegrityTest {
+
+ public AesCbcWithIntegrityTest() {
+ }
+
+ @Test
+ public void testEncryptionDecryptionWorks() throws Exception {
+ AesCbcWithIntegrity.SecretKeys keys = AesCbcWithIntegrity.generateKey();
+
+ //encrypt
+ AesCbcWithIntegrity.CipherTextIvMac cipherTextIvMac = AesCbcWithIntegrity.encrypt("some test", keys);
+ //store or send to server
+ String ciphertextString = cipherTextIvMac.toString();
+
+ //decrypt
+ String plainText = AesCbcWithIntegrity.decryptString(cipherTextIvMac, keys);
+
+ assertEquals("some test", plainText);
+
+ }
+
+}
\ No newline at end of file
diff --git a/aes-crypto-sample-app/.gitignore b/aes-crypto-sample-app/.gitignore
deleted file mode 100644
index 9a7b916..0000000
--- a/aes-crypto-sample-app/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-*.iml
-.gradle
-/local.properties
-.DS_Store
-/build
-.externalNativeBuild
-.idea
diff --git a/aes-crypto/.gitignore b/aes-crypto/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/aes-crypto/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/aes-crypto/aes-crypto.iml b/aes-crypto/aes-crypto.iml
deleted file mode 100644
index f435a8d..0000000
--- a/aes-crypto/aes-crypto.iml
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/java-aes-crypto.iml b/java-aes-crypto.iml
deleted file mode 100644
index 0bb6048..0000000
--- a/java-aes-crypto.iml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/settings.gradle b/settings.gradle
index 92a8f0b..2749b9f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':sample', ':aes-crypto'
+include ':sample', ':aes-crypto', ':aes-crypto-jre'