From 14d5e6fe642739dc804bc5e870cc232c662e5f20 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Fri, 2 Dec 2011 12:57:39 +0000
Subject: [PATCH] Replaced encrypted IVs with pseudo-random tags.

---
 .../sf/briar/crypto/CryptoComponentImpl.java  | 10 ++--
 .../transport/ConnectionDecrypterImpl.java    | 11 ++--
 .../transport/ConnectionEncrypterImpl.java    | 20 ++-----
 .../ConnectionReaderFactoryImpl.java          | 28 ++--------
 .../transport/ConnectionRecogniserImpl.java   | 21 ++------
 .../ConnectionWriterFactoryImpl.java          | 26 ++-------
 .../net/sf/briar/transport/IvEncoder.java     | 40 +++-----------
 .../net/sf/briar/transport/TagEncoder.java    | 54 +++++++++++++++++++
 .../ConnectionDecrypterImplTest.java          | 23 +++-----
 .../ConnectionEncrypterImplTest.java          | 17 ++----
 .../ConnectionRecogniserImplTest.java         |  4 +-
 .../briar/transport/FrameReadWriteTest.java   | 24 +++------
 12 files changed, 104 insertions(+), 174 deletions(-)
 create mode 100644 components/net/sf/briar/transport/TagEncoder.java

diff --git a/components/net/sf/briar/crypto/CryptoComponentImpl.java b/components/net/sf/briar/crypto/CryptoComponentImpl.java
index 34eeea8bd8..b1ae497099 100644
--- a/components/net/sf/briar/crypto/CryptoComponentImpl.java
+++ b/components/net/sf/briar/crypto/CryptoComponentImpl.java
@@ -27,13 +27,11 @@ class CryptoComponentImpl implements CryptoComponent {
 	private static final String DIGEST_ALGO = "SHA-256";
 	private static final String KEY_PAIR_ALGO = "ECDSA";
 	private static final int KEY_PAIR_BITS = 256;
-	private static final String FRAME_CIPHER_ALGO = "AES/CTR/NoPadding";
+	private static final String CIPHER_ALGO = "AES/CTR/NoPadding";
 	private static final String SECRET_KEY_ALGO = "AES";
 	private static final int SECRET_KEY_BYTES = 32; // 256 bits
-	private static final String TAG_CIPHER_ALGO = "AES/ECB/NoPadding";
 	private static final String MAC_ALGO = "HMacSHA256";
 	private static final String SIGNATURE_ALGO = "ECDSA";
-	private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
 	private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits
 
 	// Labels for key derivation, null-terminated
@@ -104,7 +102,7 @@ class CryptoComponentImpl implements CryptoComponent {
 		assert ivBytes[ivBytes.length - 1] == 0;
 		IvParameterSpec iv = new IvParameterSpec(ivBytes);
 		try {
-			Cipher cipher = Cipher.getInstance(KEY_DERIVATION_ALGO, PROVIDER);
+			Cipher cipher = Cipher.getInstance(CIPHER_ALGO, PROVIDER);
 			cipher.init(Cipher.ENCRYPT_MODE, key, iv);
 			byte[] output = cipher.doFinal(KEY_DERIVATION_INPUT);
 			assert output.length == SECRET_KEY_BYTES;
@@ -137,7 +135,7 @@ class CryptoComponentImpl implements CryptoComponent {
 
 	public Cipher getFrameCipher() {
 		try {
-			return Cipher.getInstance(FRAME_CIPHER_ALGO, PROVIDER);
+			return Cipher.getInstance(CIPHER_ALGO, PROVIDER);
 		} catch(GeneralSecurityException e) {
 			throw new RuntimeException(e);
 		}
@@ -178,7 +176,7 @@ class CryptoComponentImpl implements CryptoComponent {
 
 	public Cipher getTagCipher() {
 		try {
-			return Cipher.getInstance(TAG_CIPHER_ALGO, PROVIDER);
+			return Cipher.getInstance(CIPHER_ALGO, PROVIDER);
 		} catch(GeneralSecurityException e) {
 			throw new RuntimeException(e);
 		}
diff --git a/components/net/sf/briar/transport/ConnectionDecrypterImpl.java b/components/net/sf/briar/transport/ConnectionDecrypterImpl.java
index 7eb56ab101..ac9557c88c 100644
--- a/components/net/sf/briar/transport/ConnectionDecrypterImpl.java
+++ b/components/net/sf/briar/transport/ConnectionDecrypterImpl.java
@@ -1,6 +1,5 @@
 package net.sf.briar.transport;
 
-import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 import java.io.EOFException;
@@ -13,10 +12,11 @@ import java.security.InvalidKeyException;
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
-import net.sf.briar.api.crypto.ErasableKey;
 import javax.crypto.ShortBufferException;
 import javax.crypto.spec.IvParameterSpec;
 
+import net.sf.briar.api.crypto.ErasableKey;
+
 class ConnectionDecrypterImpl extends FilterInputStream
 implements ConnectionDecrypter {
 
@@ -28,14 +28,13 @@ implements ConnectionDecrypter {
 	private long frame = 0L;
 	private boolean betweenFrames = true;
 
-	ConnectionDecrypterImpl(InputStream in, byte[] iv, Cipher frameCipher,
+	ConnectionDecrypterImpl(InputStream in, Cipher frameCipher,
 			ErasableKey frameKey) {
 		super(in);
-		if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
-		this.iv = iv;
 		this.frameCipher = frameCipher;
 		this.frameKey = frameKey;
-		buf = new byte[TAG_LENGTH];
+		iv = IvEncoder.encodeIv(0, frameCipher.getBlockSize());
+		buf = new byte[frameCipher.getBlockSize()];
 	}
 
 	public InputStream getInputStream() {
diff --git a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
index 0eb7ff5111..54446b09e2 100644
--- a/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
+++ b/components/net/sf/briar/transport/ConnectionEncrypterImpl.java
@@ -25,27 +25,17 @@ implements ConnectionEncrypter {
 	private long capacity, frame = 0L;
 	private boolean tagWritten = false, betweenFrames = false;
 
-	ConnectionEncrypterImpl(OutputStream out, long capacity, byte[] iv,
-			Cipher tagCipher, Cipher frameCipher, ErasableKey tagKey,
-			ErasableKey frameKey) {
+	ConnectionEncrypterImpl(OutputStream out, long capacity, Cipher tagCipher,
+			Cipher frameCipher, ErasableKey tagKey, ErasableKey frameKey) {
 		super(out);
 		this.capacity = capacity;
-		this.iv = iv;
 		this.frameCipher = frameCipher;
 		this.frameKey = frameKey;
+		iv = IvEncoder.encodeIv(0, frameCipher.getBlockSize());
 		// Encrypt the tag
-		try {
-			tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
-			tag = tagCipher.doFinal(iv);
-		} catch(BadPaddingException badCipher) {
-			throw new IllegalArgumentException(badCipher);
-		} catch(IllegalBlockSizeException badCipher) {
-			throw new IllegalArgumentException(badCipher);
-		} catch(InvalidKeyException badKey) {
-			throw new IllegalArgumentException(badKey);
-		}
-		if(tag.length != TAG_LENGTH) throw new IllegalArgumentException();
+		tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
 		tagKey.erase();
+		if(tag.length != TAG_LENGTH) throw new IllegalArgumentException();
 	}
 
 	public OutputStream getOutputStream() {
diff --git a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
index 6ad7b806f8..837ce54087 100644
--- a/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionReaderFactoryImpl.java
@@ -1,11 +1,8 @@
 package net.sf.briar.transport;
 
 import java.io.InputStream;
-import java.security.InvalidKeyException;
 
-import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.Mac;
 
 import net.sf.briar.api.crypto.CryptoComponent;
@@ -28,26 +25,12 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 
 	public ConnectionReader createConnectionReader(InputStream in,
 			ConnectionContext ctx, byte[] tag) {
-		// Decrypt the tag
+		// Validate the tag
 		Cipher tagCipher = crypto.getTagCipher();
 		ErasableKey tagKey = crypto.deriveTagKey(ctx.getSecret(), true);
-		byte[] iv;
-		try {
-			tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
-			iv = tagCipher.doFinal(tag);
-		} catch(BadPaddingException badCipher) {
-			throw new IllegalArgumentException(badCipher);
-		} catch(IllegalBlockSizeException badCipher) {
-			throw new IllegalArgumentException(badCipher);
-		} catch(InvalidKeyException badKey) {
-			throw new IllegalArgumentException(badKey);
-		}
+		boolean valid = TagEncoder.validateTag(tag, 0, tagCipher, tagKey);
 		tagKey.erase();
-		// Validate the tag
-		int index = ctx.getTransportIndex().getInt();
-		long connection = ctx.getConnectionNumber();
-		if(!IvEncoder.validateIv(iv, index, connection))
-			throw new IllegalArgumentException();
+		if(!valid) throw new IllegalArgumentException();
 		return createConnectionReader(in, true, ctx);
 	}
 
@@ -64,11 +47,8 @@ class ConnectionReaderFactoryImpl implements ConnectionReaderFactory {
 		ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
 		ByteUtils.erase(secret);
 		// Create the decrypter
-		int index = ctx.getTransportIndex().getInt();
-		long connection = ctx.getConnectionNumber();
-		byte[] iv = IvEncoder.encodeIv(index, connection);
 		Cipher frameCipher = crypto.getFrameCipher();
-		ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, iv,
+		ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in,
 				frameCipher, frameKey);
 		// Create the reader
 		Mac mac = crypto.getMac();
diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
index e365d6a99c..08c2ed3de2 100644
--- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
+++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
@@ -2,7 +2,6 @@ package net.sf.briar.transport;
 
 import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 
-import java.security.InvalidKeyException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -15,9 +14,7 @@ import java.util.concurrent.Executor;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
 
 import net.sf.briar.api.Bytes;
 import net.sf.briar.api.ContactId;
@@ -103,22 +100,10 @@ DatabaseListener {
 
 	// Locking: this
 	private Bytes calculateTag(Context ctx, byte[] secret) {
-		byte[] iv = IvEncoder.encodeIv(ctx.transportIndex.getInt(),
-				ctx.connection);
 		ErasableKey tagKey = crypto.deriveTagKey(secret, true);
-		try {
-			tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
-			byte[] tag = tagCipher.doFinal(iv);
-			return new Bytes(tag);
-		} catch(BadPaddingException badCipher) {
-			throw new RuntimeException(badCipher);
-		} catch(IllegalBlockSizeException badCipher) {
-			throw new RuntimeException(badCipher);
-		} catch(InvalidKeyException badKey) {
-			throw new RuntimeException(badKey);
-		} finally {
-			tagKey.erase();
-		}
+		byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
+		tagKey.erase();
+		return new Bytes(tag);
 	}
 
 	public void acceptConnection(final TransportId t, final byte[] tag,
diff --git a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
index 04fffe4e90..694db4ae77 100644
--- a/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
+++ b/components/net/sf/briar/transport/ConnectionWriterFactoryImpl.java
@@ -1,11 +1,8 @@
 package net.sf.briar.transport;
 
 import java.io.OutputStream;
-import java.security.InvalidKeyException;
 
-import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.Mac;
 
 import net.sf.briar.api.crypto.CryptoComponent;
@@ -36,23 +33,9 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
 		// Decrypt the tag
 		Cipher tagCipher = crypto.getTagCipher();
 		ErasableKey tagKey = crypto.deriveTagKey(ctx.getSecret(), true);
-		byte[] iv;
-		try {
-			tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
-			iv = tagCipher.doFinal(tag);
-		} catch(BadPaddingException badCipher) {
-			throw new RuntimeException(badCipher);
-		} catch(IllegalBlockSizeException badCipher) {
-			throw new RuntimeException(badCipher);
-		} catch(InvalidKeyException badKey) {
-			throw new RuntimeException(badKey);
-		}
+		boolean valid = TagEncoder.validateTag(tag, 0, tagCipher, tagKey);
 		tagKey.erase();
-		// Validate the tag
-		int index = ctx.getTransportIndex().getInt();
-		long connection = ctx.getConnectionNumber();
-		if(!IvEncoder.validateIv(iv, index, connection))
-			throw new IllegalArgumentException();
+		if(!valid) throw new IllegalArgumentException();
 		return createConnectionWriter(out, capacity, false, ctx);
 	}
 
@@ -65,13 +48,10 @@ class ConnectionWriterFactoryImpl implements ConnectionWriterFactory {
 		ErasableKey macKey = crypto.deriveMacKey(secret, initiator);
 		ByteUtils.erase(secret);
 		// Create the encrypter
-		int index = ctx.getTransportIndex().getInt();
-		long connection = ctx.getConnectionNumber();
-		byte[] iv = IvEncoder.encodeIv(index, connection);
 		Cipher tagCipher = crypto.getTagCipher();
 		Cipher frameCipher = crypto.getFrameCipher();
 		ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
-				capacity, iv, tagCipher, frameCipher, tagKey, frameKey);
+				capacity, tagCipher, frameCipher, tagKey, frameKey);
 		// Create the writer
 		Mac mac = crypto.getMac();
 		return new ConnectionWriterImpl(encrypter, mac, macKey);
diff --git a/components/net/sf/briar/transport/IvEncoder.java b/components/net/sf/briar/transport/IvEncoder.java
index 73fd0aea37..8475f29d3e 100644
--- a/components/net/sf/briar/transport/IvEncoder.java
+++ b/components/net/sf/briar/transport/IvEncoder.java
@@ -1,45 +1,19 @@
 package net.sf.briar.transport;
 
-import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 import net.sf.briar.util.ByteUtils;
 
 class IvEncoder {
 
-	static byte[] encodeIv(int index, long connection) {
-		byte[] iv = new byte[TAG_LENGTH];
-		// Encode the transport index as an unsigned 16-bit integer
-		ByteUtils.writeUint16(index, iv, 4);
-		// Encode the connection number as an unsigned 32-bit integer
-		ByteUtils.writeUint32(connection, iv, 6);
+	static byte[] encodeIv(long frame, int blockSize) {
+		if(frame > ByteUtils.MAX_32_BIT_UNSIGNED)
+			throw new IllegalArgumentException();
+		byte[] iv = new byte[blockSize];
+		updateIv(iv, frame);
 		return iv;
 	}
 
 	static void updateIv(byte[] iv, long frame) {
-		if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
-		// Encode the frame number as an unsigned 32-bit integer
-		ByteUtils.writeUint32(frame, iv, 10);
-	}
-
-	static boolean validateIv(byte[] iv, int index, long connection) {
-		if(iv.length != TAG_LENGTH) return false;
-		// Check that the reserved bits are all zero
-		for(int i = 0; i < 3; i++) if(iv[i] != 0) return false;
-		for(int i = 10; i < iv.length; i++) if(iv[i] != 0) return false;
-		// Check that the transport index matches
-		if(index != getTransportIndex(iv)) return false;
-		// Check that the connection number matches
-		if(connection != getConnectionNumber(iv)) return false;
-		// The IV is valid
-		return true;
-	}
-
-	static int getTransportIndex(byte[] iv) {
-		if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
-		return ByteUtils.readUint16(iv, 4);
-	}
-
-	static long getConnectionNumber(byte[] iv) {
-		if(iv.length != TAG_LENGTH) throw new IllegalArgumentException();
-		return ByteUtils.readUint32(iv, 6);
+		// Encode the frame number as a uint32, leaving 2 bytes for the counter
+		ByteUtils.writeUint32(frame, iv, iv.length - 6);
 	}
 }
diff --git a/components/net/sf/briar/transport/TagEncoder.java b/components/net/sf/briar/transport/TagEncoder.java
new file mode 100644
index 0000000000..d74da3174c
--- /dev/null
+++ b/components/net/sf/briar/transport/TagEncoder.java
@@ -0,0 +1,54 @@
+package net.sf.briar.transport;
+
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+
+import net.sf.briar.api.crypto.ErasableKey;
+import net.sf.briar.api.transport.TransportConstants;
+import net.sf.briar.util.ByteUtils;
+
+class TagEncoder {
+
+	static byte[] encodeTag(long frame, Cipher tagCipher, ErasableKey tagKey) {
+		if(frame > ByteUtils.MAX_32_BIT_UNSIGNED)
+			throw new IllegalArgumentException();
+		// The plaintext is blank
+		byte[] plaintext = new byte[TransportConstants.TAG_LENGTH];
+		// Encode the frame number as a uint32 at the end of the IV
+		byte[] iv = new byte[tagCipher.getBlockSize()];
+		if(iv.length != plaintext.length) throw new IllegalArgumentException();
+		ByteUtils.writeUint32(frame, iv, iv.length - 4);
+		IvParameterSpec ivSpec = new IvParameterSpec(iv);
+		try {
+			tagCipher.init(Cipher.ENCRYPT_MODE, tagKey, ivSpec);
+			return tagCipher.doFinal(plaintext);
+		} catch(GeneralSecurityException e) {
+			// Unsuitable cipher or key
+			throw new IllegalArgumentException(e);
+		}
+	}
+
+	static boolean validateTag(byte[] tag, long frame, Cipher tagCipher,
+			ErasableKey tagKey) {
+		if(tag.length != TransportConstants.TAG_LENGTH) return false;
+		// Encode the frame number as a uint32 at the end of the IV
+		byte[] iv = new byte[tagCipher.getBlockSize()];
+		if(iv.length != tag.length) throw new IllegalArgumentException();
+		ByteUtils.writeUint32(frame, iv, iv.length - 4);
+		IvParameterSpec ivSpec = new IvParameterSpec(iv);
+		try {
+			tagCipher.init(Cipher.DECRYPT_MODE, tagKey, ivSpec);
+			byte[] plaintext = tagCipher.doFinal(tag);
+			// The plaintext should be blank
+			for(int i = 0; i < plaintext.length; i++) {
+				if(plaintext[i] != 0) return false;
+			}
+			return true;
+		} catch(GeneralSecurityException e) {
+			// Unsuitable cipher or key
+			throw new IllegalArgumentException(e);
+		}
+	}
+}
diff --git a/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java
index 1289790de1..e8a13dc3f3 100644
--- a/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionDecrypterImplTest.java
@@ -1,18 +1,16 @@
 package net.sf.briar.transport;
 
-import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 import static org.junit.Assert.assertArrayEquals;
 
 import java.io.ByteArrayInputStream;
 
 import javax.crypto.Cipher;
-import net.sf.briar.api.crypto.ErasableKey;
 import javax.crypto.spec.IvParameterSpec;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
 import net.sf.briar.api.crypto.CryptoComponent;
-import net.sf.briar.api.protocol.TransportIndex;
+import net.sf.briar.api.crypto.ErasableKey;
 import net.sf.briar.crypto.CryptoModule;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -25,18 +23,14 @@ public class ConnectionDecrypterImplTest extends TestCase {
 
 	private static final int MAC_LENGTH = 32;
 
-	private final Cipher tagCipher, frameCipher;
-	private final ErasableKey tagKey, frameKey;
-	private final TransportIndex transportIndex = new TransportIndex(13);
-	private final long connection = 12345L;
+	private final Cipher frameCipher;
+	private final ErasableKey frameKey;
 
 	public ConnectionDecrypterImplTest() {
 		super();
 		Injector i = Guice.createInjector(new CryptoModule());
 		CryptoComponent crypto = i.getInstance(CryptoComponent.class);
-		tagCipher = crypto.getTagCipher();
 		frameCipher = crypto.getFrameCipher();
-		tagKey = crypto.generateTestKey();
 		frameKey = crypto.generateTestKey();
 	}
 
@@ -51,12 +45,8 @@ public class ConnectionDecrypterImplTest extends TestCase {
 	}
 
 	private void testDecryption(boolean initiator) throws Exception {
-		// Calculate the plaintext and ciphertext for the IV
-		byte[] iv = IvEncoder.encodeIv(transportIndex.getInt(), connection);
-		tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
-		byte[] tag  = tagCipher.doFinal(iv);
-		assertEquals(TAG_LENGTH, tag.length);
 		// Calculate the expected plaintext for the first frame
+		byte[] iv = new byte[frameCipher.getBlockSize()];
 		byte[] ciphertext = new byte[123];
 		byte[] ciphertextMac = new byte[MAC_LENGTH];
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
@@ -84,9 +74,8 @@ public class ConnectionDecrypterImplTest extends TestCase {
 		out.write(ciphertextMac);
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
 		// Use a ConnectionDecrypter to decrypt the ciphertext
-		ConnectionDecrypter d = new ConnectionDecrypterImpl(in,
-				IvEncoder.encodeIv(transportIndex.getInt(), connection),
-				frameCipher, frameKey);
+		ConnectionDecrypter d = new ConnectionDecrypterImpl(in, frameCipher,
+				frameKey);
 		// First frame
 		byte[] decrypted = new byte[ciphertext.length];
 		TestUtils.readFully(d.getInputStream(), decrypted);
diff --git a/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java
index 1af4902cfd..b7463f0126 100644
--- a/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionEncrypterImplTest.java
@@ -1,17 +1,15 @@
 package net.sf.briar.transport;
 
-import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 import static org.junit.Assert.assertArrayEquals;
 
 import java.io.ByteArrayOutputStream;
 
 import javax.crypto.Cipher;
-import net.sf.briar.api.crypto.ErasableKey;
 import javax.crypto.spec.IvParameterSpec;
 
 import junit.framework.TestCase;
 import net.sf.briar.api.crypto.CryptoComponent;
-import net.sf.briar.api.protocol.TransportIndex;
+import net.sf.briar.api.crypto.ErasableKey;
 import net.sf.briar.crypto.CryptoModule;
 
 import org.junit.Test;
@@ -25,8 +23,6 @@ public class ConnectionEncrypterImplTest extends TestCase {
 
 	private final Cipher tagCipher, frameCipher;
 	private final ErasableKey tagKey, frameKey;
-	private final TransportIndex transportIndex = new TransportIndex(13);
-	private final long connection = 12345L;
 
 	public ConnectionEncrypterImplTest() {
 		super();
@@ -49,12 +45,10 @@ public class ConnectionEncrypterImplTest extends TestCase {
 	}
 
 	private void testEncryption(boolean initiator) throws Exception {
-		// Calculate the expected ciphertext for the IV
-		byte[] iv = IvEncoder.encodeIv(transportIndex.getInt(), connection);
-		tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
-		byte[] tag = tagCipher.doFinal(iv);
-		assertEquals(TAG_LENGTH, tag.length);
+		// Calculate the expected tag
+		byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
 		// Calculate the expected ciphertext for the first frame
+		byte[] iv = new byte[frameCipher.getBlockSize()];
 		byte[] plaintext = new byte[123];
 		byte[] plaintextMac = new byte[MAC_LENGTH];
 		IvParameterSpec ivSpec = new IvParameterSpec(iv);
@@ -82,9 +76,8 @@ public class ConnectionEncrypterImplTest extends TestCase {
 		byte[] expected = out.toByteArray();
 		// Use a ConnectionEncrypter to encrypt the plaintext
 		out.reset();
-		iv = IvEncoder.encodeIv(transportIndex.getInt(), connection);
 		ConnectionEncrypter e = new ConnectionEncrypterImpl(out, Long.MAX_VALUE,
-				iv, tagCipher, frameCipher, tagKey, frameKey);
+				tagCipher, frameCipher, tagKey, frameKey);
 		e.getOutputStream().write(plaintext);
 		e.writeMac(plaintextMac);
 		e.getOutputStream().write(plaintext1);
diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
index 2198b6402c..2ff78152bb 100644
--- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
@@ -617,8 +617,6 @@ public class ConnectionRecogniserImplTest extends TestCase {
 		// Calculate the expected tag for connection number 3
 		ErasableKey tagKey = crypto.deriveTagKey(secret, true);
 		Cipher tagCipher = crypto.getTagCipher();
-		tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
-		byte[] iv = IvEncoder.encodeIv(remoteIndex.getInt(), 3);
-		return tagCipher.doFinal(iv);
+		return TagEncoder.encodeTag(0, tagCipher, tagKey);
 	}
 }
diff --git a/test/net/sf/briar/transport/FrameReadWriteTest.java b/test/net/sf/briar/transport/FrameReadWriteTest.java
index b15428f3ea..4c71859f51 100644
--- a/test/net/sf/briar/transport/FrameReadWriteTest.java
+++ b/test/net/sf/briar/transport/FrameReadWriteTest.java
@@ -11,11 +11,10 @@ import java.util.Random;
 
 import javax.crypto.Cipher;
 import javax.crypto.Mac;
-import net.sf.briar.api.crypto.ErasableKey;
 
 import junit.framework.TestCase;
 import net.sf.briar.api.crypto.CryptoComponent;
-import net.sf.briar.api.protocol.TransportIndex;
+import net.sf.briar.api.crypto.ErasableKey;
 import net.sf.briar.api.transport.ConnectionReader;
 import net.sf.briar.api.transport.ConnectionWriter;
 import net.sf.briar.crypto.CryptoModule;
@@ -33,8 +32,6 @@ public class FrameReadWriteTest extends TestCase {
 	private final byte[] outSecret;
 	private final ErasableKey tagKey, frameKey, macKey;
 	private final Mac mac;
-	private final TransportIndex transportIndex = new TransportIndex(13);
-	private final long connection = 12345L;
 
 	public FrameReadWriteTest() {
 		super();
@@ -63,11 +60,8 @@ public class FrameReadWriteTest extends TestCase {
 	}
 
 	private void testWriteAndRead(boolean initiator) throws Exception {
-		// Create and encrypt the IV
-		byte[] iv = IvEncoder.encodeIv(transportIndex.getInt(), connection);
-		tagCipher.init(Cipher.ENCRYPT_MODE, tagKey);
-		byte[] tag = tagCipher.doFinal(iv);
-		assertEquals(TAG_LENGTH, tag.length);
+		// Encode the tag
+		byte[] tag = TagEncoder.encodeTag(0, tagCipher, tagKey);
 		// Generate two random frames
 		byte[] frame = new byte[12345];
 		random.nextBytes(frame);
@@ -80,7 +74,7 @@ public class FrameReadWriteTest extends TestCase {
 		// Write the frames
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		ConnectionEncrypter encrypter = new ConnectionEncrypterImpl(out,
-				Long.MAX_VALUE, iv, tagCipher, frameCipher, tagCopy, frameCopy);
+				Long.MAX_VALUE, tagCipher, frameCipher, tagCopy, frameCopy);
 		ConnectionWriter writer = new ConnectionWriterImpl(encrypter, mac,
 				macCopy);
 		OutputStream out1 = writer.getOutputStream();
@@ -88,18 +82,14 @@ public class FrameReadWriteTest extends TestCase {
 		out1.flush();
 		out1.write(frame1);
 		out1.flush();
-		// Read the IV back
+		// Read the tag back
 		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
 		byte[] recoveredTag = new byte[TAG_LENGTH];
 		assertEquals(TAG_LENGTH, in.read(recoveredTag));
 		assertArrayEquals(tag, recoveredTag);
-		// Decrypt the IV
-		tagCipher.init(Cipher.DECRYPT_MODE, tagKey);
-		byte[] recoveredIv = tagCipher.doFinal(recoveredTag);
-		iv = IvEncoder.encodeIv(transportIndex.getInt(), connection);
-		assertArrayEquals(iv, recoveredIv);
+		assertTrue(TagEncoder.validateTag(tag, 0, tagCipher, tagKey));
 		// Read the frames back
-		ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in, iv,
+		ConnectionDecrypter decrypter = new ConnectionDecrypterImpl(in,
 				frameCipher, frameKey);
 		ConnectionReader reader = new ConnectionReaderImpl(decrypter, mac,
 				macKey);
-- 
GitLab