diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
index c6cd78c4cb8f36b0fc834cd6634b26d768351da3..3d610c8506d2195e68788f97574f061c05ddb6c1 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java
@@ -137,7 +137,8 @@ public interface CryptoComponent {
 	TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
 
 	/** Encodes the pseudo-random tag that is used to recognise a stream. */
-	void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber);
+	void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
+			long streamNumber);
 
 	/**
 	 * Signs the given byte[] with the given PrivateKey.
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java
index 5bd47f0a0ac83a0e97069d78b9c8fb135b13246c..af9d09731dcb40aa8549797a7573037161f0d3c5 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/TransportConstants.java
@@ -4,6 +4,11 @@ import org.briarproject.bramble.api.crypto.SecretKey;
 
 public interface TransportConstants {
 
+	/**
+	 * The current version of the transport protocol.
+	 */
+	int PROTOCOL_VERSION = 3;
+
 	/**
 	 * The length of the pseudo-random tag in bytes.
 	 */
@@ -15,20 +20,21 @@ public interface TransportConstants {
 	int STREAM_HEADER_NONCE_LENGTH = 24;
 
 	/**
-	 * The length of the stream header initialisation vector (IV) in bytes.
+	 * The length of the message authentication code (MAC) in bytes.
 	 */
-	int STREAM_HEADER_IV_LENGTH = STREAM_HEADER_NONCE_LENGTH - 8;
+	int MAC_LENGTH = 16;
 
 	/**
-	 * The length of the message authentication code (MAC) in bytes.
+	 * The length of the stream header plaintext in bytes. The stream header
+	 * contains the protocol version, stream number and frame key.
 	 */
-	int MAC_LENGTH = 16;
+	int STREAM_HEADER_PLAINTEXT_LENGTH = 2 + 8 + SecretKey.LENGTH;
 
 	/**
 	 * The length of the stream header in bytes.
 	 */
-	int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH
-			+ MAC_LENGTH;
+	int STREAM_HEADER_LENGTH = STREAM_HEADER_NONCE_LENGTH
+			+ STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH;
 
 	/**
 	 * The length of the frame nonce in bytes.
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
index 197ef79738c379d56185f09361997554746b708f..f0c26edd9ef9e59915e68cb71d5524dad7c5a1c0 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java
@@ -45,8 +45,10 @@ import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_B
 import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
+import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
 import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
 import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
+import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
 import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 class CryptoComponentImpl implements CryptoComponent {
@@ -412,8 +414,11 @@ class CryptoComponentImpl implements CryptoComponent {
 	}
 
 	@Override
-	public void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber) {
+	public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
+			long streamNumber) {
 		if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
+		if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
+			throw new IllegalArgumentException();
 		if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
 			throw new IllegalArgumentException();
 		// Initialise the PRF
@@ -421,10 +426,14 @@ class CryptoComponentImpl implements CryptoComponent {
 		// The output of the PRF must be long enough to use as a tag
 		int macLength = prf.getDigestSize();
 		if (macLength < TAG_LENGTH) throw new IllegalStateException();
-		// The input is the stream number as a 64-bit integer
-		byte[] input = new byte[INT_64_BYTES];
-		ByteUtils.writeUint64(streamNumber, input, 0);
-		prf.update(input, 0, input.length);
+		// The input is the protocol version as a 16-bit integer, followed by
+		// the stream number as a 64-bit integer
+		byte[] protocolVersionBytes = new byte[INT_16_BYTES];
+		ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
+		prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
+		byte[] streamNumberBytes = new byte[INT_64_BYTES];
+		ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
+		prf.update(streamNumberBytes, 0, streamNumberBytes.length);
 		byte[] mac = new byte[macLength];
 		prf.doFinal(mac, 0);
 		// The output is the first TAG_LENGTH bytes of the MAC
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java
index 22c522d3d5c5cbd4a2525b70d0fdeab6bf207864..16edc2c740946e421f04e8d5d8059491fb70746b 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterImpl.java
@@ -20,9 +20,11 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
 import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
-import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
+import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
 import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
 
 @NotThreadSafe
@@ -117,7 +119,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
 
 	private void readStreamHeader() throws IOException {
 		byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
-		byte[] streamHeaderPlaintext = new byte[SecretKey.LENGTH];
+		byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
 		// Read the stream header
 		int offset = 0;
 		while (offset < STREAM_HEADER_LENGTH) {
@@ -126,21 +128,35 @@ class StreamDecrypterImpl implements StreamDecrypter {
 			if (read == -1) throw new EOFException();
 			offset += read;
 		}
-		// The nonce consists of the stream number followed by the IV
+		// Extract the nonce
 		byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
-		ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
-		System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce,
-				INT_64_BYTES, STREAM_HEADER_IV_LENGTH);
+		System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce, 0,
+				STREAM_HEADER_NONCE_LENGTH);
 		// Decrypt and authenticate the stream header
 		try {
 			cipher.init(false, streamHeaderKey, streamHeaderNonce);
 			int decrypted = cipher.process(streamHeaderCiphertext,
-					STREAM_HEADER_IV_LENGTH, SecretKey.LENGTH + MAC_LENGTH,
+					STREAM_HEADER_NONCE_LENGTH,
+					STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH,
 					streamHeaderPlaintext, 0);
-			if (decrypted != SecretKey.LENGTH) throw new RuntimeException();
+			if (decrypted != STREAM_HEADER_PLAINTEXT_LENGTH)
+				throw new RuntimeException();
 		} catch (GeneralSecurityException e) {
 			throw new FormatException();
 		}
-		frameKey = new SecretKey(streamHeaderPlaintext);
+		// Check the protocol version
+		int receivedProtocolVersion =
+				ByteUtils.readUint16(streamHeaderPlaintext, 0);
+		if (receivedProtocolVersion != PROTOCOL_VERSION)
+			throw new FormatException();
+		// Check the stream number
+		long receivedStreamNumber = ByteUtils.readUint64(streamHeaderPlaintext,
+				INT_16_BYTES);
+		if (receivedStreamNumber != streamNumber) throw new FormatException();
+		// Extract the frame key
+		byte[] frameKeyBytes = new byte[SecretKey.LENGTH];
+		System.arraycopy(streamHeaderPlaintext, INT_16_BYTES + INT_64_BYTES,
+				frameKeyBytes, 0, SecretKey.LENGTH);
+		frameKey = new SecretKey(frameKeyBytes);
 	}
 }
\ No newline at end of file
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java
index 4feff3ece868bbeb8fbc98614447b47bde4381fd..be7f553de2d4fc2284496069dada1d0dea657267 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java
@@ -13,7 +13,8 @@ import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
+import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 
 @Immutable
@@ -36,22 +37,22 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
 		AuthenticatedCipher cipher = cipherProvider.get();
 		long streamNumber = ctx.getStreamNumber();
 		byte[] tag = new byte[TAG_LENGTH];
-		crypto.encodeTag(tag, ctx.getTagKey(), streamNumber);
-		byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
-		crypto.getSecureRandom().nextBytes(streamHeaderIv);
+		crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
+		byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
+		crypto.getSecureRandom().nextBytes(streamHeaderNonce);
 		SecretKey frameKey = crypto.generateSecretKey();
 		return new StreamEncrypterImpl(out, cipher, streamNumber, tag,
-				streamHeaderIv, ctx.getHeaderKey(), frameKey);
+				streamHeaderNonce, ctx.getHeaderKey(), frameKey);
 	}
 
 	@Override
 	public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
 			SecretKey headerKey) {
 		AuthenticatedCipher cipher = cipherProvider.get();
-		byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
-		crypto.getSecureRandom().nextBytes(streamHeaderIv);
+		byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
+		crypto.getSecureRandom().nextBytes(streamHeaderNonce);
 		SecretKey frameKey = crypto.generateSecretKey();
-		return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderIv,
+		return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
 				headerKey, frameKey);
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java
index 59afd8531f6f3cdc011b2da46d5d6994dbb0bcd2..d64cc4aae85d34ac242c0a264b17d106b9393416 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterImpl.java
@@ -18,9 +18,11 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
 import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
-import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
+import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
 import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
 
 @NotThreadSafe
@@ -33,7 +35,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
 	private final long streamNumber;
 	@Nullable
 	private final byte[] tag;
-	private final byte[] streamHeaderIv;
+	private final byte[] streamHeaderNonce;
 	private final byte[] frameNonce, frameHeader;
 	private final byte[] framePlaintext, frameCiphertext;
 
@@ -41,13 +43,13 @@ class StreamEncrypterImpl implements StreamEncrypter {
 	private boolean writeTag, writeStreamHeader;
 
 	StreamEncrypterImpl(OutputStream out, AuthenticatedCipher cipher,
-			long streamNumber, @Nullable byte[] tag, byte[] streamHeaderIv,
+			long streamNumber, @Nullable byte[] tag, byte[] streamHeaderNonce,
 			SecretKey streamHeaderKey, SecretKey frameKey) {
 		this.out = out;
 		this.cipher = cipher;
 		this.streamNumber = streamNumber;
 		this.tag = tag;
-		this.streamHeaderIv = streamHeaderIv;
+		this.streamHeaderNonce = streamHeaderNonce;
 		this.streamHeaderKey = streamHeaderKey;
 		this.frameKey = frameKey;
 		frameNonce = new byte[FRAME_NONCE_LENGTH];
@@ -114,22 +116,23 @@ class StreamEncrypterImpl implements StreamEncrypter {
 	}
 
 	private void writeStreamHeader() throws IOException {
-		// The nonce consists of the stream number followed by the IV
-		byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
-		ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
-		System.arraycopy(streamHeaderIv, 0, streamHeaderNonce, INT_64_BYTES,
-				STREAM_HEADER_IV_LENGTH);
-		byte[] streamHeaderPlaintext = frameKey.getBytes();
+		// The header contains the protocol version, stream number and frame key
+		byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
+		ByteUtils.writeUint16(PROTOCOL_VERSION, streamHeaderPlaintext, 0);
+		ByteUtils.writeUint64(streamNumber, streamHeaderPlaintext,
+				INT_16_BYTES);
+		System.arraycopy(frameKey.getBytes(), 0, streamHeaderPlaintext,
+				INT_16_BYTES + INT_64_BYTES, SecretKey.LENGTH);
 		byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
-		System.arraycopy(streamHeaderIv, 0, streamHeaderCiphertext, 0,
-				STREAM_HEADER_IV_LENGTH);
-		// Encrypt and authenticate the frame key
+		System.arraycopy(this.streamHeaderNonce, 0, streamHeaderCiphertext, 0,
+				STREAM_HEADER_NONCE_LENGTH);
+		// Encrypt and authenticate the stream header key
 		try {
 			cipher.init(true, streamHeaderKey, streamHeaderNonce);
 			int encrypted = cipher.process(streamHeaderPlaintext, 0,
-					SecretKey.LENGTH, streamHeaderCiphertext,
-					STREAM_HEADER_IV_LENGTH);
-			if (encrypted != SecretKey.LENGTH + MAC_LENGTH)
+					STREAM_HEADER_PLAINTEXT_LENGTH, streamHeaderCiphertext,
+					STREAM_HEADER_NONCE_LENGTH);
+			if (encrypted != STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH)
 				throw new RuntimeException();
 		} catch (GeneralSecurityException badCipher) {
 			throw new RuntimeException(badCipher);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
index 9c1582425c8bf89b2d0cc5171322578ecc4133f4..86c50d5fcc275a24b993184d2bd5c5f540026649 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
@@ -29,6 +29,7 @@ import javax.annotation.concurrent.ThreadSafe;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
@@ -126,7 +127,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 		for (long streamNumber : inKeys.getWindow().getUnseen()) {
 			TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
 			byte[] tag = new byte[TAG_LENGTH];
-			crypto.encodeTag(tag, inKeys.getTagKey(), streamNumber);
+			crypto.encodeTag(tag, inKeys.getTagKey(),PROTOCOL_VERSION,
+					streamNumber);
 			inContexts.put(new Bytes(tag), tagCtx);
 		}
 	}
@@ -242,7 +244,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			// Add tags for any stream numbers added to the window
 			for (long streamNumber : change.getAdded()) {
 				byte[] addTag = new byte[TAG_LENGTH];
-				crypto.encodeTag(addTag, inKeys.getTagKey(), streamNumber);
+				crypto.encodeTag(addTag, inKeys.getTagKey(), PROTOCOL_VERSION,
+						streamNumber);
 				inContexts.put(new Bytes(addTag), new TagContext(
 						tagCtx.contactId, inKeys, streamNumber));
 			}
@@ -250,7 +253,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 			for (long streamNumber : change.getRemoved()) {
 				if (streamNumber == tagCtx.streamNumber) continue;
 				byte[] removeTag = new byte[TAG_LENGTH];
-				crypto.encodeTag(removeTag, inKeys.getTagKey(), streamNumber);
+				crypto.encodeTag(removeTag, inKeys.getTagKey(),
+						PROTOCOL_VERSION, streamNumber);
 				inContexts.remove(new Bytes(removeTag));
 			}
 			// Write the window back to the DB
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java
index 0a6a5f4c0ede9658a15fb50b1bbdf82c3d79e039..e5ea2d31dfdecdb5a6db9739e04399710a961f76 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamDecrypterImplTest.java
@@ -14,7 +14,8 @@ import static junit.framework.Assert.assertEquals;
 import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
-import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
+import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
 import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
 import static org.junit.Assert.assertArrayEquals;
 
@@ -22,7 +23,8 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 
 	private final AuthenticatedCipher cipher;
 	private final SecretKey streamHeaderKey, frameKey;
-	private final byte[] streamHeaderIv, payload;
+	private final byte[] streamHeaderNonce, protocolVersionBytes;
+	private final byte[] streamNumberBytes, payload;
 	private final int payloadLength = 123, paddingLength = 234;
 	private final long streamNumber = 1234;
 
@@ -30,7 +32,12 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 		cipher = new TestAuthenticatedCipher(); // Null cipher
 		streamHeaderKey = TestUtils.getSecretKey();
 		frameKey = TestUtils.getSecretKey();
-		streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
+		streamHeaderNonce =
+				TestUtils.getRandomBytes(STREAM_HEADER_NONCE_LENGTH);
+		protocolVersionBytes = new byte[2];
+		ByteUtils.writeUint16(PROTOCOL_VERSION, protocolVersionBytes, 0);
+		streamNumberBytes = new byte[8];
+		ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
 		payload = TestUtils.getRandomBytes(payloadLength);
 	}
 
@@ -47,7 +54,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 		byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
 
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(streamHeaderIv);
+		out.write(streamHeaderNonce);
+		out.write(protocolVersionBytes);
+		out.write(streamNumberBytes);
 		out.write(frameKey.getBytes());
 		out.write(new byte[MAC_LENGTH]);
 		out.write(frameHeader);
@@ -76,6 +85,85 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 		assertEquals(-1, s.readFrame(buffer));
 	}
 
+	@Test(expected = IOException.class)
+	public void testWrongProtocolVersionThrowsException() throws Exception {
+		byte[] wrongProtocolVersionBytes = new byte[2];
+		ByteUtils.writeUint16(PROTOCOL_VERSION + 1, wrongProtocolVersionBytes,
+				0);
+
+		byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
+		FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
+				paddingLength);
+
+		byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
+		int payloadLength1 = 345, paddingLength1 = 456;
+		FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
+				paddingLength1);
+		byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(streamHeaderNonce);
+		out.write(wrongProtocolVersionBytes);
+		out.write(streamNumberBytes);
+		out.write(frameKey.getBytes());
+		out.write(new byte[MAC_LENGTH]);
+		out.write(frameHeader);
+		out.write(payload);
+		out.write(new byte[paddingLength]);
+		out.write(new byte[MAC_LENGTH]);
+		out.write(frameHeader1);
+		out.write(payload1);
+		out.write(new byte[paddingLength1]);
+		out.write(new byte[MAC_LENGTH]);
+
+		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+		StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
+				streamNumber, streamHeaderKey);
+
+		// Try to read the first frame
+		byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
+		s.readFrame(buffer);
+	}
+
+	@Test(expected = IOException.class)
+	public void testWrongStreamNumberThrowsException() throws Exception {
+		byte[] wrongStreamNumberBytes = new byte[8];
+		ByteUtils.writeUint64(streamNumber + 1, wrongStreamNumberBytes, 0);
+
+		byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
+		FrameEncoder.encodeHeader(frameHeader, false, payloadLength,
+				paddingLength);
+
+		byte[] frameHeader1 = new byte[FRAME_HEADER_LENGTH];
+		int payloadLength1 = 345, paddingLength1 = 456;
+		FrameEncoder.encodeHeader(frameHeader1, true, payloadLength1,
+				paddingLength1);
+		byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		out.write(streamHeaderNonce);
+		out.write(protocolVersionBytes);
+		out.write(wrongStreamNumberBytes);
+		out.write(frameKey.getBytes());
+		out.write(new byte[MAC_LENGTH]);
+		out.write(frameHeader);
+		out.write(payload);
+		out.write(new byte[paddingLength]);
+		out.write(new byte[MAC_LENGTH]);
+		out.write(frameHeader1);
+		out.write(payload1);
+		out.write(new byte[paddingLength1]);
+		out.write(new byte[MAC_LENGTH]);
+
+		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+		StreamDecrypterImpl s = new StreamDecrypterImpl(in, cipher,
+				streamNumber, streamHeaderKey);
+
+		// Try to read the first frame
+		byte[] buffer = new byte[MAX_PAYLOAD_LENGTH];
+		s.readFrame(buffer);
+	}
+
 	@Test(expected = IOException.class)
 	public void testTruncatedFrameThrowsException() throws Exception {
 		byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -83,7 +171,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 				paddingLength);
 
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(streamHeaderIv);
+		out.write(streamHeaderNonce);
+		out.write(protocolVersionBytes);
+		out.write(streamNumberBytes);
 		out.write(frameKey.getBytes());
 		out.write(new byte[MAC_LENGTH]);
 		out.write(frameHeader);
@@ -111,7 +201,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 		byte[] payload = TestUtils.getRandomBytes(payloadLength);
 
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(streamHeaderIv);
+		out.write(streamHeaderNonce);
+		out.write(protocolVersionBytes);
+		out.write(streamNumberBytes);
 		out.write(frameKey.getBytes());
 		out.write(new byte[MAC_LENGTH]);
 		out.write(frameHeader);
@@ -138,7 +230,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 		padding[paddingLength - 1] = 1;
 
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(streamHeaderIv);
+		out.write(streamHeaderNonce);
+		out.write(protocolVersionBytes);
+		out.write(streamNumberBytes);
 		out.write(frameKey.getBytes());
 		out.write(new byte[MAC_LENGTH]);
 		out.write(frameHeader);
@@ -162,7 +256,9 @@ public class StreamDecrypterImplTest extends BrambleTestCase {
 				paddingLength);
 
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		out.write(streamHeaderIv);
+		out.write(streamHeaderNonce);
+		out.write(protocolVersionBytes);
+		out.write(streamNumberBytes);
 		out.write(frameKey.getBytes());
 		out.write(new byte[MAC_LENGTH]);
 		out.write(frameHeader);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java
index c13749f684170dcc55593cb616fc23f5e9331938..c720805bd22c72278d2f2e8432d1cbdaa566edbe 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/StreamEncrypterImplTest.java
@@ -3,6 +3,7 @@ package org.briarproject.bramble.crypto;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.bramble.test.TestUtils;
+import org.briarproject.bramble.util.ByteUtils;
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
@@ -11,8 +12,9 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_HE
 import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
-import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -21,7 +23,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 
 	private final AuthenticatedCipher cipher;
 	private final SecretKey streamHeaderKey, frameKey;
-	private final byte[] tag, streamHeaderIv, payload;
+	private final byte[] tag, streamHeaderNonce, protocolVersionBytes;
+	private final byte[] streamNumberBytes, payload;
 	private final long streamNumber = 1234;
 	private final int payloadLength = 123, paddingLength = 234;
 
@@ -30,7 +33,12 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 		streamHeaderKey = TestUtils.getSecretKey();
 		frameKey = TestUtils.getSecretKey();
 		tag = TestUtils.getRandomBytes(TAG_LENGTH);
-		streamHeaderIv = TestUtils.getRandomBytes(STREAM_HEADER_IV_LENGTH);
+		streamHeaderNonce =
+				TestUtils.getRandomBytes(STREAM_HEADER_NONCE_LENGTH);
+		protocolVersionBytes = new byte[2];
+		ByteUtils.writeUint16(PROTOCOL_VERSION, protocolVersionBytes, 0);
+		streamNumberBytes = new byte[8];
+		ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
 		payload = TestUtils.getRandomBytes(payloadLength);
 	}
 
@@ -38,7 +46,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testRejectsNegativePayloadLength() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, -1, 0, false);
 	}
@@ -47,7 +56,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testRejectsNegativePaddingLength() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, 0, -1, false);
 	}
@@ -56,7 +66,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testRejectsMaxPayloadPlusPadding() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH + 1];
 		s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 1, false);
@@ -66,7 +77,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testAcceptsMaxPayloadIncludingPadding() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
 		s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH - 1, 1, false);
@@ -78,7 +90,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testAcceptsMaxPayloadWithoutPadding() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		byte[] bigPayload = new byte[MAX_PAYLOAD_LENGTH];
 		s.writeFrame(bigPayload, MAX_PAYLOAD_LENGTH, 0, false);
@@ -90,14 +103,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, 0, false);
 
 		// Expect the tag, stream header, frame header, payload and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
 		expected.write(tag);
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -113,14 +129,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWriteUnpaddedFinalFrameWithTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, 0, true);
 
 		// Expect the tag, stream header, frame header, payload and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
 		expected.write(tag);
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -136,13 +155,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWriteUnpaddedNonFinalFrameWithoutTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, null, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, 0, false);
 
 		// Expect the stream header, frame header, payload and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -158,13 +180,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWriteUnpaddedFinalFrameWithoutTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, null, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, 0, true);
 
 		// Expect the stream header, frame header, payload and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -180,14 +205,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWritePaddedNonFinalFrameWithTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, paddingLength, false);
 
 		// Expect the tag, stream header, frame header, payload, padding and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
 		expected.write(tag);
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -205,14 +233,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWritePaddedFinalFrameWithTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, paddingLength, true);
 
 		// Expect the tag, stream header, frame header, payload, padding and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
 		expected.write(tag);
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -230,13 +261,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWritePaddedNonFinalFrameWithoutTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, null, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, paddingLength, false);
 
 		// Expect the stream header, frame header, payload, padding and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -254,13 +288,16 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWritePaddedFinalFrameWithoutTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, null, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		s.writeFrame(payload, payloadLength, paddingLength, true);
 
 		// Expect the stream header, frame header, payload, padding and MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -278,7 +315,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testWriteTwoFramesWithTag() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 		int payloadLength1 = 345, paddingLength1 = 456;
 		byte[] payload1 = TestUtils.getRandomBytes(payloadLength1);
 
@@ -289,7 +327,9 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 		// MAC, second frame header, payload, padding, MAC
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
 		expected.write(tag);
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 		byte[] expectedFrameHeader = new byte[FRAME_HEADER_LENGTH];
@@ -315,7 +355,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 			throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		// Flush the stream once
 		s.flush();
@@ -323,7 +364,9 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 		// Expect the tag and stream header
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
 		expected.write(tag);
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 
@@ -335,7 +378,8 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 			throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, tag, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, tag, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		// Flush the stream twice
 		s.flush();
@@ -344,7 +388,9 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 		// Expect the tag and stream header
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
 		expected.write(tag);
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 
@@ -355,14 +401,17 @@ public class StreamEncrypterImplTest extends BrambleTestCase {
 	public void testFlushDoesNotWriteTagIfNull() throws Exception {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		StreamEncrypterImpl s = new StreamEncrypterImpl(out, cipher,
-				streamNumber, null, streamHeaderIv, streamHeaderKey, frameKey);
+				streamNumber, null, streamHeaderNonce, streamHeaderKey,
+				frameKey);
 
 		// Flush the stream once
 		s.flush();
 
 		// Expect the stream header
 		ByteArrayOutputStream expected = new ByteArrayOutputStream();
-		expected.write(streamHeaderIv);
+		expected.write(streamHeaderNonce);
+		expected.write(protocolVersionBytes);
+		expected.write(streamNumberBytes);
 		expected.write(frameKey.getBytes());
 		expected.write(new byte[MAC_LENGTH]);
 
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/TagEncodingTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/TagEncodingTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b10654e9f3d2c7b9d571b71d6634f9c0060dffe3
--- /dev/null
+++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/TagEncodingTest.java
@@ -0,0 +1,59 @@
+package org.briarproject.bramble.crypto;
+
+import org.briarproject.bramble.api.Bytes;
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.test.BrambleTestCase;
+import org.briarproject.bramble.test.TestSecureRandomProvider;
+import org.briarproject.bramble.test.TestUtils;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
+import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+
+public class TagEncodingTest extends BrambleTestCase {
+
+	private final CryptoComponent crypto;
+	private final SecretKey tagKey;
+	private final long streamNumber = 1234567890;
+
+	public TagEncodingTest() {
+		crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
+		tagKey = TestUtils.getSecretKey();
+	}
+
+	@Test
+	public void testKeyAffectsTag() throws Exception {
+		Set<Bytes> set = new HashSet<Bytes>();
+		for (int i = 0; i < 100; i++) {
+			byte[] tag = new byte[TAG_LENGTH];
+			SecretKey tagKey = TestUtils.getSecretKey();
+			crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber);
+			assertTrue(set.add(new Bytes(tag)));
+		}
+	}
+
+	@Test
+	public void testProtocolVersionAffectsTag() throws Exception {
+		Set<Bytes> set = new HashSet<Bytes>();
+		for (int i = 0; i < 100; i++) {
+			byte[] tag = new byte[TAG_LENGTH];
+			crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
+			assertTrue(set.add(new Bytes(tag)));
+		}
+	}
+
+	@Test
+	public void testStreamNumberAffectsTag() throws Exception {
+		Set<Bytes> set = new HashSet<Bytes>();
+		for (int i = 0; i < 100; i++) {
+			byte[] tag = new byte[TAG_LENGTH];
+			crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
+			assertTrue(set.add(new Bytes(tag)));
+		}
+	}
+}
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java
index 533dfb2fab638178323b3dfe534b044a19daf663..c19d148b1eeccdb98157e3999c3353390efb8942 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/sync/SyncIntegrationTest.java
@@ -34,6 +34,7 @@ import java.util.Collection;
 import javax.inject.Inject;
 
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -115,7 +116,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
 	private void read(byte[] connectionData) throws Exception {
 		// Calculate the expected tag
 		byte[] expectedTag = new byte[TAG_LENGTH];
-		crypto.encodeTag(expectedTag, tagKey, streamNumber);
+		crypto.encodeTag(expectedTag, tagKey, PROTOCOL_VERSION, streamNumber);
 
 		// Read the tag
 		InputStream in = new ByteArrayInputStream(connectionData);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
index 5962afcf5269e1df0a41925b7de2586db0dedff3..b66f5c745493e42ccdaaa2fd491da2a80e1be1e6 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
@@ -33,6 +33,7 @@ import java.util.concurrent.ScheduledExecutorService;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
+import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
@@ -86,7 +87,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets per contact)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(6).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction());
 			}
 			// Save the keys that were rotated
@@ -133,7 +134,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction());
 			}
 			// Save the keys
@@ -199,7 +200,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction());
 			}
 			// Rotate the transport keys (the keys are unaffected)
@@ -247,7 +248,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction());
 			}
 			// Rotate the transport keys (the keys are unaffected)
@@ -306,7 +307,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction());
 			}
 			// Rotate the transport keys (the keys are unaffected)
@@ -355,7 +356,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction(tags));
 			}
 			// Rotate the transport keys (the keys are unaffected)
@@ -365,7 +366,8 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			oneOf(db).addTransportKeys(txn, contactId, transportKeys);
 			// Encode a new tag after sliding the window
 			oneOf(crypto).encodeTag(with(any(byte[].class)),
-					with(tagKey), with((long) REORDERING_WINDOW_SIZE));
+					with(tagKey), with(PROTOCOL_VERSION),
+					with((long) REORDERING_WINDOW_SIZE));
 			will(new EncodeTagAction(tags));
 			// Save the reordering window (previous rotation period, base 1)
 			oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
@@ -428,7 +430,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction());
 			}
 			// Schedule key rotation at the start of the next rotation period
@@ -450,7 +452,7 @@ public class TransportKeyManagerImplTest extends BrambleTestCase {
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
-						with(tagKey), with(i));
+						with(tagKey), with(PROTOCOL_VERSION), with(i));
 				will(new EncodeTagAction());
 			}
 			// Save the keys that were rotated