Skip to content

Commit

Permalink
update encryption
Browse files Browse the repository at this point in the history
update encryption
first stable encryption release
update README
  • Loading branch information
FabianTerhorst committed Jan 9, 2016
1 parent 352c511 commit c5bb209
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 69 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<img src="https://img.shields.io/bintray/v/fabianterhorst/maven/iron-compiler.svg?label=Compiler"></img>
<img src="https://img.shields.io/bintray/v/fabianterhorst/maven/iron-retrofit.svg?label=Retrofit"></img>
<img src="https://img.shields.io/bintray/v/fabianterhorst/maven/iron-annotations.svg?label=Annotations"></img>
<img src="https://img.shields.io/bintray/v/fabianterhorst/maven/iron-encryption.svg?label=Encryption"></img>
<img src="https://img.shields.io/github/license/fabianterhorst/iron.svg"></img>

# Iron
Expand All @@ -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'
Expand All @@ -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());
}
}
```
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions iron-encryption/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ android {

dependencies {
compile project(':iron')
compile 'commons-io:commons-io:2.4'
}

apply from: '../publish-encryption.gradle'
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion iron/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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().<List>read("persons2")).isNull();
assertThat(Iron.chest().read("persons2", new ArrayList<Person>())).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().<List>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().<List>read("persons2")).isNotEmpty();
}
Expand Down
94 changes: 49 additions & 45 deletions iron/src/main/java/io/fabianterhorst/iron/DbStoragePlainFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -219,28 +221,38 @@ private File getOriginalFile(String key) {
private <E> void writeTableFile(String key, IronTable<E> 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()) {
Expand All @@ -254,40 +266,32 @@ private <E> void writeTableFile(String key, IronTable<E> 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() : "";
}

private <E> 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<E> ironTable = kryo.readObject(decryptedInputStream, IronTable.class);
stream.close();
decryptedInputStream.close();
return ironTable.mContent;
} else {
//noinspection unchecked
final IronTable<E> 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<E> 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<E> ironTable = kryo.readObject(i, IronTable.class);
i.close();
return ironTable.mContent;
}
//noinspection unchecked
final IronTable<E> 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()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit c5bb209

Please sign in to comment.