Use GCM instead of a hash to check master key integrity

This is the last database format change before the initial release, probably
This commit is contained in:
Alexander Bakker 2018-06-06 19:38:13 +02:00
parent da37b5175e
commit c3f94b37c8
21 changed files with 211 additions and 214 deletions

View file

@ -1,6 +1,36 @@
package me.impy.aegis.crypto; package me.impy.aegis.crypto;
public class CryptParameters { import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException;
public class CryptParameters implements Serializable {
public byte[] Nonce; public byte[] Nonce;
public byte[] Tag; public byte[] Tag;
public JSONObject toJson() {
JSONObject obj = new JSONObject();
try {
obj.put("nonce", Hex.encode(Nonce));
obj.put("tag", Hex.encode(Tag));
} catch (JSONException e) {
throw new RuntimeException(e);
}
return obj;
}
public static CryptParameters parseJson(JSONObject obj) throws JSONException, HexException {
byte[] tag = Hex.decode(obj.getString("tag"));
byte[] nonce = Hex.decode(obj.getString("nonce"));
return new CryptParameters() {{
Tag = tag;
Nonce = nonce;
}};
}
} }

View file

@ -2,16 +2,13 @@ package me.impy.aegis.crypto;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays; import java.util.Arrays;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
@ -20,20 +17,16 @@ import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator; import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import org.spongycastle.crypto.generators.SCrypt; import org.spongycastle.crypto.generators.SCrypt;
public class CryptoUtils { public class CryptoUtils {
public static final String CRYPTO_HASH = "SHA-256"; public static final String CRYPTO_AEAD = "AES/GCM/NoPadding";
public static final byte CRYPTO_AEAD_KEY_SIZE = 32;
public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding"; public static final byte CRYPTO_AEAD_TAG_SIZE = 16;
public static final byte CRYPTO_KEY_SIZE = 32; public static final byte CRYPTO_AEAD_NONCE_SIZE = 12;
public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding";
public static final byte CRYPTO_TAG_SIZE = 16;
public static final byte CRYPTO_NONCE_SIZE = 12;
public static final int CRYPTO_SCRYPT_N = 1 << 15; public static final int CRYPTO_SCRYPT_N = 1 << 15;
public static final int CRYPTO_SCRYPT_r = 8; public static final int CRYPTO_SCRYPT_r = 8;
@ -41,23 +34,36 @@ public class CryptoUtils {
public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) { public static SecretKey deriveKey(char[] password, byte[] salt, int n, int r, int p) {
byte[] bytes = toBytes(password); byte[] bytes = toBytes(password);
byte[] keyBytes = SCrypt.generate(bytes, salt, n, r, p, CRYPTO_KEY_SIZE); byte[] keyBytes = SCrypt.generate(bytes, salt, n, r, p, CRYPTO_AEAD_KEY_SIZE);
return new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES"); return new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
} }
public static Cipher createCipher(SecretKey key, int opmode) public static Cipher createEncryptCipher(SecretKey key)
throws NoSuchPaddingException, NoSuchAlgorithmException, throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException { InvalidAlgorithmParameterException, InvalidKeyException {
byte[] nonce = generateNonce(); return createCipher(key, Cipher.ENCRYPT_MODE, null);
return createCipher(key, opmode, nonce);
} }
public static Cipher createCipher(SecretKey key, int opmode, byte[] nonce) public static Cipher createDecryptCipher(SecretKey key, byte[] nonce)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException,
InvalidKeyException, NoSuchPaddingException {
return createCipher(key, Cipher.DECRYPT_MODE, nonce);
}
private static Cipher createCipher(SecretKey key, int opmode, byte[] nonce)
throws NoSuchPaddingException, NoSuchAlgorithmException, throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException { InvalidAlgorithmParameterException, InvalidKeyException {
IvParameterSpec spec = new IvParameterSpec(nonce); Cipher cipher = Cipher.getInstance(CRYPTO_AEAD);
Cipher cipher = Cipher.getInstance(CRYPTO_CIPHER_AEAD);
cipher.init(opmode, key, spec); // generate the nonce if none is given
// we are not allowed to do this ourselves as "setRandomizedEncryptionRequired" is set to true
if (nonce != null) {
GCMParameterSpec spec = new GCMParameterSpec(CRYPTO_AEAD_TAG_SIZE * 8, nonce);
cipher.init(opmode, key, spec);
} else {
cipher.init(opmode, key);
}
return cipher; return cipher;
} }
@ -65,8 +71,8 @@ public class CryptoUtils {
throws BadPaddingException, IllegalBlockSizeException { throws BadPaddingException, IllegalBlockSizeException {
// split off the tag to store it separately // split off the tag to store it separately
byte[] result = cipher.doFinal(data); byte[] result = cipher.doFinal(data);
byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_TAG_SIZE, result.length); byte[] tag = Arrays.copyOfRange(result, result.length - CRYPTO_AEAD_TAG_SIZE, result.length);
byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_TAG_SIZE); byte[] encrypted = Arrays.copyOfRange(result, 0, result.length - CRYPTO_AEAD_TAG_SIZE);
return new CryptResult() {{ return new CryptResult() {{
Parameters = new CryptParameters() {{ Parameters = new CryptParameters() {{
@ -93,23 +99,10 @@ public class CryptoUtils {
}}; }};
} }
public static byte[] hashKey(SecretKey key) {
MessageDigest hash;
try {
hash = MessageDigest.getInstance(CRYPTO_HASH);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
byte[] bytes = key.getEncoded();
hash.update(bytes);
return hash.digest();
}
public static SecretKey generateKey() { public static SecretKey generateKey() {
try { try {
KeyGenerator generator = KeyGenerator.getInstance("AES"); KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(CRYPTO_KEY_SIZE * 8); generator.init(CRYPTO_AEAD_KEY_SIZE * 8);
return generator.generateKey(); return generator.generateKey();
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new AssertionError(e); throw new AssertionError(e);
@ -117,11 +110,7 @@ public class CryptoUtils {
} }
public static byte[] generateSalt() { public static byte[] generateSalt() {
return generateRandomBytes(CRYPTO_KEY_SIZE); return generateRandomBytes(CRYPTO_AEAD_KEY_SIZE);
}
public static byte[] generateNonce() {
return generateRandomBytes(CRYPTO_NONCE_SIZE);
} }
public static byte[] generateRandomBytes(int length) { public static byte[] generateRandomBytes(int length) {

View file

@ -1,6 +1,5 @@
package me.impy.aegis.crypto; package me.impy.aegis.crypto;
import android.annotation.SuppressLint;
import android.os.Build; import android.os.Build;
import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyPermanentlyInvalidatedException;
@ -51,11 +50,11 @@ public class KeyStoreHandle {
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, STORE_NAME); KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, STORE_NAME);
generator.init(new KeyGenParameterSpec.Builder(id, generator.init(new KeyGenParameterSpec.Builder(id,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB) .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true) .setUserAuthenticationRequired(true)
.setRandomizedEncryptionRequired(false) .setRandomizedEncryptionRequired(true)
.setKeySize(CryptoUtils.CRYPTO_KEY_SIZE * 8) .setKeySize(CryptoUtils.CRYPTO_AEAD_KEY_SIZE * 8)
.build()); .build());
return generator.generateKey(); return generator.generateKey();
@ -81,8 +80,7 @@ public class KeyStoreHandle {
// and see if KeyPermanentlyInvalidatedException is thrown // and see if KeyPermanentlyInvalidatedException is thrown
if (isSupported()) { if (isSupported()) {
try { try {
@SuppressLint("GetInstance") Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_AEAD);
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW);
cipher.init(Cipher.ENCRYPT_MODE, key); cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (KeyPermanentlyInvalidatedException e) { } catch (KeyPermanentlyInvalidatedException e) {
return null; return null;

View file

@ -28,7 +28,7 @@ public class MasterKey implements Serializable {
public CryptResult encrypt(byte[] bytes) throws MasterKeyException { public CryptResult encrypt(byte[] bytes) throws MasterKeyException {
try { try {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.ENCRYPT_MODE); Cipher cipher = CryptoUtils.createEncryptCipher(_key);
return CryptoUtils.encrypt(bytes, cipher); return CryptoUtils.encrypt(bytes, cipher);
} catch (NoSuchPaddingException } catch (NoSuchPaddingException
| NoSuchAlgorithmException | NoSuchAlgorithmException
@ -42,7 +42,7 @@ public class MasterKey implements Serializable {
public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException { public CryptResult decrypt(byte[] bytes, CryptParameters params) throws MasterKeyException {
try { try {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce); Cipher cipher = CryptoUtils.createDecryptCipher(_key, params.Nonce);
return CryptoUtils.decrypt(bytes, cipher, params); return CryptoUtils.decrypt(bytes, cipher, params);
} catch (NoSuchPaddingException } catch (NoSuchPaddingException
| NoSuchAlgorithmException | NoSuchAlgorithmException
@ -55,10 +55,6 @@ public class MasterKey implements Serializable {
} }
} }
public byte[] getHash() {
return CryptoUtils.hashKey(_key);
}
public byte[] getBytes() { public byte[] getBytes() {
return _key.getEncoded(); return _key.getEncoded();
} }

View file

@ -1,5 +1,6 @@
package me.impy.aegis.db; package me.impy.aegis.db;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -9,11 +10,10 @@ import me.impy.aegis.crypto.CryptParameters;
import me.impy.aegis.crypto.CryptResult; import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.crypto.MasterKeyException; import me.impy.aegis.crypto.MasterKeyException;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.slots.SlotCollectionException; import me.impy.aegis.db.slots.SlotListException;
import me.impy.aegis.encoding.Base64; import me.impy.aegis.encoding.Base64;
import me.impy.aegis.encoding.Base64Exception; import me.impy.aegis.encoding.Base64Exception;
import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException; import me.impy.aegis.encoding.HexException;
public class DatabaseFile { public class DatabaseFile {
@ -21,22 +21,15 @@ public class DatabaseFile {
private Object _content; private Object _content;
private CryptParameters _cryptParameters; private CryptParameters _cryptParameters;
private SlotCollection _slots; private SlotList _slots;
public byte[] serialize() { public byte[] serialize() {
try { try {
JSONObject cryptObj = null; // don't write the crypt parameters and slots if the content is not encrypted
if (isEncrypted()) { boolean plain = _content instanceof JSONObject || !isEncrypted();
cryptObj = new JSONObject();
cryptObj.put("nonce", Hex.encode(_cryptParameters.Nonce));
cryptObj.put("tag", Hex.encode(_cryptParameters.Tag));
}
// don't write the crypt parameters if the content is not encrypted
boolean plain = _content instanceof JSONObject || _slots == null || cryptObj == null;
JSONObject headerObj = new JSONObject(); JSONObject headerObj = new JSONObject();
headerObj.put("slots", plain ? JSONObject.NULL : SlotCollection.serialize(_slots)); headerObj.put("slots", plain ? JSONObject.NULL : SlotList.serialize(_slots));
headerObj.put("params", plain ? JSONObject.NULL : cryptObj); headerObj.put("params", plain ? JSONObject.NULL : _cryptParameters.toJson());
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("version", VERSION); obj.put("version", VERSION);
@ -58,17 +51,14 @@ public class DatabaseFile {
throw new DatabaseFileException("unsupported version"); throw new DatabaseFileException("unsupported version");
} }
JSONObject slotObj = headerObj.optJSONObject("slots"); JSONArray slotObj = headerObj.optJSONArray("slots");
if (slotObj != null) { if (slotObj != null) {
_slots = SlotCollection.deserialize(slotObj); _slots = SlotList.deserialize(slotObj);
} }
JSONObject cryptObj = headerObj.optJSONObject("params"); JSONObject cryptObj = headerObj.optJSONObject("params");
if (cryptObj != null) { if (cryptObj != null) {
_cryptParameters = new CryptParameters() {{ _cryptParameters = CryptParameters.parseJson(cryptObj);
Nonce = Hex.decode(cryptObj.getString("nonce"));
Tag = Hex.decode(cryptObj.getString("tag"));
}};
} }
if (cryptObj == null || slotObj == null) { if (cryptObj == null || slotObj == null) {
@ -76,7 +66,7 @@ public class DatabaseFile {
} else { } else {
_content = obj.getString("db"); _content = obj.getString("db");
} }
} catch (SlotCollectionException | UnsupportedEncodingException | JSONException | HexException e) { } catch (SlotListException | UnsupportedEncodingException | JSONException | HexException e) {
throw new DatabaseFileException(e); throw new DatabaseFileException(e);
} }
} }
@ -118,11 +108,11 @@ public class DatabaseFile {
} }
} }
public SlotCollection getSlots() { public SlotList getSlots() {
return _slots; return _slots;
} }
public void setSlots(SlotCollection slots) { public void setSlots(SlotList slots) {
_slots = slots; _slots = slots;
} }
} }

View file

@ -14,7 +14,7 @@ import java.util.List;
import me.impy.aegis.BuildConfig; import me.impy.aegis.BuildConfig;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotList;
public class DatabaseManager { public class DatabaseManager {
private static final String FILENAME = "aegis.json"; private static final String FILENAME = "aegis.json";
@ -196,7 +196,7 @@ public class DatabaseManager {
return _file; return _file;
} }
public void enableEncryption(MasterKey key, SlotCollection slots) { public void enableEncryption(MasterKey key, SlotList slots) {
assertState(false, true); assertState(false, true);
_key = key; _key = key;
_file.setSlots(slots); _file.setSlots(slots);

View file

@ -1,15 +1,16 @@
package me.impy.aegis.db.slots; package me.impy.aegis.db.slots;
import android.annotation.SuppressLint;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.UUID; import java.util.UUID;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
@ -17,6 +18,8 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import me.impy.aegis.crypto.CryptParameters;
import me.impy.aegis.crypto.CryptResult;
import me.impy.aegis.crypto.CryptoUtils; import me.impy.aegis.crypto.CryptoUtils;
import me.impy.aegis.crypto.MasterKey; import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.encoding.Hex; import me.impy.aegis.encoding.Hex;
@ -29,40 +32,55 @@ public abstract class Slot implements Serializable {
protected UUID _uuid; protected UUID _uuid;
protected byte[] _encryptedMasterKey; protected byte[] _encryptedMasterKey;
protected CryptParameters _encryptedMasterKeyParams;
protected Slot() { protected Slot() {
_uuid = UUID.randomUUID(); _uuid = UUID.randomUUID();
} }
// getKey decrypts the encrypted master key in this slot with the given key and returns it. // getKey decrypts the encrypted master key in this slot using the given cipher and returns it.
public SecretKey getKey(Cipher cipher) throws SlotException { public MasterKey getKey(Cipher cipher) throws SlotException, SlotIntegrityException {
try { try {
byte[] decryptedKeyBytes = cipher.doFinal(_encryptedMasterKey); CryptResult res = CryptoUtils.decrypt(_encryptedMasterKey, cipher, _encryptedMasterKeyParams);
return new SecretKeySpec(decryptedKeyBytes, CryptoUtils.CRYPTO_CIPHER_AEAD); SecretKey key = new SecretKeySpec(res.Data, CryptoUtils.CRYPTO_AEAD);
} catch (BadPaddingException | IllegalBlockSizeException e) { return new MasterKey(key);
} catch (AEADBadTagException e) {
throw new SlotIntegrityException(e);
} catch (IOException | BadPaddingException | IllegalBlockSizeException e) {
throw new SlotException(e); throw new SlotException(e);
} }
} }
// setKey encrypts the given master key with the given key and stores the result in this slot. // setKey encrypts the given master key using the given cipher and stores the result in this slot.
public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException { public void setKey(MasterKey masterKey, Cipher cipher) throws SlotException {
try { try {
byte[] masterKeyBytes = masterKey.getBytes(); byte[] masterKeyBytes = masterKey.getBytes();
_encryptedMasterKey = cipher.doFinal(masterKeyBytes); CryptResult res = CryptoUtils.encrypt(masterKeyBytes, cipher);
_encryptedMasterKey = res.Data;
_encryptedMasterKeyParams = res.Parameters;
} catch (BadPaddingException | IllegalBlockSizeException e) { } catch (BadPaddingException | IllegalBlockSizeException e) {
throw new SlotException(e); throw new SlotException(e);
} }
} }
// suppress the AES ECB warning public static Cipher createEncryptCipher(SecretKey key) throws SlotException {
// this is perfectly safe because we discard this cipher after passing CryptoUtils.CRYPTO_KEY_SIZE bytes through it
@SuppressLint("getInstance")
public static Cipher createCipher(SecretKey key, int mode) throws SlotException {
try { try {
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_CIPHER_RAW); return CryptoUtils.createEncryptCipher(key);
cipher.init(mode, key); } catch (InvalidAlgorithmParameterException
return cipher; | NoSuchPaddingException
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { | NoSuchAlgorithmException
| InvalidKeyException e) {
throw new SlotException(e);
}
}
public Cipher createDecryptCipher(SecretKey key) throws SlotException {
try {
return CryptoUtils.createDecryptCipher(key, _encryptedMasterKeyParams.Nonce);
} catch (InvalidAlgorithmParameterException
| NoSuchAlgorithmException
| InvalidKeyException
| NoSuchPaddingException e) {
throw new SlotException(e); throw new SlotException(e);
} }
} }
@ -70,9 +88,11 @@ public abstract class Slot implements Serializable {
public JSONObject serialize() { public JSONObject serialize() {
try { try {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
JSONObject paramObj = _encryptedMasterKeyParams.toJson();
obj.put("type", getType()); obj.put("type", getType());
obj.put("uuid", _uuid.toString()); obj.put("uuid", _uuid.toString());
obj.put("key", Hex.encode(_encryptedMasterKey)); obj.put("key", Hex.encode(_encryptedMasterKey));
obj.put("key_params", paramObj);
return obj; return obj;
} catch (JSONException e) { } catch (JSONException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -84,13 +104,17 @@ public abstract class Slot implements Serializable {
if (obj.getInt("type") != getType()) { if (obj.getInt("type") != getType()) {
throw new SlotException("slot type mismatch"); throw new SlotException("slot type mismatch");
} }
// if there is no uuid, generate a new one // if there is no uuid, generate a new one
if (!obj.has("uuid")) { if (!obj.has("uuid")) {
_uuid = UUID.randomUUID(); _uuid = UUID.randomUUID();
} else { } else {
_uuid = UUID.fromString(obj.getString("uuid")); _uuid = UUID.fromString(obj.getString("uuid"));
} }
JSONObject paramObj = obj.getJSONObject("key_params");
_encryptedMasterKey = Hex.decode(obj.getString("key")); _encryptedMasterKey = Hex.decode(obj.getString("key"));
_encryptedMasterKeyParams = CryptParameters.parseJson(paramObj);
} catch (JSONException | HexException e) { } catch (JSONException | HexException e) {
throw new SlotException(e); throw new SlotException(e);
} }

View file

@ -1,11 +0,0 @@
package me.impy.aegis.db.slots;
public class SlotCollectionException extends Exception {
public SlotCollectionException(Throwable cause) {
super(cause);
}
public SlotCollectionException(String message) {
super(message);
}
}

View file

@ -1,5 +1,11 @@
package me.impy.aegis.db.slots; package me.impy.aegis.db.slots;
public class SlotIntegrityException extends Exception { public class SlotIntegrityException extends Exception {
public SlotIntegrityException() {
}
public SlotIntegrityException(Throwable cause) {
super(cause);
}
} }

View file

@ -6,47 +6,28 @@ import org.json.JSONObject;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.crypto.Cipher; public class SlotList implements Iterable<Slot>, Serializable {
import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.encoding.Hex;
import me.impy.aegis.encoding.HexException;
public class SlotCollection implements Iterable<Slot>, Serializable {
private List<Slot> _slots = new ArrayList<>(); private List<Slot> _slots = new ArrayList<>();
private byte[] _masterHash;
public static JSONObject serialize(SlotCollection slots) { public static JSONArray serialize(SlotList slots) {
try { JSONArray array = new JSONArray();
JSONObject obj = new JSONObject(); for (Slot slot : slots) {
obj.put("hash", Hex.encode(slots.getMasterHash())); array.put(slot.serialize());
JSONArray entries = new JSONArray();
for (Slot slot : slots) {
entries.put(slot.serialize());
}
obj.put("entries", entries);
return obj;
} catch (JSONException e) {
throw new RuntimeException(e);
} }
return array;
} }
public static SlotCollection deserialize(JSONObject obj) throws SlotCollectionException { public static SlotList deserialize(JSONArray array) throws SlotListException {
SlotCollection slots = new SlotCollection(); SlotList slots = new SlotList();
try { try {
byte[] masterHash = Hex.decode(obj.getString("hash")); for (int i = 0; i < array.length(); i++) {
slots.setMasterHash(masterHash);
JSONArray entries = obj.getJSONArray("entries");
for (int i = 0; i < entries.length(); i++) {
Slot slot; Slot slot;
JSONObject slotObj = entries.getJSONObject(i); JSONObject slotObj = array.getJSONObject(i);
switch (slotObj.getInt("type")) { switch (slotObj.getInt("type")) {
case Slot.TYPE_RAW: case Slot.TYPE_RAW:
@ -65,8 +46,8 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
slot.deserialize(slotObj); slot.deserialize(slotObj);
slots.add(slot); slots.add(slot);
} }
} catch (SlotException | JSONException | HexException e) { } catch (SlotException | JSONException e) {
throw new SlotCollectionException(e); throw new SlotListException(e);
} }
return slots; return slots;
@ -116,26 +97,4 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
public Iterator<Slot> iterator() { public Iterator<Slot> iterator() {
return _slots.iterator(); return _slots.iterator();
} }
public void encrypt(Slot slot, MasterKey key, Cipher cipher) throws SlotException {
slot.setKey(key, cipher);
setMasterHash(key.getHash());
}
public MasterKey decrypt(Slot slot, Cipher cipher) throws SlotException, SlotIntegrityException {
byte[] hash = getMasterHash();
MasterKey key = new MasterKey(slot.getKey(cipher));
if (!Arrays.equals(hash, key.getHash())) {
throw new SlotIntegrityException();
}
return key;
}
private void setMasterHash(byte[] masterHash) {
_masterHash = masterHash;
}
private byte[] getMasterHash() {
return _masterHash;
}
} }

View file

@ -0,0 +1,11 @@
package me.impy.aegis.db.slots;
public class SlotListException extends Exception {
public SlotListException(Throwable cause) {
super(cause);
}
public SlotListException(String message) {
super(message);
}
}

View file

@ -26,7 +26,7 @@ import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.FingerprintHelper; import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.helpers.FingerprintUiHelper; import me.impy.aegis.helpers.FingerprintUiHelper;
@ -36,7 +36,7 @@ import me.impy.aegis.ui.tasks.SlotCollectionTask;
public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotCollectionTask.Callback { public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotCollectionTask.Callback {
private EditText _textPassword; private EditText _textPassword;
private SlotCollection _slots; private SlotList _slots;
private FingerprintUiHelper _fingerHelper; private FingerprintUiHelper _fingerHelper;
private Cipher _fingerCipher; private Cipher _fingerCipher;
@ -57,7 +57,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
} }
Intent intent = getIntent(); Intent intent = getIntent();
_slots = (SlotCollection) intent.getSerializableExtra("slots"); _slots = (SlotList) intent.getSerializableExtra("slots");
// only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found // only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found
FingerprintManager manager = FingerprintHelper.getManager(this); FingerprintManager manager = FingerprintHelper.getManager(this);
@ -75,7 +75,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
invalidated = true; invalidated = true;
continue; continue;
} }
_fingerCipher = Slot.createCipher(key, Cipher.DECRYPT_MODE); _fingerCipher = slot.createDecryptCipher(key);
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this); _fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
boxFingerprint.setVisibility(View.VISIBLE); boxFingerprint.setVisibility(View.VISIBLE);
invalidated = false; invalidated = false;

View file

@ -23,7 +23,7 @@ import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.Database; import me.impy.aegis.db.Database;
import me.impy.aegis.db.DatabaseFile; import me.impy.aegis.db.DatabaseFile;
import me.impy.aegis.db.DatabaseManager; import me.impy.aegis.db.DatabaseManager;
@ -145,7 +145,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
masterKey = MasterKey.generate(); masterKey = MasterKey.generate();
} }
SlotCollection slots = null; SlotList slots = null;
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) { if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
// encrypt the master key with a key derived from the user's password // encrypt the master key with a key derived from the user's password
// and add it to the list of slots // and add it to the list of slots
@ -153,8 +153,8 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
throw new RuntimeException(); throw new RuntimeException();
} }
try { try {
slots = new SlotCollection(); _passwordSlot.setKey(masterKey, _passwordCipher);
slots.encrypt(_passwordSlot, masterKey, _passwordCipher); slots = new SlotList();
slots.add(_passwordSlot); slots.add(_passwordSlot);
_databaseFile.setSlots(slots); _databaseFile.setSlots(slots);
} catch (SlotException e) { } catch (SlotException e) {
@ -168,7 +168,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
// and add it to the list of slots // and add it to the list of slots
FingerprintSlot slot = _authenticatedSlide.getFingerSlot(); FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
Cipher cipher = _authenticatedSlide.getFingerCipher(); Cipher cipher = _authenticatedSlide.getFingerCipher();
slots.encrypt(slot, masterKey, cipher); slot.setKey(masterKey, cipher);
slots.add(slot); slots.add(slot);
} catch (SlotException e) { } catch (SlotException e) {
setException(e); setException(e);
@ -204,7 +204,7 @@ public class IntroActivity extends AppIntro implements DerivationTask.Callback {
public void onTaskFinished(SecretKey key) { public void onTaskFinished(SecretKey key) {
if (key != null) { if (key != null) {
try { try {
_passwordCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); _passwordCipher = Slot.createEncryptCipher(key);
} catch (SlotException e) { } catch (SlotException e) {
setException(e); setException(e);
} }

View file

@ -32,7 +32,7 @@ import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.db.DatabaseManager; import me.impy.aegis.db.DatabaseManager;
import me.impy.aegis.db.DatabaseManagerException; import me.impy.aegis.db.DatabaseManagerException;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.PermissionHelper; import me.impy.aegis.helpers.PermissionHelper;
import me.impy.aegis.importers.AegisImporter; import me.impy.aegis.importers.AegisImporter;
@ -369,7 +369,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
return; return;
} }
SlotCollection slots = (SlotCollection) data.getSerializableExtra("slots"); SlotList slots = (SlotList) data.getSerializableExtra("slots");
_db.getFile().setSlots(slots); _db.getFile().setSlots(slots);
saveDatabase(); saveDatabase();
} }
@ -390,9 +390,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
public void onSlotResult(Slot slot, Cipher cipher) { public void onSlotResult(Slot slot, Cipher cipher) {
MasterKey masterKey = MasterKey.generate(); MasterKey masterKey = MasterKey.generate();
SlotCollection slots = new SlotCollection(); SlotList slots = new SlotList();
try { try {
slots.encrypt(slot, masterKey, cipher); slot.setKey(masterKey, cipher);
} catch (SlotException e) { } catch (SlotException e) {
onException(e); onException(e);
return; return;

View file

@ -20,7 +20,7 @@ import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.helpers.FingerprintHelper; import me.impy.aegis.helpers.FingerprintHelper;
import me.impy.aegis.ui.dialogs.Dialogs; import me.impy.aegis.ui.dialogs.Dialogs;
@ -31,7 +31,7 @@ import me.impy.aegis.ui.dialogs.SlotDialogFragment;
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener { public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, SlotDialogFragment.Listener {
private MasterKey _masterKey; private MasterKey _masterKey;
private SlotCollection _slots; private SlotList _slots;
private SlotAdapter _adapter; private SlotAdapter _adapter;
private boolean _edited = false; private boolean _edited = false;
@ -65,7 +65,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
// load the slots and masterKey // load the slots and masterKey
_masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey"); _masterKey = (MasterKey) getIntent().getSerializableExtra("masterKey");
_slots = (SlotCollection) getIntent().getSerializableExtra("slots"); _slots = (SlotList) getIntent().getSerializableExtra("slots");
for (Slot slot : _slots) { for (Slot slot : _slots) {
_adapter.addSlot(slot); _adapter.addSlot(slot);
} }
@ -176,7 +176,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
@Override @Override
public void onSlotResult(Slot slot, Cipher cipher) { public void onSlotResult(Slot slot, Cipher cipher) {
try { try {
_slots.encrypt(slot, _masterKey, cipher); slot.setKey(_masterKey, cipher);
} catch (SlotException e) { } catch (SlotException e) {
onException(e); onException(e);
return; return;

View file

@ -38,7 +38,7 @@ public class FingerprintDialogFragment extends SlotDialogFragment implements Fin
try { try {
_slot = new FingerprintSlot(); _slot = new FingerprintSlot();
SecretKey key = new KeyStoreHandle().generateKey(_slot.getUUID().toString()); SecretKey key = new KeyStoreHandle().generateKey(_slot.getUUID().toString());
_cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); _cipher = Slot.createEncryptCipher(key);
_helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this); _helper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
} catch (KeyStoreHandleException | SlotException e) { } catch (KeyStoreHandleException | SlotException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View file

@ -51,7 +51,7 @@ public class PasswordDialogFragment extends SlotDialogFragment {
DerivationTask task = new DerivationTask(getActivity(), key -> { DerivationTask task = new DerivationTask(getActivity(), key -> {
Cipher cipher; Cipher cipher;
try { try {
cipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); cipher = Slot.createEncryptCipher(key);
} catch (SlotException e) { } catch (SlotException e) {
getListener().onException(e); getListener().onException(e);
dialog.cancel(); dialog.cancel();

View file

@ -113,7 +113,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
} }
try { try {
_fingerCipher = Slot.createCipher(key, Cipher.ENCRYPT_MODE); _fingerCipher = Slot.createEncryptCipher(key);
} catch (Exception e) { } catch (Exception e) {
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }

View file

@ -2,8 +2,6 @@ package me.impy.aegis.ui.tasks;
import android.content.Context; import android.content.Context;
import java.lang.reflect.UndeclaredThrowableException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -11,7 +9,7 @@ import me.impy.aegis.crypto.MasterKey;
import me.impy.aegis.db.slots.FingerprintSlot; import me.impy.aegis.db.slots.FingerprintSlot;
import me.impy.aegis.db.slots.PasswordSlot; import me.impy.aegis.db.slots.PasswordSlot;
import me.impy.aegis.db.slots.Slot; import me.impy.aegis.db.slots.Slot;
import me.impy.aegis.db.slots.SlotCollection; import me.impy.aegis.db.slots.SlotList;
import me.impy.aegis.db.slots.SlotException; import me.impy.aegis.db.slots.SlotException;
import me.impy.aegis.db.slots.SlotIntegrityException; import me.impy.aegis.db.slots.SlotIntegrityException;
@ -41,15 +39,17 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
if (slot instanceof PasswordSlot) { if (slot instanceof PasswordSlot) {
char[] password = (char[])params.Obj; char[] password = (char[])params.Obj;
SecretKey key = ((PasswordSlot)slot).deriveKey(password); SecretKey key = ((PasswordSlot)slot).deriveKey(password);
Cipher cipher = Slot.createCipher(key, Cipher.DECRYPT_MODE); Cipher cipher = slot.createDecryptCipher(key);
masterKey = params.Slots.decrypt(slot, cipher); masterKey = slot.getKey(cipher);
} else if (slot instanceof FingerprintSlot) { } else if (slot instanceof FingerprintSlot) {
masterKey = params.Slots.decrypt(slot, (Cipher)params.Obj); masterKey = slot.getKey((Cipher)params.Obj);
} else { } else {
throw new RuntimeException(); throw new RuntimeException();
} }
break; break;
} catch (SlotIntegrityException e) { } } catch (SlotIntegrityException e) {
}
} }
if (masterKey == null) { if (masterKey == null) {
@ -60,7 +60,7 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
} catch (SlotIntegrityException e) { } catch (SlotIntegrityException e) {
return null; return null;
} catch (SlotException e) { } catch (SlotException e) {
throw new UndeclaredThrowableException(e); throw new RuntimeException(e);
} }
} }
@ -71,7 +71,7 @@ public class SlotCollectionTask<T extends Slot> extends ProgressDialogTask<SlotC
} }
public static class Params { public static class Params {
public SlotCollection Slots; public SlotList Slots;
public Object Obj; public Object Obj;
} }

File diff suppressed because one or more lines are too long

View file

@ -52,7 +52,7 @@
"secret": "6CAIGVYB5MQ6TSZLJ56HJBWU5S3H7FUC", "secret": "6CAIGVYB5MQ6TSZLJ56HJBWU5S3H7FUC",
"algo": "SHA512", "algo": "SHA512",
"digits": 6, "digits": 6,
"counter": 30 "counter": 96
} }
}, },
{ {
@ -100,7 +100,7 @@
"secret": "EIQMT7NHFYJUMBKQ35P34JGLG3MO7L2W", "secret": "EIQMT7NHFYJUMBKQ35P34JGLG3MO7L2W",
"algo": "SHA1", "algo": "SHA1",
"digits": 8, "digits": 8,
"counter": 20 "counter": 30
} }
}, },
{ {