From c5bb2099fe1508e691446d359c2e52e01cfe4a85 Mon Sep 17 00:00:00 2001 From: fabianterhorst Date: Sat, 9 Jan 2016 15:13:18 +0100 Subject: [PATCH] update encryption update encryption first stable encryption release update README --- README.md | 15 ++- iron-encryption/build.gradle | 1 + .../iron/encryption/IronEncryption.java | 98 +++++++++++++++++-- iron/build.gradle | 2 +- .../java/io/fabianterhorst/iron/DataTest.java | 9 +- .../iron/DbStoragePlainFile.java | 94 +++++++++--------- .../iron/IronEncryptionExtension.java | 10 +- publish-encryption.gradle | 2 +- publish.gradle | 2 +- 9 files changed, 164 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index e814558..063b775 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ + # Iron @@ -29,8 +30,9 @@ apply plugin: 'com.neenbedankt.android-apt' Add dependencies to your application gradle build file (only compile 'io.fabianterhorst:iron:0.5.2' is required) ```groovy -compile 'io.fabianterhorst:iron:0.5.4' +compile 'io.fabianterhorst:iron:0.5.6' compile 'io.fabianterhorst:iron-retrofit:0.3' +compile 'io.fabianterhorst:iron-encryption:0.3' //is only required for using the compiler compile 'io.fabianterhorst:iron-annotations:0.1' apt 'io.fabianterhorst:iron-compiler:0.3.1' @@ -46,6 +48,8 @@ public class MyApplication extends Application { Iron.init(getApplicationContext()); //Optional if iron-retrofit is included Iron.setLoaderExtension(new IronRetrofit()); + //Optional if iron-encryption is included + Iron.setEncryptionExtension(new IronEncryption()); } } ``` @@ -267,11 +271,12 @@ allprojects { //Latest commit compile 'com.github.FabianTerhorst:Iron:-SNAPSHOT' -compile 'com.github.fabianterhorst.iron:iron-retrofit:0.5.4' -compile 'com.github.fabianterhorst.iron:iron:0.5.4' +compile 'com.github.fabianterhorst.iron:iron-retrofit:0.5.6' +compile 'com.github.fabianterhorst.iron:iron:0.5.6' +compile 'com.github.fabianterhorst.iron:iron-encryption:0.5.6' //is only required for using the compiler -compile 'com.github.fabianterhorst.iron:iron-annotations:0.5.4' -apt 'com.github.fabianterhorst.iron:iron-compiler:0.5.4' +compile 'com.github.fabianterhorst.iron:iron-annotations:0.5.6' +apt 'com.github.fabianterhorst.iron:iron-compiler:0.5.6' ``` ### License diff --git a/iron-encryption/build.gradle b/iron-encryption/build.gradle index e203fbf..e0de04a 100644 --- a/iron-encryption/build.gradle +++ b/iron-encryption/build.gradle @@ -25,6 +25,7 @@ android { dependencies { compile project(':iron') + compile 'commons-io:commons-io:2.4' } apply from: '../publish-encryption.gradle' \ No newline at end of file diff --git a/iron-encryption/src/main/java/io/fabianterhorst/iron/encryption/IronEncryption.java b/iron-encryption/src/main/java/io/fabianterhorst/iron/encryption/IronEncryption.java index e8a3ca3..24261ce 100644 --- a/iron-encryption/src/main/java/io/fabianterhorst/iron/encryption/IronEncryption.java +++ b/iron-encryption/src/main/java/io/fabianterhorst/iron/encryption/IronEncryption.java @@ -2,9 +2,22 @@ import android.util.Log; +import org.apache.commons.io.IOUtils; + import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; import io.fabianterhorst.iron.Iron; import io.fabianterhorst.iron.IronEncryptionExtension; @@ -13,36 +26,103 @@ public class IronEncryption implements IronEncryptionExtension { protected AesCbcWithIntegrity.SecretKeys mKey; + 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 HMAC_KEY_LENGTH_BITS = 256; + private static final String HMAC_ALGORITHM = "HmacSHA256"; + private static final String RANDOM_ALGORITHM = "SHA1PRNG"; + + private static final int IV_LENGTH_BYTES = 16; + + static final AtomicBoolean prngFixed = new AtomicBoolean(false); + public IronEncryption() { mKey = getKey(); Log.d("crypt", "created with key:" + mKey.toString()); } + private static void fixPrng() { + if (!prngFixed.get()) { + synchronized (AesCbcWithIntegrity.PrngFixes.class) { + if (!prngFixed.get()) { + AesCbcWithIntegrity.PrngFixes.apply(); + prngFixed.set(true); + } + } + } + } + + private static byte[] randomBytes(int length) throws GeneralSecurityException { + fixPrng(); + SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM); + byte[] b = new byte[length]; + random.nextBytes(b); + return b; + } + + public static byte[] generateIv() throws GeneralSecurityException { + return randomBytes(IV_LENGTH_BYTES); + } + + public static AesCbcWithIntegrity.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(); + + + AesCbcWithIntegrity.SecretKeySpec secretKeySpec = new AesCbcWithIntegrity.SecretKeySpec(); + secretKeySpec.algorithm = confidentialityKey.getAlgorithm(); + secretKeySpec.format = confidentialityKey.getFormat(); + secretKeySpec.encoded = confidentialityKey.getEncoded(); + + //Now make the HMAC key + byte[] integrityKeyBytes = randomBytes(HMAC_KEY_LENGTH_BITS / 8);//to get bytes + AesCbcWithIntegrity.SecretKeySpec integrityKey = new AesCbcWithIntegrity.SecretKeySpec(); + integrityKey.generate(integrityKeyBytes, HMAC_ALGORITHM); + AesCbcWithIntegrity.SecretKeys secretKeys = new AesCbcWithIntegrity.SecretKeys(); + secretKeys.setConfidentialityKey(secretKeySpec/*confidentialityKey*/); + secretKeys.setIntegrityKey(integrityKey); + return secretKeys; + } + @Override - public String encrypt(byte[] bytes) { + public Cipher getCipher(int mode){ try { - return AesCbcWithIntegrity.encrypt(bytes, mKey).toString(); - } catch (GeneralSecurityException e) { + byte[] iv = generateIv(); + Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + cipher.init(mode, mKey.getConfidentialityKey(), new IvParameterSpec(iv)); + return cipher; + }catch(GeneralSecurityException e){ e.printStackTrace(); } return null; } @Override - public InputStream decrypt(String text) { + public ByteArrayInputStream decrypt(InputStream inputStream) { + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, getCipher(Cipher.DECRYPT_MODE)); try { - return new ByteArrayInputStream(AesCbcWithIntegrity.decrypt(new AesCbcWithIntegrity.CipherTextIvMac(text), mKey)); - } catch (GeneralSecurityException e) { - e.printStackTrace(); + return new ByteArrayInputStream(IOUtils.toByteArray(cipherInputStream)); + }catch(IOException io){ + io.printStackTrace(); } return null; } - public AesCbcWithIntegrity.SecretKeys getKey(){ + @Override + public CipherOutputStream encrypt(OutputStream outputStream) { + return new CipherOutputStream(outputStream, getCipher(Cipher.ENCRYPT_MODE)); + } + + public AesCbcWithIntegrity.SecretKeys getKey() { AesCbcWithIntegrity.SecretKeys key = Iron.chest("keys").read("key"); if (key == null) try { - key = AesCbcWithIntegrity.generateKey(); + key = generateKey(); Iron.chest("keys").write("key", key); } catch (GeneralSecurityException gse) { gse.printStackTrace(); diff --git a/iron/build.gradle b/iron/build.gradle index fef5361..527aa31 100644 --- a/iron/build.gradle +++ b/iron/build.gradle @@ -46,7 +46,7 @@ dependencies { compile 'com.esotericsoftware:kryo:3.0.3'//3.0.2 compile 'de.javakaffee:kryo-serializers:0.37'//0.33 - androidTestCompile 'io.fabianterhorst:iron-encryption:0.2@aar' + androidTestCompile 'io.fabianterhorst:iron-encryption:0.3@aar' androidTestCompile 'com.orhanobut:hawk:1.14' androidTestCompile 'com.android.support.test:runner:0.3' androidTestCompile 'com.squareup.assertj:assertj-android:1.0.0' diff --git a/iron/src/androidTest/java/io/fabianterhorst/iron/DataTest.java b/iron/src/androidTest/java/io/fabianterhorst/iron/DataTest.java index b56eb83..25d8da3 100644 --- a/iron/src/androidTest/java/io/fabianterhorst/iron/DataTest.java +++ b/iron/src/androidTest/java/io/fabianterhorst/iron/DataTest.java @@ -14,7 +14,6 @@ import java.util.List; import java.util.Map; -import io.fabianterhorst.iron.encryption.IronEncryption; import io.fabianterhorst.iron.testdata.ClassWithoutPublicNoArgConstructor; import io.fabianterhorst.iron.testdata.Person; @@ -35,7 +34,7 @@ public void setUp() throws Exception { Iron.init(getTargetContext()); Iron.chest("keys").destroy(); Iron.chest().destroy(); - Iron.setEncryptionExtension(new IronEncryption()); + //Iron.setEncryptionExtension(new IronEncryption()); } @Test @@ -51,18 +50,18 @@ public void testReadEmptyListInEmptyChest() { Iron.init(getTargetContext()); Iron.chest("keys").destroy(); Iron.chest().destroy(); - Iron.setEncryptionExtension(new IronEncryption()); + //Iron.setEncryptionExtension(new IronEncryption()); assertThat(Iron.chest().read("persons2")).isNull(); assertThat(Iron.chest().read("persons2", new ArrayList())).isNotNull(); Iron.chest().write("persons2", genPersonList(1)); Iron.chest().invalidateCache("persons2"); Iron.init(getTargetContext()); - Iron.setEncryptionExtension(new IronEncryption()); + //Iron.setEncryptionExtension(new IronEncryption()); assertThat(Iron.chest().read("persons2")).isNotNull(); assertThat(Iron.chest().read("persons2")).isNotEmpty(); Iron.chest().invalidateCache("persons2"); Iron.init(getTargetContext()); - Iron.setEncryptionExtension(new IronEncryption()); + //Iron.setEncryptionExtension(new IronEncryption()); assertThat(Iron.chest().read("persons2")).isNotNull(); assertThat(Iron.chest().read("persons2")).isNotEmpty(); } diff --git a/iron/src/main/java/io/fabianterhorst/iron/DbStoragePlainFile.java b/iron/src/main/java/io/fabianterhorst/iron/DbStoragePlainFile.java index 4b3bcb0..b72337c 100644 --- a/iron/src/main/java/io/fabianterhorst/iron/DbStoragePlainFile.java +++ b/iron/src/main/java/io/fabianterhorst/iron/DbStoragePlainFile.java @@ -10,17 +10,19 @@ import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.PushbackInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import javax.crypto.CipherOutputStream; + import de.javakaffee.kryoserializers.ArraysAsListSerializer; import de.javakaffee.kryoserializers.SynchronizedCollectionsSerializer; import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer; @@ -219,28 +221,38 @@ private File getOriginalFile(String key) { private void writeTableFile(String key, IronTable ironTable, File originalFile, File backupFile) { try { - FileOutputStream fileStream = new FileOutputStream(originalFile); - final Output kryoOutput = new Output(fileStream); - getKryo().writeObject(kryoOutput, ironTable); - if (mEncryptionExtension != null) { - String text = mEncryptionExtension.encrypt(kryoOutput.toBytes()); - kryoOutput.clear(); - //Todo : test - kryoOutput.writeString(text); - //kryoOutput.write(text.getBytes()); - kryoOutput.flush(); - fileStream.flush(); - sync(fileStream); - kryoOutput.close(); - } else { + if(mEncryptionExtension == null) { + FileOutputStream outputStream = new FileOutputStream(originalFile); + final Output kryoOutput = new Output(outputStream); + getKryo().writeObject(kryoOutput, ironTable); kryoOutput.flush(); - fileStream.flush(); - sync(fileStream); + outputStream.flush(); + sync(outputStream); kryoOutput.close(); //also close file stream + // Writing was successful, delete the backup file if there is one. + //noinspection ResultOfMethodCallIgnored + backupFile.delete(); + } else { + FileOutputStream fileOutputStream = new FileOutputStream(originalFile); + final Output kryoOutput = new Output(fileOutputStream); + CipherOutputStream outputStream = mEncryptionExtension.encrypt(kryoOutput); + Output cipherOutput = new Output(outputStream, 256) { + public void close () throws KryoException { + // Don't allow the CipherOutputStream to close the output. + } + }; + getKryo().writeObject(cipherOutput, ironTable); + cipherOutput.flush(); + try { + outputStream.close(); + } catch (IOException ex) { + throw new KryoException(ex); + } + // Writing was successful, delete the backup file if there is one. + //noinspection ResultOfMethodCallIgnored + backupFile.delete(); } - // Writing was successful, delete the backup file if there is one. - //noinspection ResultOfMethodCallIgnored - backupFile.delete(); + } catch (IOException | KryoException e) { // Clean up an unsuccessfully written file if (originalFile.exists()) { @@ -254,6 +266,7 @@ private void writeTableFile(String key, IronTable ironTable, } } + //Todo : remove (just for debug) static String convertStreamToString(java.io.InputStream is) { java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; @@ -261,33 +274,24 @@ static String convertStreamToString(java.io.InputStream is) { private E readTableFile(String key, File originalFile) { try { - PushbackInputStream pushbackInputStream = new PushbackInputStream(new FileInputStream(originalFile)); - int bufferSize = pushbackInputStream.available() + 8192; - final Input i = new Input(pushbackInputStream, bufferSize); final Kryo kryo = getKryo(); - if (mEncryptionExtension != null) { - String result = convertStreamToString(i.getInputStream()); - if (result.split(":").length == 3) { - i.close(); - InputStream stream = mEncryptionExtension.decrypt(result); - final Input decryptedInputStream = new Input(stream); - //noinspection unchecked - final IronTable ironTable = kryo.readObject(decryptedInputStream, IronTable.class); - stream.close(); - decryptedInputStream.close(); - return ironTable.mContent; - } else { - //noinspection unchecked - final IronTable ironTable = kryo.readObject(i, IronTable.class); - i.close(); - return ironTable.mContent; - } + if(mEncryptionExtension == null) { + InputStream inputStream = new FileInputStream(originalFile); + final Input i = new Input(inputStream); + //noinspection unchecked + final IronTable ironTable = kryo.readObject(i, IronTable.class); + i.close(); + return ironTable.mContent; + } else { + FileInputStream fileInputStream = new FileInputStream(originalFile); + ByteArrayInputStream inputStream = mEncryptionExtension.decrypt(fileInputStream); + Input i = new Input(inputStream, 256); + //noinspection unchecked + final IronTable ironTable = kryo.readObject(i, IronTable.class); + i.close(); + return ironTable.mContent; } - //noinspection unchecked - final IronTable ironTable = kryo.readObject(i, IronTable.class); - i.close(); - return ironTable.mContent; - } catch (/*FileNotFoundException | */KryoException | IllegalArgumentException | IOException e) { + } catch (KryoException | IllegalArgumentException | IOException e) { // Clean up an unsuccessfully written file if (originalFile.exists()) { if (!originalFile.delete()) { diff --git a/iron/src/main/java/io/fabianterhorst/iron/IronEncryptionExtension.java b/iron/src/main/java/io/fabianterhorst/iron/IronEncryptionExtension.java index 9cf170f..01652b2 100644 --- a/iron/src/main/java/io/fabianterhorst/iron/IronEncryptionExtension.java +++ b/iron/src/main/java/io/fabianterhorst/iron/IronEncryptionExtension.java @@ -1,8 +1,14 @@ package io.fabianterhorst.iron; +import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.io.OutputStream; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; public interface IronEncryptionExtension { - InputStream decrypt(String text); - String encrypt(byte[] bytes); + Cipher getCipher(int mode); + ByteArrayInputStream decrypt(InputStream text); + CipherOutputStream encrypt(OutputStream bytes); } diff --git a/publish-encryption.gradle b/publish-encryption.gradle index d370d17..d52a028 100644 --- a/publish-encryption.gradle +++ b/publish-encryption.gradle @@ -4,7 +4,7 @@ apply plugin: 'com.jfrog.bintray' def siteUrl = 'https://github.com/FabianTerhorst/Iron' def gitUrl = 'https://github.com/FabianTerhorst/Iron.git' -version = "0.2" +version = "0.3" group = "io.fabianterhorst" install { diff --git a/publish.gradle b/publish.gradle index 39116c1..9a3c13c 100644 --- a/publish.gradle +++ b/publish.gradle @@ -4,7 +4,7 @@ apply plugin: 'com.jfrog.bintray' def siteUrl = 'https://github.com/FabianTerhorst/Iron' def gitUrl = 'https://github.com/FabianTerhorst/Iron.git' -version = "0.5.4" +version = "0.5.6" group = "io.fabianterhorst" install {