mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 21:59:54 +01:00
Refactored bundle encryption code.
This commit is contained in:
@@ -1,41 +1,27 @@
|
|||||||
package net.sf.briar.android;
|
package net.sf.briar.android;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
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 java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sf.briar.api.android.BundleEncrypter;
|
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.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.ErasableKey;
|
|
||||||
import net.sf.briar.util.ByteUtils;
|
import net.sf.briar.util.ByteUtils;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
// This class is not thread-safe
|
|
||||||
class BundleEncrypterImpl implements BundleEncrypter {
|
class BundleEncrypterImpl implements BundleEncrypter {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BundleEncrypterImpl.class.getName());
|
Logger.getLogger(BundleEncrypterImpl.class.getName());
|
||||||
|
|
||||||
private final AuthenticatedCipher cipher;
|
private final CryptoComponent crypto;
|
||||||
private final SecureRandom random;
|
|
||||||
private final ErasableKey key;
|
|
||||||
private final int blockSize, macLength;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BundleEncrypterImpl(CryptoComponent crypto) {
|
BundleEncrypterImpl(CryptoComponent crypto) {
|
||||||
cipher = crypto.getBundleCipher();
|
this.crypto = crypto;
|
||||||
random = crypto.getSecureRandom();
|
|
||||||
key = crypto.generateSecretKey();
|
|
||||||
blockSize = cipher.getBlockSize();
|
|
||||||
macLength = cipher.getMacLength();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,41 +35,23 @@ class BundleEncrypterImpl implements BundleEncrypter {
|
|||||||
LOG.info("Marshalled " + b.size() + " mappings, "
|
LOG.info("Marshalled " + b.size() + " mappings, "
|
||||||
+ plaintext.length + " plaintext bytes");
|
+ plaintext.length + " plaintext bytes");
|
||||||
}
|
}
|
||||||
// Encrypt the byte array using a random IV
|
// Encrypt the plaintext
|
||||||
byte[] iv = new byte[blockSize];
|
byte[] ciphertext = crypto.encryptTemporaryStorage(plaintext);
|
||||||
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);
|
|
||||||
}
|
|
||||||
ByteUtils.erase(plaintext);
|
ByteUtils.erase(plaintext);
|
||||||
// Replace the plaintext contents with the IV and the ciphertext
|
// Replace the plaintext contents with the ciphertext
|
||||||
b.clear();
|
b.clear();
|
||||||
b.putByteArray("net.sf.briar.IV", iv);
|
|
||||||
b.putByteArray("net.sf.briar.CIPHERTEXT", ciphertext);
|
b.putByteArray("net.sf.briar.CIPHERTEXT", ciphertext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean decrypt(Bundle b) {
|
public boolean decrypt(Bundle b) {
|
||||||
// Retrieve the IV and the ciphertext
|
// Retrieve the ciphertext
|
||||||
byte[] iv = b.getByteArray("net.sf.briar.IV");
|
|
||||||
if(iv == null) throw new IllegalArgumentException();
|
|
||||||
if(iv.length != blockSize) throw new IllegalArgumentException();
|
|
||||||
byte[] ciphertext = b.getByteArray("net.sf.briar.CIPHERTEXT");
|
byte[] ciphertext = b.getByteArray("net.sf.briar.CIPHERTEXT");
|
||||||
if(ciphertext == null) throw new IllegalArgumentException();
|
if(ciphertext == null) throw new IllegalArgumentException();
|
||||||
if(ciphertext.length < macLength) throw new IllegalArgumentException();
|
// Decrypt the ciphertext
|
||||||
// Decrypt the ciphertext using the IV
|
byte[] plaintext = crypto.decryptTemporaryStorage(ciphertext);
|
||||||
byte[] plaintext = new byte[ciphertext.length - macLength];
|
if(plaintext == null) return false;
|
||||||
try {
|
// Unmarshall the plaintext
|
||||||
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
|
|
||||||
Parcel p = Parcel.obtain();
|
Parcel p = Parcel.obtain();
|
||||||
p.unmarshall(plaintext, 0, plaintext.length);
|
p.unmarshall(plaintext, 0, plaintext.length);
|
||||||
ByteUtils.erase(plaintext);
|
ByteUtils.erase(plaintext);
|
||||||
|
|||||||
@@ -77,7 +77,18 @@ public interface CryptoComponent {
|
|||||||
|
|
||||||
AuthenticatedCipher getFrameCipher();
|
AuthenticatedCipher getFrameCipher();
|
||||||
|
|
||||||
AuthenticatedCipher getBundleCipher();
|
|
||||||
|
|
||||||
Signature getSignature();
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package net.sf.briar.crypto;
|
package net.sf.briar.crypto;
|
||||||
|
|
||||||
|
import static javax.crypto.Cipher.DECRYPT_MODE;
|
||||||
import static javax.crypto.Cipher.ENCRYPT_MODE;
|
import static javax.crypto.Cipher.ENCRYPT_MODE;
|
||||||
import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS;
|
import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS;
|
||||||
import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
|
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.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
@@ -56,6 +58,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
private static final String SIGNATURE_ALGO = "ECDSA";
|
private static final String SIGNATURE_ALGO = "ECDSA";
|
||||||
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
|
private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
|
||||||
private static final int GCM_MAC_LENGTH = 16; // 128 bits
|
private static final int GCM_MAC_LENGTH = 16; // 128 bits
|
||||||
|
private static final int STORAGE_IV_LENGTH = 32; // 256 bits
|
||||||
|
|
||||||
// Labels for key derivation
|
// Labels for key derivation
|
||||||
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
|
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 agreementKeyPairGenerator;
|
||||||
private final KeyPairGenerator signatureKeyPairGenerator;
|
private final KeyPairGenerator signatureKeyPairGenerator;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
|
private final ErasableKey temporaryStorageKey;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CryptoComponentImpl() {
|
CryptoComponentImpl() {
|
||||||
@@ -139,6 +143,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
secureRandom = new SecureRandom();
|
secureRandom = new SecureRandom();
|
||||||
|
temporaryStorageKey = generateSecretKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
|
public ErasableKey deriveTagKey(byte[] secret, boolean alice) {
|
||||||
@@ -378,10 +383,56 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
|
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticatedCipher getBundleCipher() {
|
private AuthenticatedCipher getTemporaryStorageCipher() {
|
||||||
// This code is specific to BouncyCastle because javax.crypto.Cipher
|
// This code is specific to BouncyCastle because javax.crypto.Cipher
|
||||||
// doesn't support additional authenticated data until Java 7
|
// doesn't support additional authenticated data until Java 7
|
||||||
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
|
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
|
||||||
return new AuthenticatedCipherImpl(cipher, GCM_MAC_LENGTH);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user