Skip to content
Snippets Groups Projects
Commit 9215537d authored by akwizgran's avatar akwizgran
Browse files

Refactored bundle encryption code.

parent f33348ff
No related branches found
No related tags found
No related merge requests found
package net.sf.briar.android;
import static java.util.logging.Level.INFO;
import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.logging.Logger;
import net.sf.briar.api.android.BundleEncrypter;
import net.sf.briar.api.crypto.AuthenticatedCipher;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.ErasableKey;
import net.sf.briar.util.ByteUtils;
import android.os.Bundle;
import android.os.Parcel;
import com.google.inject.Inject;
// This class is not thread-safe
class BundleEncrypterImpl implements BundleEncrypter {
private static final Logger LOG =
Logger.getLogger(BundleEncrypterImpl.class.getName());
private final AuthenticatedCipher cipher;
private final SecureRandom random;
private final ErasableKey key;
private final int blockSize, macLength;
private final CryptoComponent crypto;
@Inject
BundleEncrypterImpl(CryptoComponent crypto) {
cipher = crypto.getBundleCipher();
random = crypto.getSecureRandom();
key = crypto.generateSecretKey();
blockSize = cipher.getBlockSize();
macLength = cipher.getMacLength();
this.crypto = crypto;
}
@Override
......@@ -49,41 +35,23 @@ class BundleEncrypterImpl implements BundleEncrypter {
LOG.info("Marshalled " + b.size() + " mappings, "
+ plaintext.length + " plaintext bytes");
}
// Encrypt the byte array using a random IV
byte[] iv = new byte[blockSize];
random.nextBytes(iv);
byte[] ciphertext = new byte[plaintext.length + macLength];
try {
cipher.init(ENCRYPT_MODE, key, iv, null);
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext, 0);
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}
// Encrypt the plaintext
byte[] ciphertext = crypto.encryptTemporaryStorage(plaintext);
ByteUtils.erase(plaintext);
// Replace the plaintext contents with the IV and the ciphertext
// Replace the plaintext contents with the ciphertext
b.clear();
b.putByteArray("net.sf.briar.IV", iv);
b.putByteArray("net.sf.briar.CIPHERTEXT", ciphertext);
}
@Override
public boolean decrypt(Bundle b) {
// Retrieve the IV and the ciphertext
byte[] iv = b.getByteArray("net.sf.briar.IV");
if(iv == null) throw new IllegalArgumentException();
if(iv.length != blockSize) throw new IllegalArgumentException();
// Retrieve the ciphertext
byte[] ciphertext = b.getByteArray("net.sf.briar.CIPHERTEXT");
if(ciphertext == null) throw new IllegalArgumentException();
if(ciphertext.length < macLength) throw new IllegalArgumentException();
// Decrypt the ciphertext using the IV
byte[] plaintext = new byte[ciphertext.length - macLength];
try {
cipher.init(DECRYPT_MODE, key, iv, null);
cipher.doFinal(ciphertext, 0, ciphertext.length, plaintext, 0);
} catch(GeneralSecurityException e) {
return false; // Invalid ciphertext
}
// Unmarshall the byte array
// Decrypt the ciphertext
byte[] plaintext = crypto.decryptTemporaryStorage(ciphertext);
if(plaintext == null) return false;
// Unmarshall the plaintext
Parcel p = Parcel.obtain();
p.unmarshall(plaintext, 0, plaintext.length);
ByteUtils.erase(plaintext);
......
......@@ -77,7 +77,18 @@ public interface CryptoComponent {
AuthenticatedCipher getFrameCipher();
AuthenticatedCipher getBundleCipher();
Signature getSignature();
/**
* Encrypts the given plaintext so it can be written to temporary storage.
* The ciphertext will not be decryptable after the app restarts.
*/
byte[] encryptTemporaryStorage(byte[] plaintext);
/**
* Decrypts the given ciphertext that has been read from temporary storage.
* Returns null if the ciphertext is not decryptable (for example, if it
* was written before the app restarted).
*/
byte[] decryptTemporaryStorage(byte[] ciphertext);
}
package net.sf.briar.crypto;
import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS;
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
......@@ -7,6 +8,7 @@ import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
......@@ -56,6 +58,7 @@ class CryptoComponentImpl implements CryptoComponent {
private static final String SIGNATURE_ALGO = "ECDSA";
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
private static final int GCM_MAC_LENGTH = 16; // 128 bits
private static final int STORAGE_IV_LENGTH = 32; // 256 bits
// Labels for key derivation
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
......@@ -116,6 +119,7 @@ class CryptoComponentImpl implements CryptoComponent {
private final KeyPairGenerator agreementKeyPairGenerator;
private final KeyPairGenerator signatureKeyPairGenerator;
private final SecureRandom secureRandom;
private final ErasableKey temporaryStorageKey;
@Inject
CryptoComponentImpl() {
......@@ -139,6 +143,7 @@ class CryptoComponentImpl implements CryptoComponent {
throw new RuntimeException(e);
}
secureRandom = new SecureRandom();
temporaryStorageKey = generateSecretKey();
}
public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
......@@ -378,10 +383,56 @@ class CryptoComponentImpl implements CryptoComponent {
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
}
public AuthenticatedCipher getBundleCipher() {
private AuthenticatedCipher getTemporaryStorageCipher() {
// This code is specific to BouncyCastle because javax.crypto.Cipher
// doesn't support additional authenticated data until Java 7
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
}
public byte[] encryptTemporaryStorage(byte[] plaintext) {
AuthenticatedCipher cipher = getTemporaryStorageCipher();
// Generate a random IV
byte[] iv = new byte[STORAGE_IV_LENGTH];
secureRandom.nextBytes(iv);
// The output contains the IV, ciphertext and MAC
int ciphertextLength = iv.length + plaintext.length + GCM_MAC_LENGTH;
byte[] ciphertext = new byte[ciphertextLength];
System.arraycopy(iv, 0, ciphertext, 0, iv.length);
// Initialise the cipher and encrypt the plaintext
try {
cipher.init(ENCRYPT_MODE, temporaryStorageKey, iv, null);
cipher.doFinal(plaintext, 0, plaintext.length, ciphertext,
iv.length);
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
}
return ciphertext;
}
public byte[] decryptTemporaryStorage(byte[] ciphertext) {
// The input contains the IV, ciphertext and MAC
if(ciphertext.length < STORAGE_IV_LENGTH + GCM_MAC_LENGTH)
throw new IllegalArgumentException();
AuthenticatedCipher cipher = getTemporaryStorageCipher();
// Copy the IV
byte[] iv = new byte[STORAGE_IV_LENGTH];
System.arraycopy(ciphertext, 0, iv, 0, iv.length);
// Initialise the cipher
try {
cipher.init(DECRYPT_MODE, temporaryStorageKey, iv, null);
} catch(InvalidKeyException e) {
throw new RuntimeException(e);
}
// Try to decrypt the ciphertext (may be invalid)
int plaintextLength = ciphertext.length - iv.length - GCM_MAC_LENGTH;
byte[] plaintext = new byte[plaintextLength];
try {
cipher.doFinal(ciphertext, iv.length, ciphertext.length - iv.length,
plaintext, 0);
} catch(GeneralSecurityException e) {
return null; // Invalid ciphertext
}
return plaintext;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment