Verify integrity of decrypted slots and display a dialog on error

This commit is contained in:
Alexander Bakker 2017-08-18 22:12:45 +02:00
parent 1ae9364c5e
commit 7269cc2b6a
6 changed files with 74 additions and 9 deletions

View file

@ -2,11 +2,13 @@ package me.impy.aegis;
import android.Manifest; import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager;
import android.os.Build; import android.os.Build;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
@ -14,14 +16,12 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.TextView; import android.widget.TextView;
import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.UndeclaredThrowableException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import me.impy.aegis.crypto.CryptoUtils; import me.impy.aegis.crypto.CryptoUtils;
@ -101,13 +101,26 @@ public class AuthActivity extends AppCompatActivity implements FingerprintUiHelp
throw new UndeclaredThrowableException(e); throw new UndeclaredThrowableException(e);
} }
// send the master key back to the main activity
setKey(masterKey); setKey(masterKey);
} }
}); });
} }
private void setKey(MasterKey key) { private void setKey(MasterKey key) {
if (!Arrays.equals(slots.getMasterHash(), key.getHash())) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Unable to decrypt the master key with the given key.");
builder.setCancelable(false);
builder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.create().show();
return;
}
// send the master key back to the main activity // send the master key back to the main activity
Intent result = new Intent(); Intent result = new Intent();
result.putExtra("key", key); result.putExtra("key", key);

View file

@ -109,6 +109,8 @@ public class IntroActivity extends AppIntro {
} }
SlotCollection slots = databaseFile.getSlots(); SlotCollection slots = databaseFile.getSlots();
slots.setMasterHash(masterKey.getHash());
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) { if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
try { try {
// 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

View file

@ -2,11 +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.security.spec.InvalidKeySpecException;
@ -24,10 +26,14 @@ 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 byte CRYPTO_HASH_SIZE = 32;
public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding"; public static final String CRYPTO_CIPHER_RAW = "AES/ECB/NoPadding";
public static final byte CRYPTO_KEY_SIZE = 32;
public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding"; public static final String CRYPTO_CIPHER_AEAD = "AES/GCM/NoPadding";
public static final byte CRYPTO_TAG_SIZE = 16; public static final byte CRYPTO_TAG_SIZE = 16;
public static final byte CRYPTO_KEY_SIZE = 32;
public static final byte CRYPTO_NONCE_SIZE = 12; public static final byte CRYPTO_NONCE_SIZE = 12;
public static final byte CRYPTO_SALT_SIZE = 32; public static final byte CRYPTO_SALT_SIZE = 32;
@ -86,6 +92,20 @@ public class CryptoUtils {
}}; }};
} }
public static byte[] hashKey(SecretKey key) {
MessageDigest hash;
try {
hash = MessageDigest.getInstance(CRYPTO_HASH);
} catch (NoSuchAlgorithmException e) {
throw new UndeclaredThrowableException(e);
}
byte[] bytes = key.getEncoded();
hash.update(bytes);
CryptoUtils.zero(bytes);
return hash.digest();
}
public static SecretKey generateKey() throws NoSuchAlgorithmException { public static SecretKey generateKey() throws NoSuchAlgorithmException {
KeyGenerator generator = KeyGenerator.getInstance("AES"); KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(CRYPTO_KEY_SIZE * 8); generator.init(CRYPTO_KEY_SIZE * 8);

View file

@ -51,4 +51,8 @@ public class MasterKey implements Serializable {
Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce); Cipher cipher = CryptoUtils.createCipher(_key, Cipher.DECRYPT_MODE, params.Nonce);
return CryptoUtils.decrypt(bytes, cipher, params); return CryptoUtils.decrypt(bytes, cipher, params);
} }
public byte[] getHash() {
return CryptoUtils.hashKey(_key);
}
} }

View file

@ -5,10 +5,14 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.crypto.SecretKey;
import me.impy.aegis.crypto.CryptoUtils;
import me.impy.aegis.util.LittleByteBuffer; import me.impy.aegis.util.LittleByteBuffer;
public class SlotCollection implements Iterable<Slot>, Serializable { public class SlotCollection implements Iterable<Slot>, Serializable {
private List<Slot> _slots = new ArrayList<>(); private List<Slot> _slots = new ArrayList<>();
private byte[] _masterHash;
public static byte[] serialize(SlotCollection slots) { public static byte[] serialize(SlotCollection slots) {
// yep, no streams at this api level // yep, no streams at this api level
@ -16,8 +20,10 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
for (Slot slot : slots) { for (Slot slot : slots) {
size += slot.getSize(); size += slot.getSize();
} }
size += CryptoUtils.CRYPTO_HASH_SIZE;
LittleByteBuffer buffer = LittleByteBuffer.allocate(size); LittleByteBuffer buffer = LittleByteBuffer.allocate(size);
buffer.put(slots.getMasterHash());
for (Slot slot : slots) { for (Slot slot : slots) {
byte[] bytes = slot.serialize(); byte[] bytes = slot.serialize();
@ -27,8 +33,12 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
} }
public static SlotCollection deserialize(byte[] data) throws Exception { public static SlotCollection deserialize(byte[] data) throws Exception {
SlotCollection slots = new SlotCollection();
LittleByteBuffer buffer = LittleByteBuffer.wrap(data); LittleByteBuffer buffer = LittleByteBuffer.wrap(data);
byte[] masterHash = new byte[CryptoUtils.CRYPTO_HASH_SIZE];
buffer.get(masterHash);
SlotCollection slots = new SlotCollection();
slots.setMasterHash(masterHash);
while (buffer.remaining() > 0) { while (buffer.remaining() > 0) {
Slot slot; Slot slot;
@ -86,4 +96,12 @@ public class SlotCollection implements Iterable<Slot>, Serializable {
public Iterator<Slot> iterator() { public Iterator<Slot> iterator() {
return _slots.iterator(); return _slots.iterator();
} }
public void setMasterHash(byte[] masterHash) {
_masterHash = masterHash;
}
public byte[] getMasterHash() {
return _masterHash;
}
} }

View file

@ -54,9 +54,17 @@ ID can be one of:
#### Slots #### Slots
This section contains a list of slots. All slots contain the master key This section contains a SHA-256 hash of the master key and a list of slots. The
encrypted with raw AES. The key that is used for encryption depends on the slot hash is used to verify the integrity of a decrypted slot. Note that this is just
type. for convenience, not security.
| Length | Contents |
|:-------|:--------------------------|
| `32` | `uint8_t` Master Key Hash |
| `?` | Slots |
All slots contain the master key encrypted with raw AES. The key that is used for
encryption depends on the slot type.
A slot has the following structure. A slot has the following structure.