From 6a15c03e81ff3b1cdded825c284d112cb360af32 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Tue, 15 Nov 2011 16:07:14 +0000
Subject: [PATCH] Store the incoming and outgoing secrets separately.

---
 .../sf/briar/api/crypto/CryptoComponent.java  | 16 +--
 .../sf/briar/api/db/DatabaseComponent.java    |  7 +-
 .../transport/ConnectionReaderFactory.java    |  5 +-
 .../transport/ConnectionWriterFactory.java    |  5 +-
 .../sf/briar/crypto/CryptoComponentImpl.java  | 68 ++++---------
 .../net/sf/briar/crypto/SharedSecret.java     | 58 -----------
 components/net/sf/briar/db/Database.java      |  8 +-
 .../sf/briar/db/DatabaseComponentImpl.java    | 10 +-
 components/net/sf/briar/db/JdbcDatabase.java  | 18 ++--
 .../ConnectionReaderFactoryImpl.java          | 10 +-
 .../transport/ConnectionRecogniserImpl.java   | 13 ++-
 .../ConnectionWriterFactoryImpl.java          | 10 +-
 .../batch/IncomingBatchConnection.java        |  2 +-
 .../batch/OutgoingBatchConnection.java        |  2 +-
 .../stream/IncomingStreamConnection.java      |  4 +-
 .../stream/OutgoingStreamConnection.java      |  4 +-
 test/build.xml                                |  2 -
 .../net/sf/briar/ProtocolIntegrationTest.java | 19 ++--
 .../sf/briar/crypto/CryptoComponentTest.java  | 49 ----------
 .../net/sf/briar/crypto/SharedSecretTest.java | 39 --------
 .../sf/briar/db/DatabaseComponentTest.java    | 30 ++++--
 test/net/sf/briar/db/H2DatabaseTest.java      | 97 ++++++++++---------
 .../ConnectionRecogniserImplTest.java         | 22 +++--
 .../briar/transport/ConnectionWriterTest.java |  7 +-
 .../briar/transport/FrameReadWriteTest.java   | 14 +--
 .../batch/BatchConnectionReadWriteTest.java   | 15 +--
 26 files changed, 199 insertions(+), 335 deletions(-)
 delete mode 100644 components/net/sf/briar/crypto/SharedSecret.java
 delete mode 100644 test/net/sf/briar/crypto/CryptoComponentTest.java
 delete mode 100644 test/net/sf/briar/crypto/SharedSecretTest.java

diff --git a/api/net/sf/briar/api/crypto/CryptoComponent.java b/api/net/sf/briar/api/crypto/CryptoComponent.java
index a932231989..2b01765b7d 100644
--- a/api/net/sf/briar/api/crypto/CryptoComponent.java
+++ b/api/net/sf/briar/api/crypto/CryptoComponent.java
@@ -9,20 +9,16 @@ import javax.crypto.Mac;
 
 public interface CryptoComponent {
 
-	ErasableKey deriveIncomingFrameKey(byte[] secret);
+	ErasableKey deriveFrameKey(byte[] source, boolean initiator);
 
-	ErasableKey deriveIncomingIvKey(byte[] secret);
+	ErasableKey deriveIvKey(byte[] source, boolean initiator);
 
-	ErasableKey deriveIncomingMacKey(byte[] secret);
-
-	ErasableKey deriveOutgoingFrameKey(byte[] secret);
-
-	ErasableKey deriveOutgoingIvKey(byte[] secret);
-
-	ErasableKey deriveOutgoingMacKey(byte[] secret);
+	ErasableKey deriveMacKey(byte[] source, boolean initiator);
 
 	KeyPair generateKeyPair();
 
+	ErasableKey generateTestKey();
+
 	Cipher getFrameCipher();
 
 	Cipher getIvCipher();
@@ -36,6 +32,4 @@ public interface CryptoComponent {
 	SecureRandom getSecureRandom();
 
 	Signature getSignature();
-
-	ErasableKey generateTestKey();
 }
diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index 857e6c1e5e..1717f00ca9 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -53,10 +53,11 @@ public interface DatabaseComponent {
 	void removeListener(DatabaseListener d);
 
 	/**
-	 * Adds a new contact to the database with the given secret and returns an
+	 * Adds a new contact to the database with the given secrets and returns an
 	 * ID for the contact.
 	 */
-	ContactId addContact(byte[] secret) throws DbException;
+	ContactId addContact(byte[] incomingSecret, byte[] outgoingSecret)
+	throws DbException;
 
 	/** Adds a locally generated group message to the database. */
 	void addLocalGroupMessage(Message m) throws DbException;
@@ -158,7 +159,7 @@ public interface DatabaseComponent {
 	throws DbException;
 
 	/** Returns the secret shared with the given contact. */
-	byte[] getSharedSecret(ContactId c) throws DbException;
+	byte[] getSharedSecret(ContactId c, boolean incoming) throws DbException;
 
 	/** Returns the set of groups to which the user subscribes. */
 	Collection<Group> getSubscriptions() throws DbException;
diff --git a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java
index 5d7c9142c7..db9dead7b0 100644
--- a/api/net/sf/briar/api/transport/ConnectionReaderFactory.java
+++ b/api/net/sf/briar/api/transport/ConnectionReaderFactory.java
@@ -8,14 +8,15 @@ public interface ConnectionReaderFactory {
 
 	/**
 	 * Creates a connection reader for a batch-mode connection or the
-	 * initiator's side of a stream-mode connection.
+	 * initiator's side of a stream-mode connection. The secret is erased before
+	 * returning.
 	 */
 	ConnectionReader createConnectionReader(InputStream in, TransportIndex i,
 			byte[] encryptedIv, byte[] secret);
 
 	/**
 	 * Creates a connection reader for the responder's side of a stream-mode
-	 * connection.
+	 * connection. The secret is erased before returning.
 	 */
 	ConnectionReader createConnectionReader(InputStream in, TransportIndex i,
 			long connection, byte[] secret);
diff --git a/api/net/sf/briar/api/transport/ConnectionWriterFactory.java b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java
index 63a13a3619..8b05d3e2a5 100644
--- a/api/net/sf/briar/api/transport/ConnectionWriterFactory.java
+++ b/api/net/sf/briar/api/transport/ConnectionWriterFactory.java
@@ -8,14 +8,15 @@ public interface ConnectionWriterFactory {
 
 	/**
 	 * Creates a connection writer for a batch-mode connection or the
-	 * initiator's side of a stream-mode connection.
+	 * initiator's side of a stream-mode connection. The secret is erased before
+	 * returning.
 	 */
 	ConnectionWriter createConnectionWriter(OutputStream out, long capacity,
 			TransportIndex i, long connection, byte[] secret);
 
 	/**
 	 * Creates a connection writer for the responder's side of a stream-mode
-	 * connection.
+	 * connection. The secret is erased before returning.
 	 */
 	ConnectionWriter createConnectionWriter(OutputStream out, long capacity,
 			TransportIndex i, byte[] encryptedIv, byte[] secret);
diff --git a/components/net/sf/briar/crypto/CryptoComponentImpl.java b/components/net/sf/briar/crypto/CryptoComponentImpl.java
index cd64e05655..670245c2d4 100644
--- a/components/net/sf/briar/crypto/CryptoComponentImpl.java
+++ b/components/net/sf/briar/crypto/CryptoComponentImpl.java
@@ -53,17 +53,22 @@ class CryptoComponentImpl implements CryptoComponent {
 		}
 	}
 
-	public ErasableKey deriveIncomingFrameKey(byte[] secret) {
-		SharedSecret s = new SharedSecret(secret);
-		return deriveFrameKey(s, !s.getAlice());
+	public ErasableKey deriveFrameKey(byte[] source, boolean initiator) {
+		if(initiator) return deriveKey("FRAME_I", source);
+		else return deriveKey("FRAME_R", source);
 	}
 
-	private ErasableKey deriveFrameKey(SharedSecret s, boolean alice) {
-		if(alice) return deriveKey("F_A", s.getSecret());
-		else return deriveKey("F_B", s.getSecret());
+	public ErasableKey deriveIvKey(byte[] source, boolean initiator) {
+		if(initiator) return deriveKey("IV_I", source);
+		else return deriveKey("IV_R", source);
 	}
 
-	private ErasableKey deriveKey(String name, byte[] secret) {
+	public ErasableKey deriveMacKey(byte[] source, boolean initiator) {
+		if(initiator) return deriveKey("MAC_I", source);
+		else return deriveKey("MAC_R", source);
+	}
+
+	private ErasableKey deriveKey(String name, byte[] source) {
 		MessageDigest digest = getMessageDigest();
 		assert digest.getDigestLength() == SECRET_KEY_BYTES;
 		try {
@@ -71,49 +76,20 @@ class CryptoComponentImpl implements CryptoComponent {
 		} catch(UnsupportedEncodingException e) {
 			throw new RuntimeException(e);
 		}
-		digest.update(secret);
+		digest.update(source);
 		return new ErasableKeyImpl(digest.digest(), SECRET_KEY_ALGO);
 	}
 
-	public ErasableKey deriveIncomingIvKey(byte[] secret) {
-		SharedSecret s = new SharedSecret(secret);
-		return deriveIvKey(s, !s.getAlice());
-	}
-
-	private ErasableKey deriveIvKey(SharedSecret s, boolean alice) {
-		if(alice) return deriveKey("I_A", s.getSecret());
-		else return deriveKey("I_B", s.getSecret());
-	}
-
-	public ErasableKey deriveIncomingMacKey(byte[] secret) {
-		SharedSecret s = new SharedSecret(secret);
-		return deriveMacKey(s, !s.getAlice());
-	}
-
-	private ErasableKey deriveMacKey(SharedSecret s, boolean alice) {
-		if(alice) return deriveKey("M_A", s.getSecret());
-		else return deriveKey("M_B", s.getSecret());
-	}
-
-	public ErasableKey deriveOutgoingFrameKey(byte[] secret) {
-		SharedSecret s = new SharedSecret(secret);
-		return deriveFrameKey(s, s.getAlice());
-	}
-
-	public ErasableKey deriveOutgoingIvKey(byte[] secret) {
-		SharedSecret s = new SharedSecret(secret);
-		return deriveIvKey(s, s.getAlice());
-	}
-
-	public ErasableKey deriveOutgoingMacKey(byte[] secret) {
-		SharedSecret s = new SharedSecret(secret);
-		return deriveMacKey(s, s.getAlice());
-	}
-
 	public KeyPair generateKeyPair() {
 		return keyPairGenerator.generateKeyPair();
 	}
 
+	public ErasableKey generateTestKey() {
+		byte[] b = new byte[SECRET_KEY_BYTES];
+		getSecureRandom().nextBytes(b);
+		return new ErasableKeyImpl(b, SECRET_KEY_ALGO);
+	}
+
 	public Cipher getFrameCipher() {
 		try {
 			return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
@@ -177,10 +153,4 @@ class CryptoComponentImpl implements CryptoComponent {
 			throw new RuntimeException(e);
 		}
 	}
-
-	public ErasableKey generateTestKey() {
-		byte[] b = new byte[SECRET_KEY_BYTES];
-		getSecureRandom().nextBytes(b);
-		return new ErasableKeyImpl(b, SECRET_KEY_ALGO);
-	}
 }
diff --git a/components/net/sf/briar/crypto/SharedSecret.java b/components/net/sf/briar/crypto/SharedSecret.java
deleted file mode 100644
index 0f2a984b9a..0000000000
--- a/components/net/sf/briar/crypto/SharedSecret.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package net.sf.briar.crypto;
-
-/**
- * A shared secret from which authentication and encryption keys can be derived.
- * The secret carries a flag indicating whether Alice's keys or Bob's keys
- * should be derived from the secret. When two parties agree on a shared secret,
- * they must decide which of them will derive Alice's keys and which Bob's.
- */
-class SharedSecret {
-
-	private final boolean alice;
-	private final byte[] secret;
-
-	SharedSecret(byte[] b) {
-		if(b.length < 2) throw new IllegalArgumentException();
-		switch(b[0]) {
-		case 0:
-			alice = false;
-			break;
-		case 1:
-			alice = true;
-			break;
-		default:
-			throw new IllegalArgumentException();
-		}
-		secret = new byte[b.length - 1];
-		System.arraycopy(b, 1, secret, 0, secret.length);
-	}
-
-	SharedSecret(boolean alice, byte[] secret) {
-		this.alice = alice;
-		this.secret = secret;
-	}
-
-	/**
-	 * Returns true if we should play the role of Alice in connections using
-	 * this secret, or false if we should play the role of Bob.
-	 */
-	boolean getAlice() {
-		return alice;
-	}
-
-	/** Returns the shared secret. */
-	byte[] getSecret() {
-		return secret;
-	}
-
-	/**
-	 * Returns a raw representation of this object, suitable for storing in the
-	 * database.
-	 */
-	byte[] getBytes() {
-		byte[] b = new byte[1 + secret.length];
-		if(alice) b[0] = (byte) 1;
-		System.arraycopy(secret, 0, b, 1, secret.length);
-		return b;
-	}
-}
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index 60a7b3a80d..a3281c7973 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -80,12 +80,13 @@ interface Database<T> {
 	void addBatchToAck(T txn, ContactId c, BatchId b) throws DbException;
 
 	/**
-	 * Adds a new contact to the database with the given secret and returns an
+	 * Adds a new contact to the database with the given secrets and returns an
 	 * ID for the contact.
 	 * <p>
 	 * Locking: contact write.
 	 */
-	ContactId addContact(T txn, byte[] secret) throws DbException;
+	ContactId addContact(T txn, byte[] incomingSecret, byte[] outgoingSecret)
+	throws DbException;
 
 	/**
 	 * Returns false if the given message is already in the database. Otherwise
@@ -376,7 +377,8 @@ interface Database<T> {
 	 * <p>
 	 * Locking: contact read.
 	 */
-	byte[] getSharedSecret(T txn, ContactId c) throws DbException;
+	byte[] getSharedSecret(T txn, ContactId c, boolean incoming)
+	throws DbException;
 
 	/**
 	 * Returns true if the given message has been starred.
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index fe2172b965..a671f95c1c 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -135,14 +135,15 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public ContactId addContact(byte[] secret) throws DbException {
+	public ContactId addContact(byte[] incomingSecret, byte[] outgoingSecret)
+	throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
 		ContactId c;
 		contactLock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
-				c = db.addContact(txn, secret);
+				c = db.addContact(txn, incomingSecret, outgoingSecret);
 				db.commitTransaction(txn);
 				if(LOG.isLoggable(Level.FINE)) LOG.fine("Added contact " + c);
 			} catch(DbException e) {
@@ -905,13 +906,14 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public byte[] getSharedSecret(ContactId c) throws DbException {
+	public byte[] getSharedSecret(ContactId c, boolean incoming)
+	throws DbException {
 		contactLock.readLock().lock();
 		try {
 			if(!containsContact(c)) throw new NoSuchContactException();
 			T txn = db.startTransaction();
 			try {
-				byte[] secret = db.getSharedSecret(txn, c);
+				byte[] secret = db.getSharedSecret(txn, c, incoming);
 				db.commitTransaction(txn);
 				return secret;
 			} catch(DbException e) {
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index 458d39edc2..0398d75e6a 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -56,7 +56,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String CREATE_CONTACTS =
 		"CREATE TABLE contacts"
 		+ " (contactId COUNTER,"
-		+ " secret BINARY NOT NULL,"
+		+ " incomingSecret BINARY NOT NULL,"
+		+ " outgoingSecret BINARY NOT NULL,"
 		+ " PRIMARY KEY (contactId))";
 
 	private static final String CREATE_MESSAGES =
@@ -509,15 +510,17 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public ContactId addContact(Connection txn, byte[] secret)
-	throws DbException {
+	public ContactId addContact(Connection txn, byte[] incomingSecret,
+			byte[] outgoingSecret) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
 			// Create a new contact row
-			String sql = "INSERT INTO contacts (secret) VALUES (?)";
+			String sql = "INSERT INTO contacts (incomingSecret, outgoingSecret)"
+				+ " VALUES (?, ?)";
 			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, secret);
+			ps.setBytes(1, incomingSecret);
+			ps.setBytes(2, outgoingSecret);
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -1643,12 +1646,13 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public byte[] getSharedSecret(Connection txn, ContactId c)
+	public byte[] getSharedSecret(Connection txn, ContactId c, boolean incoming)
 	throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT secret FROM contacts WHERE contactId = ?";
+			String col = incoming ? "incomingSecret" : "outgoingSecret";
+			String sql = "SELECT " + col + " FROM contacts WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			rs = ps.executeQuery();
diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
index b48a564bad..6469e8b4e2 100644
--- a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
@@ -29,7 +29,7 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 			TransportIndex i, byte[] encryptedIv, byte[] secret) {
 		// Decrypt the IV
 		Cipher ivCipher = crypto.getIvCipher();
-		ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
+		ErasableKey ivKey = crypto.deriveIvKey(secret, true);
 		byte[] iv;
 		try {
 			ivCipher.init(Cipher.DECRYPT_MODE, ivKey);
@@ -57,15 +57,17 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 	private ConnectionReader createConnectionReader(InputStream in,
 			boolean initiator, TransportIndex i, long connection,
 			byte[] secret) {
-		byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
+		// Derive the keys and erase the secret
+		ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
+		ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
+		for(int j = 0; j < secret.length; j++) secret[j] = 0;
 		// Create the decrypter
+		byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
 		Cipher frameCipher = crypto.getFrameCipher();
-		ErasableKey frameKey = crypto.deriveIncomingFrameKey(secret);
 		ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, iv,
 				frameCipher, frameKey);
 		// Create the reader
 		Mac mac = crypto.getMac();
-		ErasableKey macKey = crypto.deriveIncomingMacKey(secret);
 		return new ConnectionReaderImpl(decrypter, mac, macKey);
 	}
 }
diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
index 34bd3a2e15..6c139c6346 100644
--- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
+++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
@@ -75,7 +75,9 @@ DatabaseListener {
 	}
 
 	private synchronized void calculateIvs(ContactId c) throws DbException {
-		ErasableKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
+		byte[] secret = db.getSharedSecret(c, true);
+		ErasableKey ivKey = crypto.deriveIvKey(secret, true);
+		for(int i = 0; i < secret.length; i++) secret[i] = 0;
 		for(TransportId t : localTransportIds) {
 			TransportIndex i = db.getRemoteIndex(c, t);
 			if(i != null) {
@@ -131,7 +133,9 @@ DatabaseListener {
 				TransportIndex i1 = ctx1.getTransportIndex();
 				if(c1.equals(c) && i1.equals(i)) it.remove();
 			}
-			ErasableKey ivKey = crypto.deriveIncomingIvKey(db.getSharedSecret(c));
+			byte[] secret = db.getSharedSecret(c, true);
+			ErasableKey ivKey = crypto.deriveIvKey(secret, true);
+			for(int j = 0; j < secret.length; j++) secret[j] = 0;
 			calculateIvs(c, ctx.getTransportId(), i, ivKey, w);
 		} catch(NoSuchContactException e) {
 			// The contact was removed - clean up when we get the event
@@ -181,8 +185,9 @@ DatabaseListener {
 	private synchronized void calculateIvs(TransportId t) throws DbException {
 		for(ContactId c : db.getContacts()) {
 			try {
-				byte[] secret = db.getSharedSecret(c);
-				ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
+				byte[] secret = db.getSharedSecret(c, true);
+				ErasableKey ivKey = crypto.deriveIvKey(secret, true);
+				for(int i = 0; i < secret.length; i++) secret[i] = 0;
 				TransportIndex i = db.getRemoteIndex(c, t);
 				if(i != null) {
 					ConnectionWindow w = db.getConnectionWindow(c, i);
diff --git a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
index 822f6223b4..caab149260 100644
--- a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
@@ -36,7 +36,7 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
 			byte[] secret) {
 		// Decrypt the IV
 		Cipher ivCipher = crypto.getIvCipher();
-		ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
+		ErasableKey ivKey = crypto.deriveIvKey(secret, true);
 		byte[] iv;
 		try {
 			ivCipher.init(Cipher.DECRYPT_MODE, ivKey);
@@ -60,17 +60,19 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
 	private ConnectionWriter createConnectionWriter(OutputStream out,
 			long capacity, boolean initiator, TransportIndex i, long connection,
 			byte[] secret) {
+		// Derive the keys and erase the secret
+		ErasableKey ivKey = crypto.deriveIvKey(secret, initiator);
+		ErasableKey frameKey = crypto.deriveFrameKey(secret, initiator);
+		ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
+		for(int j = 0; j < secret.length; j++) secret[j] = 0;
 		// Create the encrypter
 		Cipher ivCipher = crypto.getIvCipher();
 		Cipher frameCipher = crypto.getFrameCipher();
-		ErasableKey ivKey = crypto.deriveOutgoingIvKey(secret);
-		ErasableKey frameKey = crypto.deriveOutgoingFrameKey(secret);
 		byte[] iv = IvEncoder.encodeIv(initiator, i, connection);
 		ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
 				capacity, iv, ivCipher, frameCipher, ivKey, frameKey);
 		// Create the writer
 		Mac mac = crypto.getMac();
-		ErasableKey macKey = crypto.deriveOutgoingMacKey(secret);
 		return new ConnectionWriterImpl(encrypter, mac, macKey);
 	}
 }
diff --git a/components/net/sf/briar/transport/batch/IncomingBatchConnection.java b/components/net/sf/briar/transport/batch/IncomingBatchConnection.java
index b1bfd1966c..a97c392c97 100644
--- a/components/net/sf/briar/transport/batch/IncomingBatchConnection.java
+++ b/components/net/sf/briar/transport/batch/IncomingBatchConnection.java
@@ -47,7 +47,7 @@ class IncomingBatchConnection {
 
 	void read() {
 		try {
-			byte[] secret = db.getSharedSecret(contactId);
+			byte[] secret = db.getSharedSecret(contactId, true);
 			ConnectionReader conn = connFactory.createConnectionReader(
 					reader.getInputStream(), transportIndex, encryptedIv,
 					secret);
diff --git a/components/net/sf/briar/transport/batch/OutgoingBatchConnection.java b/components/net/sf/briar/transport/batch/OutgoingBatchConnection.java
index 810a4e7899..b9d8630b35 100644
--- a/components/net/sf/briar/transport/batch/OutgoingBatchConnection.java
+++ b/components/net/sf/briar/transport/batch/OutgoingBatchConnection.java
@@ -46,7 +46,7 @@ class OutgoingBatchConnection {
 
 	void write() {
 		try {
-			byte[] secret = db.getSharedSecret(contactId);
+			byte[] secret = db.getSharedSecret(contactId, false);
 			long connection = db.getConnectionNumber(contactId, transportIndex);
 			ConnectionWriter conn = connFactory.createConnectionWriter(
 					writer.getOutputStream(), writer.getCapacity(),
diff --git a/components/net/sf/briar/transport/stream/IncomingStreamConnection.java b/components/net/sf/briar/transport/stream/IncomingStreamConnection.java
index 3518b75c92..bc01c5ad61 100644
--- a/components/net/sf/briar/transport/stream/IncomingStreamConnection.java
+++ b/components/net/sf/briar/transport/stream/IncomingStreamConnection.java
@@ -33,7 +33,7 @@ public class IncomingStreamConnection extends StreamConnection {
 	@Override
 	protected ConnectionReader createConnectionReader() throws DbException,
 	IOException {
-		byte[] secret = db.getSharedSecret(contactId);
+		byte[] secret = db.getSharedSecret(contactId, true);
 		return connReaderFactory.createConnectionReader(
 				connection.getInputStream(), transportIndex, encryptedIv,
 				secret);
@@ -42,7 +42,7 @@ public class IncomingStreamConnection extends StreamConnection {
 	@Override
 	protected ConnectionWriter createConnectionWriter() throws DbException,
 	IOException {
-		byte[] secret = db.getSharedSecret(contactId);
+		byte[] secret = db.getSharedSecret(contactId, false);
 		return connWriterFactory.createConnectionWriter(
 				connection.getOutputStream(), Long.MAX_VALUE, transportIndex,
 				encryptedIv, secret);
diff --git a/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java b/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java
index 923fcf354f..4af22e161e 100644
--- a/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java
+++ b/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java
@@ -37,7 +37,7 @@ public class OutgoingStreamConnection extends StreamConnection {
 						transportIndex);
 			}
 		}
-		byte[] secret = db.getSharedSecret(contactId);
+		byte[] secret = db.getSharedSecret(contactId, true);
 		return connReaderFactory.createConnectionReader(
 				connection.getInputStream(), transportIndex, connectionNum,
 				secret);
@@ -52,7 +52,7 @@ public class OutgoingStreamConnection extends StreamConnection {
 						transportIndex);
 			}
 		}
-		byte[] secret = db.getSharedSecret(contactId);
+		byte[] secret = db.getSharedSecret(contactId, false);
 		return connWriterFactory.createConnectionWriter(
 				connection.getOutputStream(), Long.MAX_VALUE, transportIndex,
 				connectionNum, secret);
diff --git a/test/build.xml b/test/build.xml
index b0d0118ed9..9dc9b7a109 100644
--- a/test/build.xml
+++ b/test/build.xml
@@ -17,8 +17,6 @@
 			<test name='net.sf.briar.LockFairnessTest'/>
 			<test name='net.sf.briar.ProtocolIntegrationTest'/>
 			<test name='net.sf.briar.crypto.CounterModeTest'/>
-			<test name='net.sf.briar.crypto.CryptoComponentTest'/>
-			<test name='net.sf.briar.crypto.SharedSecretTest'/>
 			<test name='net.sf.briar.db.BasicH2Test'/>
 			<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
 			<test name='net.sf.briar.db.DatabaseComponentImplTest'/>
diff --git a/test/net/sf/briar/ProtocolIntegrationTest.java b/test/net/sf/briar/ProtocolIntegrationTest.java
index f7afcc9a62..907fc63d8f 100644
--- a/test/net/sf/briar/ProtocolIntegrationTest.java
+++ b/test/net/sf/briar/ProtocolIntegrationTest.java
@@ -13,6 +13,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Random;
 
 import junit.framework.TestCase;
 import net.sf.briar.api.crypto.CryptoComponent;
@@ -56,6 +57,7 @@ import net.sf.briar.transport.TransportModule;
 import net.sf.briar.transport.batch.TransportBatchModule;
 import net.sf.briar.transport.stream.TransportStreamModule;
 
+import org.bouncycastle.util.Arrays;
 import org.junit.Test;
 
 import com.google.inject.Guice;
@@ -71,7 +73,7 @@ public class ProtocolIntegrationTest extends TestCase {
 	private final ProtocolReaderFactory protocolReaderFactory;
 	private final ProtocolWriterFactory protocolWriterFactory;
 	private final CryptoComponent crypto;
-	private final byte[] aliceSecret, bobSecret;
+	private final byte[] aliceToBobSecret;
 	private final TransportIndex transportIndex = new TransportIndex(13);
 	private final long connection = 12345L;
 	private final Author author;
@@ -96,10 +98,9 @@ public class ProtocolIntegrationTest extends TestCase {
 		crypto = i.getInstance(CryptoComponent.class);
 		assertEquals(crypto.getMessageDigest().getDigestLength(),
 				UniqueId.LENGTH);
-		// Create matching secrets: one for Alice, one for Bob
-		aliceSecret = new byte[123];
-		aliceSecret[0] = (byte) 1;
-		bobSecret = new byte[123];
+		Random r = new Random();
+		aliceToBobSecret = new byte[123];
+		r.nextBytes(aliceToBobSecret);
 		// Create two groups: one restricted, one unrestricted
 		GroupFactory groupFactory = i.getInstance(GroupFactory.class);
 		group = groupFactory.createGroup("Unrestricted group", null);
@@ -138,9 +139,9 @@ public class ProtocolIntegrationTest extends TestCase {
 
 	private byte[] write() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		// Use Alice's secret for writing
+		byte[] copyOfSecret = Arrays.clone(aliceToBobSecret);
 		ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
-				Long.MAX_VALUE, transportIndex, connection, aliceSecret);
+				Long.MAX_VALUE, transportIndex, connection, copyOfSecret);
 		OutputStream out1 = w.getOutputStream();
 
 		AckWriter a = protocolWriterFactory.createAckWriter(out1);
@@ -193,9 +194,9 @@ public class ProtocolIntegrationTest extends TestCase {
 			offset += read;
 		}
 		assertEquals(16, offset);
-		// Use Bob's secret for reading
+		byte[] copyOfSecret = Arrays.clone(aliceToBobSecret);
 		ConnectionReader r = connectionReaderFactory.createConnectionReader(in,
-				transportIndex, encryptedIv, bobSecret);
+				transportIndex, encryptedIv, copyOfSecret);
 		in = r.getInputStream();
 		ProtocolReader protocolReader =
 			protocolReaderFactory.createProtocolReader(in);
diff --git a/test/net/sf/briar/crypto/CryptoComponentTest.java b/test/net/sf/briar/crypto/CryptoComponentTest.java
deleted file mode 100644
index fcc74391e0..0000000000
--- a/test/net/sf/briar/crypto/CryptoComponentTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package net.sf.briar.crypto;
-
-import junit.framework.TestCase;
-import net.sf.briar.api.crypto.CryptoComponent;
-
-import org.junit.Test;
-
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-
-public class CryptoComponentTest extends TestCase {
-
-	private final CryptoComponent crypto;
-
-	public CryptoComponentTest() {
-		super();
-		Injector i = Guice.createInjector(new CryptoModule());
-		crypto = i.getInstance(CryptoComponent.class);
-	}
-
-	@Test
-	public void testKeyDerivation() {
-		// Create matching secrets: one for Alice, one for Bob
-		byte[] aliceSecret = new byte[123];
-		aliceSecret[0] = (byte) 1;
-		byte[] bobSecret = new byte[123];
-		// Check that Alice's incoming keys match Bob's outgoing keys
-		assertEquals(crypto.deriveIncomingMacKey(aliceSecret),
-				crypto.deriveOutgoingMacKey(bobSecret));
-		assertEquals(crypto.deriveIncomingFrameKey(aliceSecret),
-				crypto.deriveOutgoingFrameKey(bobSecret));
-		assertEquals(crypto.deriveIncomingIvKey(aliceSecret),
-				crypto.deriveOutgoingIvKey(bobSecret));
-		// Check that Alice's outgoing keys match Bob's incoming keys
-		assertEquals(crypto.deriveOutgoingMacKey(aliceSecret),
-				crypto.deriveIncomingMacKey(bobSecret));
-		assertEquals(crypto.deriveOutgoingFrameKey(aliceSecret),
-				crypto.deriveIncomingFrameKey(bobSecret));
-		assertEquals(crypto.deriveOutgoingIvKey(aliceSecret),
-				crypto.deriveIncomingIvKey(bobSecret));
-		// Check that Alice's incoming and outgoing keys are different
-		assertFalse(crypto.deriveIncomingMacKey(aliceSecret).equals(
-				crypto.deriveOutgoingMacKey(aliceSecret)));
-		assertFalse(crypto.deriveIncomingFrameKey(aliceSecret).equals(
-				crypto.deriveOutgoingFrameKey(aliceSecret)));
-		assertFalse(crypto.deriveIncomingIvKey(aliceSecret).equals(
-				crypto.deriveOutgoingIvKey(aliceSecret)));
-	}
-}
diff --git a/test/net/sf/briar/crypto/SharedSecretTest.java b/test/net/sf/briar/crypto/SharedSecretTest.java
deleted file mode 100644
index aa23d79aee..0000000000
--- a/test/net/sf/briar/crypto/SharedSecretTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package net.sf.briar.crypto;
-
-import static org.junit.Assert.assertArrayEquals;
-
-import java.util.Random;
-
-import junit.framework.TestCase;
-
-import org.junit.Test;
-
-public class SharedSecretTest extends TestCase {
-
-	@Test
-	public void testDecodeAndEncode() {
-		Random random = new Random();
-		byte[] secret = new byte[40];
-		random.nextBytes(secret);
-		secret[0] = (byte) 0;
-		SharedSecret s = new SharedSecret(secret);
-		assertArrayEquals(secret, s.getBytes());
-		secret[0] = (byte) 1;
-		s = new SharedSecret(secret);
-		assertArrayEquals(secret, s.getBytes());
-		// The Alice flag must be either 0 or 1
-		secret[0] = (byte) 2;
-		try {
-			s = new SharedSecret(secret);
-			fail();
-		} catch(IllegalArgumentException expected) {}
-		// The secret must be at least 1 byte long
-		secret = new byte[1];
-		random.nextBytes(secret);
-		secret[0] = (byte) 0;
-		try {
-			s = new SharedSecret(secret);
-			fail();
-		} catch(IllegalArgumentException expected) {}
-	}
-}
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 335d11d711..763bbf4bb7 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -5,6 +5,7 @@ import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.Random;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
@@ -46,6 +47,7 @@ import net.sf.briar.api.transport.ConnectionWindow;
 
 import org.jmock.Expectations;
 import org.jmock.Mockery;
+import static org.junit.Assert.assertArrayEquals;
 import org.junit.Test;
 
 public abstract class DatabaseComponentTest extends TestCase {
@@ -66,7 +68,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 	private final TransportIndex localIndex, remoteIndex;
 	private final Collection<Transport> transports;
 	private final Map<ContactId, TransportProperties> remoteProperties;
-	private final byte[] secret;
+	private final byte[] inSecret, outSecret;
 
 	public DatabaseComponentTest() {
 		super();
@@ -94,7 +96,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 		Transport transport = new Transport(transportId, localIndex,
 				properties);
 		transports = Collections.singletonList(transport);
-		secret = new byte[123];
+		Random r = new Random();
+		inSecret = new byte[123];
+		r.nextBytes(inSecret);
+		outSecret = new byte[123];
+		r.nextBytes(outSecret);
 	}
 
 	protected abstract <T> DatabaseComponent createDatabaseComponent(
@@ -132,7 +138,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).setRating(txn, authorId, Rating.GOOD);
 			will(returnValue(Rating.GOOD));
 			// addContact()
-			oneOf(database).addContact(txn, secret);
+			oneOf(database).addContact(txn, inSecret, outSecret);
 			will(returnValue(contactId));
 			oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class)));
 			// getContacts()
@@ -143,11 +149,16 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(true));
 			oneOf(database).getConnectionWindow(txn, contactId, remoteIndex);
 			will(returnValue(connectionWindow));
-			// getSharedSecret(contactId)
+			// getSharedSecret(contactId, true)
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
-			oneOf(database).getSharedSecret(txn, contactId);
-			will(returnValue(secret));
+			oneOf(database).getSharedSecret(txn, contactId, true);
+			will(returnValue(inSecret));
+			// getSharedSecret(contactId, false)
+			oneOf(database).containsContact(txn, contactId);
+			will(returnValue(true));
+			oneOf(database).getSharedSecret(txn, contactId, false);
+			will(returnValue(outSecret));
 			// getTransportProperties(transportId)
 			oneOf(database).getRemoteProperties(txn, transportId);
 			will(returnValue(remoteProperties));
@@ -198,11 +209,12 @@ public abstract class DatabaseComponentTest extends TestCase {
 		assertEquals(Rating.UNRATED, db.getRating(authorId));
 		db.setRating(authorId, Rating.GOOD); // First time - listeners called
 		db.setRating(authorId, Rating.GOOD); // Second time - not called
-		assertEquals(contactId, db.addContact(secret));
+		assertEquals(contactId, db.addContact(inSecret, outSecret));
 		assertEquals(Collections.singletonList(contactId), db.getContacts());
 		assertEquals(connectionWindow,
 				db.getConnectionWindow(contactId, remoteIndex));
-		assertEquals(secret, db.getSharedSecret(contactId));
+		assertArrayEquals(inSecret, db.getSharedSecret(contactId, true));
+		assertArrayEquals(outSecret, db.getSharedSecret(contactId, false));
 		assertEquals(remoteProperties, db.getRemoteProperties(transportId));
 		db.subscribe(group); // First time - listeners called
 		db.subscribe(group); // Second time - not called
@@ -564,7 +576,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		} catch(NoSuchContactException expected) {}
 
 		try {
-			db.getSharedSecret(contactId);
+			db.getSharedSecret(contactId, true);
 			fail();
 		} catch(NoSuchContactException expected) {}
 
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 9d9471f21b..ac3fee99c4 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -85,7 +85,7 @@ public class H2DatabaseTest extends TestCase {
 	private final Map<ContactId, TransportProperties> remoteProperties;
 	private final Collection<Transport> remoteTransports;
 	private final Map<Group, Long> subscriptions;
-	private final byte[] secret;
+	private final byte[] inSecret, outSecret;
 
 	public H2DatabaseTest() throws Exception {
 		super();
@@ -122,7 +122,11 @@ public class H2DatabaseTest extends TestCase {
 				properties);
 		remoteTransports = Collections.singletonList(remoteTransport);
 		subscriptions = Collections.singletonMap(group, 0L);
-		secret = new byte[123];
+		Random r = new Random();
+		inSecret = new byte[123];
+		r.nextBytes(inSecret);
+		outSecret = new byte[123];
+		r.nextBytes(outSecret);
 	}
 
 	@Before
@@ -136,7 +140,8 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId,
+				db.addContact(txn, inSecret, outSecret));
 		assertTrue(db.containsContact(txn, contactId));
 		assertFalse(db.containsSubscription(txn, groupId));
 		db.addSubscription(txn, group);
@@ -192,20 +197,20 @@ public class H2DatabaseTest extends TestCase {
 
 		// Create three contacts
 		assertFalse(db.containsContact(txn, contactId));
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		assertTrue(db.containsContact(txn, contactId));
 		assertFalse(db.containsContact(txn, contactId1));
-		assertEquals(contactId1, db.addContact(txn, secret));
+		assertEquals(contactId1, db.addContact(txn, inSecret, outSecret));
 		assertTrue(db.containsContact(txn, contactId1));
 		assertFalse(db.containsContact(txn, contactId2));
-		assertEquals(contactId2, db.addContact(txn, secret));
+		assertEquals(contactId2, db.addContact(txn, inSecret, outSecret));
 		assertTrue(db.containsContact(txn, contactId2));
 		// Delete the contact with the highest ID
 		db.removeContact(txn, contactId2);
 		assertFalse(db.containsContact(txn, contactId2));
 		// Add another contact - a new ID should be created
 		assertFalse(db.containsContact(txn, contactId3));
-		assertEquals(contactId3, db.addContact(txn, secret));
+		assertEquals(contactId3, db.addContact(txn, inSecret, outSecret));
 		assertTrue(db.containsContact(txn, contactId3));
 
 		db.commitTransaction(txn);
@@ -252,7 +257,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and store a private message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addPrivateMessage(txn, privateMessage, contactId);
 
 		// Removing the contact should remove the message
@@ -271,7 +276,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and store a private message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addPrivateMessage(txn, privateMessage, contactId);
 
 		// The message has no status yet, so it should not be sendable
@@ -310,7 +315,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and store a private message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addPrivateMessage(txn, privateMessage, contactId);
 		db.setStatus(txn, contactId, privateMessageId, Status.NEW);
 
@@ -338,7 +343,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -376,7 +381,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -418,7 +423,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.addGroupMessage(txn, message);
@@ -457,7 +462,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.addGroupMessage(txn, message);
@@ -492,7 +497,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -523,7 +528,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
 		db.addGroupMessage(txn, message);
@@ -556,7 +561,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and some batches to ack
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addBatchToAck(txn, contactId, batchId);
 		db.addBatchToAck(txn, contactId, batchId1);
 
@@ -583,7 +588,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and receive the same batch twice
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addBatchToAck(txn, contactId, batchId);
 		db.addBatchToAck(txn, contactId, batchId);
 
@@ -609,7 +614,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message);
 
@@ -634,8 +639,8 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add two contacts, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
-		ContactId contactId1 = db.addContact(txn, secret);
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
+		ContactId contactId1 = db.addContact(txn, inSecret, outSecret);
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message);
 
@@ -657,7 +662,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -696,7 +701,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -741,7 +746,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 
 		// Add some outstanding batches, a few ms apart
 		for(int i = 0; i < ids.length; i++) {
@@ -781,7 +786,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 
 		// Add some outstanding batches, a few ms apart
 		for(int i = 0; i < ids.length; i++) {
@@ -1001,7 +1006,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact with a transport
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.setTransports(txn, contactId, remoteTransports, 1);
 		assertEquals(remoteProperties,
 				db.getRemoteProperties(txn, transportId));
@@ -1094,7 +1099,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact with a transport
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.setTransports(txn, contactId, remoteTransports, 1);
 		assertEquals(remoteProperties,
 				db.getRemoteProperties(txn, transportId));
@@ -1138,7 +1143,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact with some subscriptions
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
 		assertEquals(Collections.singletonList(group),
 				db.getSubscriptions(txn, contactId));
@@ -1163,7 +1168,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact with some subscriptions
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.setSubscriptions(txn, contactId, subscriptions, 2);
 		assertEquals(Collections.singletonList(group),
 				db.getSubscriptions(txn, contactId));
@@ -1187,7 +1192,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and subscribe to a group
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
 
@@ -1205,7 +1210,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
 		db.addGroupMessage(txn, message);
@@ -1228,7 +1233,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
 		db.addGroupMessage(txn, message);
@@ -1251,7 +1256,7 @@ public class H2DatabaseTest extends TestCase {
 
 		// Add a contact, subscribe to a group and store a message -
 		// the message is older than the contact's subscription
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		Map<Group, Long> subs = Collections.singletonMap(group, timestamp + 1);
@@ -1275,7 +1280,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -1300,7 +1305,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and subscribe to a group
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -1319,7 +1324,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact with a subscription
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
 
 		// There's no local subscription for the group
@@ -1336,7 +1341,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message);
 		db.setStatus(txn, contactId, messageId, Status.NEW);
@@ -1355,7 +1360,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message);
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -1375,7 +1380,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -1397,7 +1402,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact, subscribe to a group and store a message
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		db.setVisibility(txn, groupId, Collections.singletonList(contactId));
 		db.setSubscriptions(txn, contactId, subscriptions, 1);
@@ -1418,7 +1423,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and subscribe to a group
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 		// The group should not be visible to the contact
 		assertEquals(Collections.emptyList(), db.getVisibility(txn, groupId));
@@ -1441,7 +1446,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		// Get the connection window for a new index
 		ConnectionWindow w = db.getConnectionWindow(txn, contactId,
 				remoteIndex);
@@ -1460,7 +1465,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		// Get the connection window for a new index
 		ConnectionWindow w = db.getConnectionWindow(txn, contactId,
 				remoteIndex);
@@ -1564,7 +1569,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and subscribe to a group
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 
 		// A message with a private parent should return null
@@ -1613,7 +1618,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 
 		// The subscription and transport timestamps should be initialised to 0
 		assertEquals(0L, db.getSubscriptionsModified(txn, contactId));
@@ -1644,7 +1649,7 @@ public class H2DatabaseTest extends TestCase {
 		Connection txn = db.startTransaction();
 
 		// Add a contact and subscribe to a group
-		assertEquals(contactId, db.addContact(txn, secret));
+		assertEquals(contactId, db.addContact(txn, inSecret, outSecret));
 		db.addSubscription(txn, group);
 
 		// Store a couple of messages
diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
index 056e478c92..31eab8d9bd 100644
--- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
@@ -4,14 +4,15 @@ import static net.sf.briar.api.transport.TransportConstants.IV_LENGTH;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Random;
 
 import javax.crypto.Cipher;
-import net.sf.briar.api.crypto.ErasableKey;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.crypto.CryptoComponent;
+import net.sf.briar.api.crypto.ErasableKey;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.protocol.Transport;
 import net.sf.briar.api.protocol.TransportId;
@@ -31,7 +32,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
 
 	private final CryptoComponent crypto;
 	private final ContactId contactId;
-	private final byte[] secret;
+	private final byte[] inSecret;
 	private final TransportId transportId;
 	private final TransportIndex localIndex, remoteIndex;
 	private final Collection<Transport> transports;
@@ -42,7 +43,8 @@ public class ConnectionRecogniserImplTest extends TestCase {
 		Injector i = Guice.createInjector(new CryptoModule());
 		crypto = i.getInstance(CryptoComponent.class);
 		contactId = new ContactId(1);
-		secret = new byte[18];
+		inSecret = new byte[123];
+		new Random().nextBytes(inSecret);
 		transportId = new TransportId(TestUtils.getRandomId());
 		localIndex = new TransportIndex(13);
 		remoteIndex = new TransportIndex(7);
@@ -63,8 +65,8 @@ public class ConnectionRecogniserImplTest extends TestCase {
 			will(returnValue(transports));
 			oneOf(db).getContacts();
 			will(returnValue(Collections.singletonList(contactId)));
-			oneOf(db).getSharedSecret(contactId);
-			will(returnValue(secret));
+			oneOf(db).getSharedSecret(contactId, true);
+			will(returnValue(inSecret));
 			oneOf(db).getRemoteIndex(contactId, transportId);
 			will(returnValue(remoteIndex));
 			oneOf(db).getConnectionWindow(contactId, remoteIndex);
@@ -79,7 +81,7 @@ public class ConnectionRecogniserImplTest extends TestCase {
 	@Test
 	public void testExpectedIv() throws Exception {
 		// Calculate the expected IV for connection number 3
-		ErasableKey ivKey = crypto.deriveIncomingIvKey(secret);
+		ErasableKey ivKey = crypto.deriveIvKey(inSecret, true);
 		Cipher ivCipher = crypto.getIvCipher();
 		ivCipher.init(Cipher.ENCRYPT_MODE, ivKey);
 		byte[] iv = IvEncoder.encodeIv(true, remoteIndex, 3L);
@@ -94,8 +96,8 @@ public class ConnectionRecogniserImplTest extends TestCase {
 			will(returnValue(transports));
 			oneOf(db).getContacts();
 			will(returnValue(Collections.singletonList(contactId)));
-			oneOf(db).getSharedSecret(contactId);
-			will(returnValue(secret));
+			oneOf(db).getSharedSecret(contactId, true);
+			will(returnValue(inSecret));
 			oneOf(db).getRemoteIndex(contactId, transportId);
 			will(returnValue(remoteIndex));
 			oneOf(db).getConnectionWindow(contactId, remoteIndex);
@@ -105,8 +107,8 @@ public class ConnectionRecogniserImplTest extends TestCase {
 			will(returnValue(connectionWindow));
 			oneOf(db).setConnectionWindow(contactId, remoteIndex,
 					connectionWindow);
-			oneOf(db).getSharedSecret(contactId);
-			will(returnValue(secret));
+			oneOf(db).getSharedSecret(contactId, true);
+			will(returnValue(inSecret));
 		}});
 		final ConnectionRecogniserImpl c =
 			new ConnectionRecogniserImpl(crypto, db);
diff --git a/test/net/sf/briar/transport/ConnectionWriterTest.java b/test/net/sf/briar/transport/ConnectionWriterTest.java
index e623187017..f5ffa882f5 100644
--- a/test/net/sf/briar/transport/ConnectionWriterTest.java
+++ b/test/net/sf/briar/transport/ConnectionWriterTest.java
@@ -4,6 +4,7 @@ import static net.sf.briar.api.protocol.ProtocolConstants.MAX_PACKET_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.MIN_CONNECTION_LENGTH;
 
 import java.io.ByteArrayOutputStream;
+import java.util.Random;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestDatabaseModule;
@@ -26,7 +27,7 @@ import com.google.inject.Injector;
 public class ConnectionWriterTest extends TestCase {
 
 	private final ConnectionWriterFactory connectionWriterFactory;
-	private final byte[] secret = new byte[100];
+	private final byte[] outSecret;
 	private final TransportIndex transportIndex = new TransportIndex(13);
 	private final long connection = 12345L;
 
@@ -38,6 +39,8 @@ public class ConnectionWriterTest extends TestCase {
 				new TestDatabaseModule(), new TransportBatchModule(),
 				new TransportModule(), new TransportStreamModule());
 		connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
+		outSecret = new byte[123];
+		new Random().nextBytes(outSecret);
 	}
 
 	@Test
@@ -45,7 +48,7 @@ public class ConnectionWriterTest extends TestCase {
 		ByteArrayOutputStream out =
 			new ByteArrayOutputStream(MIN_CONNECTION_LENGTH);
 		ConnectionWriter w = connectionWriterFactory.createConnectionWriter(out,
-				MIN_CONNECTION_LENGTH, transportIndex, connection, secret);
+				MIN_CONNECTION_LENGTH, transportIndex, connection, outSecret);
 		// Check that the connection writer thinks there's room for a packet
 		long capacity = w.getRemainingCapacity();
 		assertTrue(capacity >= MAX_PACKET_LENGTH);
diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java
index aa002a2536..5ed26f59a1 100644
--- a/test/net/sf/briar/transport/FrameReadWriteTest.java
+++ b/test/net/sf/briar/transport/FrameReadWriteTest.java
@@ -29,10 +29,10 @@ public class FrameReadWriteTest extends TestCase {
 
 	private final CryptoComponent crypto;
 	private final Cipher ivCipher, frameCipher;
+	private final Random random;
+	private final byte[] outSecret;
 	private final ErasableKey ivKey, frameKey, macKey;
 	private final Mac mac;
-	private final Random random;
-	private final byte[] secret = new byte[100];
 	private final TransportIndex transportIndex = new TransportIndex(13);
 	private final long connection = 12345L;
 
@@ -42,12 +42,14 @@ public class FrameReadWriteTest extends TestCase {
 		crypto = i.getInstance(CryptoComponent.class);
 		ivCipher = crypto.getIvCipher();
 		frameCipher = crypto.getFrameCipher();
+		random = new Random();
 		// Since we're sending frames to ourselves, we only need outgoing keys
-		ivKey = crypto.deriveOutgoingIvKey(secret);
-		frameKey = crypto.deriveOutgoingFrameKey(secret);
-		macKey = crypto.deriveOutgoingMacKey(secret);
+		outSecret = new byte[123];
+		random.nextBytes(outSecret);
+		ivKey = crypto.deriveIvKey(outSecret, true);
+		frameKey = crypto.deriveFrameKey(outSecret, true);
+		macKey = crypto.deriveMacKey(outSecret, true);
 		mac = crypto.getMac();
-		random = new Random();
 	}
 
 	@Test
diff --git a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
index d972415507..8ecc2dc580 100644
--- a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
+++ b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
@@ -9,6 +9,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Random;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestDatabaseModule;
@@ -54,7 +55,7 @@ public class BatchConnectionReadWriteTest extends TestCase {
 	private final File bobDir = new File(testDir, "bob");
 	private final TransportId transportId;
 	private final TransportIndex transportIndex;
-	private final byte[] aliceSecret, bobSecret;
+	private final byte[] aliceToBobSecret, bobToAliceSecret;
 
 	private Injector alice, bob;
 
@@ -63,9 +64,11 @@ public class BatchConnectionReadWriteTest extends TestCase {
 		transportId = new TransportId(TestUtils.getRandomId());
 		transportIndex = new TransportIndex(1);
 		// Create matching secrets for Alice and Bob
-		aliceSecret = new byte[123];
-		aliceSecret[0] = (byte) 1;
-		bobSecret = new byte[123];
+		Random r = new Random();
+		aliceToBobSecret = new byte[123];
+		r.nextBytes(aliceToBobSecret);
+		bobToAliceSecret = new byte[123];
+		r.nextBytes(bobToAliceSecret);
 	}
 
 	@Before
@@ -102,7 +105,7 @@ public class BatchConnectionReadWriteTest extends TestCase {
 		DatabaseComponent db = alice.getInstance(DatabaseComponent.class);
 		db.open(false);
 		// Add Bob as a contact and send him a message
-		ContactId contactId = db.addContact(aliceSecret);
+		ContactId contactId = db.addContact(bobToAliceSecret, aliceToBobSecret);
 		String subject = "Hello";
 		byte[] messageBody = "Hi Bob!".getBytes("UTF-8");
 		MessageEncoder encoder = alice.getInstance(MessageEncoder.class);
@@ -134,7 +137,7 @@ public class BatchConnectionReadWriteTest extends TestCase {
 		MessageListener listener = new MessageListener();
 		db.addListener(listener);
 		// Add Alice as a contact
-		ContactId contactId = db.addContact(bobSecret);
+		ContactId contactId = db.addContact(aliceToBobSecret, bobToAliceSecret);
 		// Add the transport
 		assertEquals(transportIndex, db.addTransport(transportId));
 		// Fake a transport update from Alice
-- 
GitLab