diff --git a/briar-api/src/org/briarproject/api/transport/TransportConstants.java b/briar-api/src/org/briarproject/api/transport/TransportConstants.java
index ae3a6c3daf9af55555e07023020a8a3ac0932629..5f126c7a40db6aa23144ca4591ee453ec60acbba 100644
--- a/briar-api/src/org/briarproject/api/transport/TransportConstants.java
+++ b/briar-api/src/org/briarproject/api/transport/TransportConstants.java
@@ -8,23 +8,29 @@ public interface TransportConstants {
 	/** The length of the pseudo-random tag in bytes. */
 	int TAG_LENGTH = 16;
 
-	/** The length of the stream header IV in bytes. */
-	int STREAM_HEADER_IV_LENGTH = 24;
-
 	/** The length of the message authentication code (MAC) in bytes. */
 	int MAC_LENGTH = 16;
 
+	/** The length of the stream header initialisation vector (IV) in bytes. */
+	int STREAM_HEADER_IV_LENGTH = 24;
+
 	/** The length of the stream header in bytes. */
 	int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH
 			+ MAC_LENGTH;
 
-	/** The length of the frame initalisation vector (IV) in bytes. */
-	int FRAME_IV_LENGTH = 24;
+	/** The length of the frame nonce in bytes. */
+	int FRAME_NONCE_LENGTH = 24;
 
-	/** The length of the frame header in bytes. */
-	int FRAME_HEADER_LENGTH = 4 + MAC_LENGTH;
+	/** The length of the plaintext frame header in bytes. */
+	int FRAME_HEADER_PLAINTEXT_LENGTH = 4;
 
-	/** The maximum length of a frame in bytes, including the header and MAC. */
+	/** The length of the encrypted and authenticated frame header in bytes. */
+	int FRAME_HEADER_LENGTH = FRAME_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH;
+
+	/**
+	 * The maximum length of an encrypted and authenticated frame in bytes,
+	 * including the header.
+	 */
 	int MAX_FRAME_LENGTH = 1024;
 
 	/** The maximum total length of the frame payload and padding in bytes. */
diff --git a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
index 97350169935db0e25b560f5ea9c79c27ce7bcef9..fc9b4bdb8a1df2434b37f4cb64a943705cfc5ba8 100644
--- a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
+++ b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
@@ -17,12 +17,10 @@ import org.briarproject.api.transport.TransportKeys;
 import org.briarproject.util.ByteUtils;
 import org.briarproject.util.StringUtils;
 import org.spongycastle.crypto.AsymmetricCipherKeyPair;
-import org.spongycastle.crypto.BlockCipher;
 import org.spongycastle.crypto.CipherParameters;
 import org.spongycastle.crypto.Digest;
 import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
 import org.spongycastle.crypto.digests.SHA256Digest;
-import org.spongycastle.crypto.engines.AESLightEngine;
 import org.spongycastle.crypto.generators.ECKeyPairGenerator;
 import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
 import org.spongycastle.crypto.params.ECKeyGenerationParameters;
@@ -30,6 +28,7 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
 import org.spongycastle.crypto.params.ECPublicKeyParameters;
 import org.spongycastle.crypto.params.KeyParameter;
 
+import java.nio.charset.Charset;
 import java.security.GeneralSecurityException;
 import java.security.SecureRandom;
 import java.util.ArrayList;
@@ -43,6 +42,8 @@ import static java.util.logging.Level.INFO;
 import static org.briarproject.api.invitation.InvitationConstants.CODE_BITS;
 import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS;
+import static org.briarproject.util.ByteUtils.INT_32_BYTES;
+import static org.briarproject.util.ByteUtils.INT_64_BYTES;
 import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 class CryptoComponentImpl implements CryptoComponent {
@@ -57,33 +58,31 @@ class CryptoComponentImpl implements CryptoComponent {
 	private static final int PBKDF_TARGET_MILLIS = 500;
 	private static final int PBKDF_SAMPLES = 30;
 
+	private static byte[] ascii(String s) {
+		return s.getBytes(Charset.forName("US-ASCII"));
+	}
+
 	// KDF label for master key derivation
-	private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R' };
+	private static final byte[] MASTER = ascii("MASTER");
 	// KDF labels for confirmation code derivation
-	private static final byte[] A_CONFIRM =
-			{ 'A', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' };
-	private static final byte[] B_CONFIRM =
-			{ 'B', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' };
+	private static final byte[] A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
+	private static final byte[] B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
 	// KDF labels for invitation stream header key derivation
-	private static final byte[] A_INVITE =
-			{ 'A', '_', 'I', 'N', 'V', 'I', 'T', 'E' };
-	private static final byte[] B_INVITE =
-			{ 'B', '_', 'I', 'N', 'V', 'I', 'T', 'E' };
+	private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
+	private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
 	// KDF labels for signature nonce derivation
-	private static final byte[] A_NONCE = { 'A', '_', 'N', 'O', 'N', 'C', 'E' };
-	private static final byte[] B_NONCE = { 'B', '_', 'N', 'O', 'N', 'C', 'E' };
+	private static final byte[] A_NONCE = ascii("ALICE_SIGNATURE_NONCE");
+	private static final byte[] B_NONCE = ascii("BOB_SIGNATURE_NONCE");
 	// KDF label for group salt derivation
-	private static final byte[] SALT = { 'S', 'A', 'L', 'T' };
+	private static final byte[] SALT = ascii("SALT");
 	// KDF labels for tag key derivation
-	private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G' };
-	private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G' };
+	private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
+	private static final byte[] B_TAG = ascii("BOB_TAG_KEY");
 	// KDF labels for header key derivation
-	private static final byte[] A_HEADER =
-			{ 'A', '_', 'H', 'E', 'A', 'D', 'E', 'R' };
-	private static final byte[] B_HEADER =
-			{ 'B', '_', 'H', 'E', 'A', 'D', 'E', 'R' };
+	private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
+	private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
 	// KDF label for key rotation
-	private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E' };
+	private static final byte[] ROTATE = ascii("ROTATE");
 
 	private final SecureRandom secureRandom;
 	private final ECKeyPairGenerator agreementKeyPairGenerator;
@@ -290,8 +289,8 @@ class CryptoComponentImpl implements CryptoComponent {
 	}
 
 	private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
-		byte[] period = new byte[4];
-		ByteUtils.writeUint32(rotationPeriod, period, 0);
+		byte[] period = new byte[INT_64_BYTES];
+		ByteUtils.writeUint64(rotationPeriod, period, 0);
 		return new SecretKey(macKdf(k, ROTATE, period));
 	}
 
@@ -311,14 +310,19 @@ class CryptoComponentImpl implements CryptoComponent {
 		if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
 		if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
 			throw new IllegalArgumentException();
-		for (int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
-		ByteUtils.writeUint32(streamNumber, tag, 0);
-		BlockCipher cipher = new AESLightEngine();
-		if (cipher.getBlockSize() != TAG_LENGTH)
-			throw new IllegalStateException();
-		KeyParameter k = new KeyParameter(tagKey.getBytes());
-		cipher.init(true, k);
-		cipher.processBlock(tag, 0, tag, 0);
+		// Initialise the PRF
+		Digest prf = new Blake2sDigest(tagKey.getBytes());
+		// 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);
+		byte[] mac = new byte[macLength];
+		prf.doFinal(mac, 0);
+		// The output is the first TAG_LENGTH bytes of the MAC
+		System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
 	}
 
 	public byte[] encryptWithPassword(byte[] input, String password) {
@@ -335,15 +339,16 @@ class CryptoComponentImpl implements CryptoComponent {
 		byte[] iv = new byte[STORAGE_IV_BYTES];
 		secureRandom.nextBytes(iv);
 		// The output contains the salt, iterations, IV, ciphertext and MAC
-		int outputLen = salt.length + 4 + iv.length + input.length + macBytes;
+		int outputLen = salt.length + INT_32_BYTES + iv.length + input.length
+				+ macBytes;
 		byte[] output = new byte[outputLen];
 		System.arraycopy(salt, 0, output, 0, salt.length);
 		ByteUtils.writeUint32(iterations, output, salt.length);
-		System.arraycopy(iv, 0, output, salt.length + 4, iv.length);
+		System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length);
 		// Initialise the cipher and encrypt the plaintext
 		try {
 			cipher.init(true, key, iv);
-			int outputOff = salt.length + 4 + iv.length;
+			int outputOff = salt.length + INT_32_BYTES + iv.length;
 			cipher.process(input, 0, input.length, output, outputOff);
 			return output;
 		} catch (GeneralSecurityException e) {
@@ -355,7 +360,8 @@ class CryptoComponentImpl implements CryptoComponent {
 		AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
 		int macBytes = cipher.getMacBytes();
 		// The input contains the salt, iterations, IV, ciphertext and MAC
-		if (input.length < PBKDF_SALT_BYTES + 4 + STORAGE_IV_BYTES + macBytes)
+		if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES
+				+ macBytes)
 			return null; // Invalid input
 		byte[] salt = new byte[PBKDF_SALT_BYTES];
 		System.arraycopy(input, 0, salt, 0, salt.length);
@@ -363,7 +369,7 @@ class CryptoComponentImpl implements CryptoComponent {
 		if (iterations < 0 || iterations > Integer.MAX_VALUE)
 			return null; // Invalid iteration count
 		byte[] iv = new byte[STORAGE_IV_BYTES];
-		System.arraycopy(input, salt.length + 4, iv, 0, iv.length);
+		System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length);
 		// Derive the key from the password
 		SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
 		// Initialise the cipher
@@ -374,7 +380,7 @@ class CryptoComponentImpl implements CryptoComponent {
 		}
 		// Try to decrypt the ciphertext (may be invalid)
 		try {
-			int inputOff = salt.length + 4 + iv.length;
+			int inputOff = salt.length + INT_32_BYTES + iv.length;
 			int inputLen = input.length - inputOff;
 			byte[] output = new byte[inputLen - macBytes];
 			cipher.process(input, inputOff, inputLen, output, 0);
@@ -392,7 +398,7 @@ class CryptoComponentImpl implements CryptoComponent {
 		int hashLength = digest.getDigestSize();
 		if (hashLength < SecretKey.LENGTH) throw new IllegalStateException();
 		// Calculate the hash over the concatenated length-prefixed inputs
-		byte[] length = new byte[4];
+		byte[] length = new byte[INT_32_BYTES];
 		for (byte[] input : inputs) {
 			ByteUtils.writeUint32(input.length, length, 0);
 			digest.update(length, 0, length.length);
@@ -416,7 +422,7 @@ class CryptoComponentImpl implements CryptoComponent {
 		int macLength = prf.getDigestSize();
 		if (macLength < SecretKey.LENGTH) throw new IllegalStateException();
 		// Calculate the PRF over the concatenated length-prefixed inputs
-		byte[] length = new byte[4];
+		byte[] length = new byte[INT_32_BYTES];
 		for (byte[] input : inputs) {
 			ByteUtils.writeUint32(input.length, length, 0);
 			prf.update(length, 0, length.length);
diff --git a/briar-core/src/org/briarproject/crypto/FrameEncoder.java b/briar-core/src/org/briarproject/crypto/FrameEncoder.java
index efd03ed49bad9c530eb7851571f78fae0f5509a0..68219c7a66ca941c44871ef0832eb3a9b5e10a5b 100644
--- a/briar-core/src/org/briarproject/crypto/FrameEncoder.java
+++ b/briar-core/src/org/briarproject/crypto/FrameEncoder.java
@@ -2,51 +2,51 @@ package org.briarproject.crypto;
 
 import org.briarproject.util.ByteUtils;
 
-import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_PLAINTEXT_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.FRAME_NONCE_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
-import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
+import static org.briarproject.util.ByteUtils.INT_16_BYTES;
+import static org.briarproject.util.ByteUtils.INT_64_BYTES;
 
 class FrameEncoder {
 
-	static void encodeIv(byte[] iv, long frameNumber, boolean header) {
-		if (iv.length < FRAME_IV_LENGTH) throw new IllegalArgumentException();
-		if (frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
+	static void encodeNonce(byte[] dest, long frameNumber, boolean header) {
+		if (dest.length < FRAME_NONCE_LENGTH)
 			throw new IllegalArgumentException();
-		ByteUtils.writeUint32(frameNumber, iv, 0);
-		if (header) iv[4] = 1;
-		else iv[4] = 0;
-		for (int i = 5; i < FRAME_IV_LENGTH; i++) iv[i] = 0;
+		if (frameNumber < 0) throw new IllegalArgumentException();
+		ByteUtils.writeUint64(frameNumber, dest, 0);
+		if (header) dest[0] |= 0x80;
+		for (int i = INT_64_BYTES; i < FRAME_NONCE_LENGTH; i++) dest[i] = 0;
 	}
 
-	static void encodeHeader(byte[] header, boolean finalFrame,
+	static void encodeHeader(byte[] dest, boolean finalFrame,
 			int payloadLength, int paddingLength) {
-		if (header.length < FRAME_HEADER_LENGTH)
+		if (dest.length < FRAME_HEADER_PLAINTEXT_LENGTH)
 			throw new IllegalArgumentException();
 		if (payloadLength < 0) throw new IllegalArgumentException();
 		if (paddingLength < 0) throw new IllegalArgumentException();
 		if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
 			throw new IllegalArgumentException();
-		ByteUtils.writeUint16(payloadLength, header, 0);
-		ByteUtils.writeUint16(paddingLength, header, 2);
-		if (finalFrame) header[0] |= 0x80;
+		ByteUtils.writeUint16(payloadLength, dest, 0);
+		ByteUtils.writeUint16(paddingLength, dest, INT_16_BYTES);
+		if (finalFrame) dest[0] |= 0x80;
 	}
 
 	static boolean isFinalFrame(byte[] header) {
-		if (header.length < FRAME_HEADER_LENGTH)
+		if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH)
 			throw new IllegalArgumentException();
 		return (header[0] & 0x80) == 0x80;
 	}
 
 	static int getPayloadLength(byte[] header) {
-		if (header.length < FRAME_HEADER_LENGTH)
+		if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH)
 			throw new IllegalArgumentException();
 		return ByteUtils.readUint16(header, 0) & 0x7FFF;
 	}
 
 	static int getPaddingLength(byte[] header) {
-		if (header.length < FRAME_HEADER_LENGTH)
+		if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH)
 			throw new IllegalArgumentException();
-		return ByteUtils.readUint16(header, 2);
+		return ByteUtils.readUint16(header, INT_16_BYTES);
 	}
 }
diff --git a/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java b/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java
index f89475e48227c7aa1957bbd23a6c8189cb249ac0..2786b2994fc7e8a1e52cb333bf4c12fb620fee31 100644
--- a/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java
+++ b/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java
@@ -3,14 +3,16 @@ package org.briarproject.crypto;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.util.ByteUtils;
 
+import static org.briarproject.util.ByteUtils.INT_32_BYTES;
+
 class PseudoRandomImpl implements PseudoRandom {
 
 	private final FortunaGenerator generator;
 
 	PseudoRandomImpl(int seed1, int seed2) {
-		byte[] seed = new byte[8];
+		byte[] seed = new byte[INT_32_BYTES * 2];
 		ByteUtils.writeUint32(seed1, seed, 0);
-		ByteUtils.writeUint32(seed2, seed, 4);
+		ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
 		generator = new FortunaGenerator(seed);
 	}
 
diff --git a/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java b/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java
index 89e18bee6119fea1b02809d6f582a9f2696d93e5..e7a1648de7b89b704a8cdc92fe3524b777a59203 100644
--- a/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java
+++ b/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java
@@ -10,7 +10,8 @@ import java.io.InputStream;
 import java.security.GeneralSecurityException;
 
 import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_PLAINTEXT_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.FRAME_NONCE_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
@@ -22,7 +23,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
 	private final InputStream in;
 	private final AuthenticatedCipher cipher;
 	private final SecretKey streamHeaderKey;
-	private final byte[] frameIv, frameHeader, frameCiphertext;
+	private final byte[] frameNonce, frameHeader, frameCiphertext;
 
 	private SecretKey frameKey;
 	private long frameNumber;
@@ -33,8 +34,8 @@ class StreamDecrypterImpl implements StreamDecrypter {
 		this.in = in;
 		this.cipher = cipher;
 		this.streamHeaderKey = streamHeaderKey;
-		frameIv = new byte[FRAME_IV_LENGTH];
-		frameHeader = new byte[FRAME_HEADER_LENGTH];
+		frameNonce = new byte[FRAME_NONCE_LENGTH];
+		frameHeader = new byte[FRAME_HEADER_PLAINTEXT_LENGTH];
 		frameCiphertext = new byte[MAX_FRAME_LENGTH];
 		frameKey = null;
 		frameNumber = 0;
@@ -46,6 +47,8 @@ class StreamDecrypterImpl implements StreamDecrypter {
 		if (payload.length < MAX_PAYLOAD_LENGTH)
 			throw new IllegalArgumentException();
 		if (finalFrame) return -1;
+		// Don't allow the frame counter to wrap
+		if (frameNumber < 0) throw new IOException();
 		// Read the stream header if required
 		if (frameKey == null) readStreamHeader();
 		// Read the frame header
@@ -57,12 +60,12 @@ class StreamDecrypterImpl implements StreamDecrypter {
 			offset += read;
 		}
 		// Decrypt and authenticate the frame header
-		FrameEncoder.encodeIv(frameIv, frameNumber, true);
+		FrameEncoder.encodeNonce(frameNonce, frameNumber, true);
 		try {
-			cipher.init(false, frameKey, frameIv);
+			cipher.init(false, frameKey, frameNonce);
 			int decrypted = cipher.process(frameCiphertext, 0,
 					FRAME_HEADER_LENGTH, frameHeader, 0);
-			if (decrypted != FRAME_HEADER_LENGTH - MAC_LENGTH)
+			if (decrypted != FRAME_HEADER_PLAINTEXT_LENGTH)
 				throw new RuntimeException();
 		} catch (GeneralSecurityException e) {
 			throw new FormatException();
@@ -82,9 +85,9 @@ class StreamDecrypterImpl implements StreamDecrypter {
 			offset += read;
 		}
 		// Decrypt and authenticate the payload and padding
-		FrameEncoder.encodeIv(frameIv, frameNumber, false);
+		FrameEncoder.encodeNonce(frameNonce, frameNumber, false);
 		try {
-			cipher.init(false, frameKey, frameIv);
+			cipher.init(false, frameKey, frameNonce);
 			int decrypted = cipher.process(frameCiphertext, FRAME_HEADER_LENGTH,
 					payloadLength + paddingLength + MAC_LENGTH, payload, 0);
 			if (decrypted != payloadLength + paddingLength)
diff --git a/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java b/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java
index 988be7e0293c9b91e0833af20fdff5543bbebdd8..6b19fa4cbafe13019ade615438a96bdf598474bc 100644
--- a/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java
+++ b/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java
@@ -8,13 +8,13 @@ import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 
 import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_PLAINTEXT_LENGTH;
+import static org.briarproject.api.transport.TransportConstants.FRAME_NONCE_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
-import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 
 class StreamEncrypterImpl implements StreamEncrypter {
 
@@ -22,7 +22,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
 	private final AuthenticatedCipher cipher;
 	private final SecretKey streamHeaderKey, frameKey;
 	private final byte[] tag, streamHeaderIv;
-	private final byte[] frameIv, framePlaintext, frameCiphertext;
+	private final byte[] frameNonce, frameHeader, framePlaintext, frameCiphertext;
 
 	private long frameNumber;
 	private boolean writeTag, writeStreamHeader;
@@ -36,8 +36,9 @@ class StreamEncrypterImpl implements StreamEncrypter {
 		this.streamHeaderIv = streamHeaderIv;
 		this.streamHeaderKey = streamHeaderKey;
 		this.frameKey = frameKey;
-		frameIv = new byte[FRAME_IV_LENGTH];
-		framePlaintext = new byte[FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
+		frameNonce = new byte[FRAME_NONCE_LENGTH];
+		frameHeader = new byte[FRAME_HEADER_PLAINTEXT_LENGTH];
+		framePlaintext = new byte[MAX_PAYLOAD_LENGTH];
 		frameCiphertext = new byte[MAX_FRAME_LENGTH];
 		frameNumber = 0;
 		writeTag = (tag != null);
@@ -49,34 +50,33 @@ class StreamEncrypterImpl implements StreamEncrypter {
 		if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
 			throw new IllegalArgumentException();
 		// Don't allow the frame counter to wrap
-		if (frameNumber > MAX_32_BIT_UNSIGNED) throw new IOException();
+		if (frameNumber < 0) throw new IOException();
 		// Write the tag if required
 		if (writeTag) writeTag();
 		// Write the stream header if required
 		if (writeStreamHeader) writeStreamHeader();
 		// Encode the frame header
-		FrameEncoder.encodeHeader(framePlaintext, finalFrame, payloadLength,
+		FrameEncoder.encodeHeader(frameHeader, finalFrame, payloadLength,
 				paddingLength);
 		// Encrypt and authenticate the frame header
-		FrameEncoder.encodeIv(frameIv, frameNumber, true);
+		FrameEncoder.encodeNonce(frameNonce, frameNumber, true);
 		try {
-			cipher.init(true, frameKey, frameIv);
-			int encrypted = cipher.process(framePlaintext, 0,
-					FRAME_HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0);
+			cipher.init(true, frameKey, frameNonce);
+			int encrypted = cipher.process(frameHeader, 0,
+					FRAME_HEADER_PLAINTEXT_LENGTH, frameCiphertext, 0);
 			if (encrypted != FRAME_HEADER_LENGTH) throw new RuntimeException();
 		} catch (GeneralSecurityException badCipher) {
 			throw new RuntimeException(badCipher);
 		}
 		// Combine the payload and padding
-		System.arraycopy(payload, 0, framePlaintext, FRAME_HEADER_LENGTH,
-				payloadLength);
+		System.arraycopy(payload, 0, framePlaintext, 0, payloadLength);
 		for (int i = 0; i < paddingLength; i++)
-			framePlaintext[FRAME_HEADER_LENGTH + payloadLength + i] = 0;
+			framePlaintext[payloadLength + i] = 0;
 		// Encrypt and authenticate the payload and padding
-		FrameEncoder.encodeIv(frameIv, frameNumber, false);
+		FrameEncoder.encodeNonce(frameNonce, frameNumber, false);
 		try {
-			cipher.init(true, frameKey, frameIv);
-			int encrypted = cipher.process(framePlaintext, FRAME_HEADER_LENGTH,
+			cipher.init(true, frameKey, frameNonce);
+			int encrypted = cipher.process(framePlaintext, 0,
 					payloadLength + paddingLength, frameCiphertext,
 					FRAME_HEADER_LENGTH);
 			if (encrypted != payloadLength + paddingLength + MAC_LENGTH)
diff --git a/briar-core/src/org/briarproject/util/ByteUtils.java b/briar-core/src/org/briarproject/util/ByteUtils.java
index 777d8231899011ac506db8052e22617a7d4dfcae..9213e65d8931072c3a30af52935bf1f330c2f1fa 100644
--- a/briar-core/src/org/briarproject/util/ByteUtils.java
+++ b/briar-core/src/org/briarproject/util/ByteUtils.java
@@ -12,50 +12,72 @@ public class ByteUtils {
 	 */
 	public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
 
-	public static void writeUint8(int i, byte[] b, int offset) {
-		if (i < 0) throw new IllegalArgumentException();
-		if (i > 255) throw new IllegalArgumentException();
-		if (b.length < offset) throw new IllegalArgumentException();
-		b[offset] = (byte) i;
+	/** The number of bytes needed to encode a 16-bit integer. */
+	public static final int INT_16_BYTES = 2;
+
+	/** The number of bytes needed to encode a 32-bit integer. */
+	public static final int INT_32_BYTES = 4;
+
+	/** The number of bytes needed to encode a 64-bit integer. */
+	public static final int INT_64_BYTES = 8;
+
+	public static void writeUint16(int src, byte[] dest, int offset) {
+		if (src < 0) throw new IllegalArgumentException();
+		if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException();
+		if (dest.length < offset + INT_16_BYTES)
+			throw new IllegalArgumentException();
+		dest[offset] = (byte) (src >> 8);
+		dest[offset + 1] = (byte) (src & 0xFF);
 	}
 
-	public static void writeUint16(int i, byte[] b, int offset) {
-		if (i < 0) throw new IllegalArgumentException();
-		if (i > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException();
-		if (b.length < offset + 2) throw new IllegalArgumentException();
-		b[offset] = (byte) (i >> 8);
-		b[offset + 1] = (byte) (i & 0xFF);
+	public static void writeUint32(long src, byte[] dest, int offset) {
+		if (src < 0) throw new IllegalArgumentException();
+		if (src > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException();
+		if (dest.length < offset + INT_32_BYTES)
+			throw new IllegalArgumentException();
+		dest[offset] = (byte) (src >> 24);
+		dest[offset + 1] = (byte) (src >> 16 & 0xFF);
+		dest[offset + 2] = (byte) (src >> 8 & 0xFF);
+		dest[offset + 3] = (byte) (src & 0xFF);
 	}
 
-	public static void writeUint32(long i, byte[] b, int offset) {
-		if (i < 0) throw new IllegalArgumentException();
-		if (i > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException();
-		if (b.length < offset + 4) throw new IllegalArgumentException();
-		b[offset] = (byte) (i >> 24);
-		b[offset + 1] = (byte) (i >> 16 & 0xFF);
-		b[offset + 2] = (byte) (i >> 8 & 0xFF);
-		b[offset + 3] = (byte) (i & 0xFF);
+	public static void writeUint64(long src, byte[] dest, int offset) {
+		if (src < 0) throw new IllegalArgumentException();
+		if (dest.length < offset + INT_64_BYTES)
+			throw new IllegalArgumentException();
+		dest[offset] = (byte) (src >> 56);
+		dest[offset + 1] = (byte) (src >> 48 & 0xFF);
+		dest[offset + 2] = (byte) (src >> 40 & 0xFF);
+		dest[offset + 3] = (byte) (src >> 32 & 0xFF);
+		dest[offset + 4] = (byte) (src >> 24 & 0xFF);
+		dest[offset + 5] = (byte) (src >> 16 & 0xFF);
+		dest[offset + 6] = (byte) (src >> 8 & 0xFF);
+		dest[offset + 7] = (byte) (src & 0xFF);
 	}
 
-	public static int readUint16(byte[] b, int offset) {
-		if (b.length < offset + 2) throw new IllegalArgumentException();
-		return ((b[offset] & 0xFF) << 8) | (b[offset + 1] & 0xFF);
+	public static int readUint16(byte[] src, int offset) {
+		if (src.length < offset + INT_16_BYTES)
+			throw new IllegalArgumentException();
+		return ((src[offset] & 0xFF) << 8) | (src[offset + 1] & 0xFF);
 	}
 
-	public static long readUint32(byte[] b, int offset) {
-		if (b.length < offset + 4) throw new IllegalArgumentException();
-		return ((b[offset] & 0xFFL) << 24) | ((b[offset + 1] & 0xFFL) << 16)
-				| ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL);
+	public static long readUint32(byte[] src, int offset) {
+		if (src.length < offset + INT_32_BYTES)
+			throw new IllegalArgumentException();
+		return ((src[offset] & 0xFFL) << 24)
+				| ((src[offset + 1] & 0xFFL) << 16)
+				| ((src[offset + 2] & 0xFFL) << 8)
+				| (src[offset + 3] & 0xFFL);
 	}
 
-	public static int readUint(byte[] b, int bits) {
-		if (b.length << 3 < bits) throw new IllegalArgumentException();
-		int result = 0;
+	public static int readUint(byte[] src, int bits) {
+		if (src.length << 3 < bits) throw new IllegalArgumentException();
+		int dest = 0;
 		for (int i = 0; i < bits; i++) {
-			if ((b[i >> 3] & 128 >> (i & 7)) != 0) result |= 1 << bits - i - 1;
+			if ((src[i >> 3] & 128 >> (i & 7)) != 0) dest |= 1 << bits - i - 1;
 		}
-		assert result >= 0;
-		assert result < 1 << bits;
-		return result;
+		assert dest >= 0;
+		assert dest < 1 << bits;
+		return dest;
 	}
 }
diff --git a/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java b/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java
index 4ba24e7ea48f9036dd73982eedd9b332d3dabfbf..152ba5818f05afdea0b7b279fa02dbb1838f6696 100644
--- a/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java
+++ b/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java
@@ -16,6 +16,7 @@ import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LEN
 import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
+import static org.briarproject.util.ByteUtils.INT_16_BYTES;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.fail;
 
@@ -119,7 +120,7 @@ public class StreamDecrypterImplTest extends BriarTestCase {
 		// The payload length plus padding length is invalid
 		int payloadLength = MAX_PAYLOAD_LENGTH - 1, paddingLength = 2;
 		ByteUtils.writeUint16(payloadLength, frameHeader, 0);
-		ByteUtils.writeUint16(paddingLength, frameHeader, 2);
+		ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES);
 		byte[] payload = new byte[payloadLength];
 		random.nextBytes(payload);
 
diff --git a/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java b/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java
index f95e46740914903240e89f439acaa78869880a5b..985b000a35e9d71a544da2743002ef57d9894ce6 100644
--- a/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java
+++ b/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java
@@ -11,6 +11,7 @@ import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LEN
 import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
+import static org.briarproject.util.ByteUtils.INT_16_BYTES;
 
 class TestStreamDecrypter implements StreamDecrypter {
 
@@ -35,7 +36,7 @@ class TestStreamDecrypter implements StreamDecrypter {
 		}
 		finalFrame = (frame[0] & 0x80) == 0x80;
 		int payloadLength = ByteUtils.readUint16(frame, 0) & 0x7FFF;
-		int paddingLength = ByteUtils.readUint16(frame, 2);
+		int paddingLength = ByteUtils.readUint16(frame, INT_16_BYTES);
 		int frameLength = FRAME_HEADER_LENGTH + payloadLength + paddingLength
 				+ MAC_LENGTH;
 		while (offset < frameLength) {
diff --git a/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java b/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java
index 2315acfa94ab20f5ba17caa6950eaf47fcf18ac2..5dbf9d3eeeb3ece17e53de8ee9ea5a592df36e94 100644
--- a/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java
+++ b/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java
@@ -9,6 +9,7 @@ import java.io.OutputStream;
 import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
+import static org.briarproject.util.ByteUtils.INT_16_BYTES;
 
 class TestStreamEncrypter implements StreamEncrypter {
 
@@ -27,7 +28,7 @@ class TestStreamEncrypter implements StreamEncrypter {
 		if (writeTagAndHeader) writeTagAndHeader();
 		byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
 		ByteUtils.writeUint16(payloadLength, frameHeader, 0);
-		ByteUtils.writeUint16(paddingLength, frameHeader, 2);
+		ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES);
 		if (finalFrame) frameHeader[0] |= 0x80;
 		out.write(frameHeader);
 		out.write(payload, 0, payloadLength);
diff --git a/briar-tests/src/org/briarproject/util/ByteUtilsTest.java b/briar-tests/src/org/briarproject/util/ByteUtilsTest.java
index 0df6548bd6c60dff8304b0cacd1748c77a5a9a07..fb9952e392e094a12d2654b755d81cf7f814c0ff 100644
--- a/briar-tests/src/org/briarproject/util/ByteUtilsTest.java
+++ b/briar-tests/src/org/briarproject/util/ByteUtilsTest.java
@@ -3,7 +3,10 @@ package org.briarproject.util;
 import org.briarproject.BriarTestCase;
 import org.junit.Test;
 
+import static org.briarproject.util.ByteUtils.MAX_16_BIT_UNSIGNED;
+import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 public class ByteUtilsTest extends BriarTestCase {
 
@@ -17,6 +20,22 @@ public class ByteUtilsTest extends BriarTestCase {
 		assertEquals(65535, ByteUtils.readUint16(b, 1));
 	}
 
+	@Test
+	public void testReadUint16ValidatesArguments() {
+		try {
+			ByteUtils.readUint16(new byte[1], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.readUint16(new byte[2], 1);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+	}
+
 	@Test
 	public void testReadUint32() {
 		byte[] b = StringUtils.fromHexString("0000000000");
@@ -27,27 +46,135 @@ public class ByteUtilsTest extends BriarTestCase {
 		assertEquals(4294967295L, ByteUtils.readUint32(b, 1));
 	}
 
+	@Test
+	public void testReadUint32ValidatesArguments() {
+		try {
+			ByteUtils.readUint32(new byte[3], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.readUint32(new byte[4], 1);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+	}
 
 	@Test
 	public void testWriteUint16() {
-		byte[] b = new byte[3];
+		byte[] b = new byte[4];
 		ByteUtils.writeUint16(0, b, 1);
-		assertEquals("000000", StringUtils.toHexString(b));
+		assertEquals("00000000", StringUtils.toHexString(b));
 		ByteUtils.writeUint16(1, b, 1);
-		assertEquals("000001", StringUtils.toHexString(b));
-		ByteUtils.writeUint16(65535, b, 1);
-		assertEquals("00FFFF", StringUtils.toHexString(b));
+		assertEquals("00000100", StringUtils.toHexString(b));
+		ByteUtils.writeUint16(Short.MAX_VALUE, b, 1);
+		assertEquals("007FFF00", StringUtils.toHexString(b));
+		ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED, b, 1);
+		assertEquals("00FFFF00", StringUtils.toHexString(b));
+	}
+
+	@Test
+	public void testWriteUint16ValidatesArguments() {
+		try {
+			ByteUtils.writeUint16(0, new byte[1], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint16(0, new byte[2], 1);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint16(-1, new byte[2], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
 	}
 
 	@Test
 	public void testWriteUint32() {
-		byte[] b = new byte[5];
+		byte[] b = new byte[6];
 		ByteUtils.writeUint32(0, b, 1);
-		assertEquals("0000000000", StringUtils.toHexString(b));
+		assertEquals("000000000000", StringUtils.toHexString(b));
 		ByteUtils.writeUint32(1, b, 1);
-		assertEquals("0000000001", StringUtils.toHexString(b));
-		ByteUtils.writeUint32(4294967295L, b, 1);
-		assertEquals("00FFFFFFFF", StringUtils.toHexString(b));
+		assertEquals("000000000100", StringUtils.toHexString(b));
+		ByteUtils.writeUint32(Integer.MAX_VALUE, b, 1);
+		assertEquals("007FFFFFFF00", StringUtils.toHexString(b));
+		ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED, b, 1);
+		assertEquals("00FFFFFFFF00", StringUtils.toHexString(b));
+	}
+
+	@Test
+	public void testWriteUint32ValidatesArguments() {
+		try {
+			ByteUtils.writeUint32(0, new byte[3], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint32(0, new byte[4], 1);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint32(-1, new byte[4], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+	}
+
+	@Test
+	public void testWriteUint64() {
+		byte[] b = new byte[10];
+		ByteUtils.writeUint64(0, b, 1);
+		assertEquals("00000000000000000000", StringUtils.toHexString(b));
+		ByteUtils.writeUint64(1, b, 1);
+		assertEquals("00000000000000000100", StringUtils.toHexString(b));
+		ByteUtils.writeUint64(Long.MAX_VALUE, b, 1);
+		assertEquals("007FFFFFFFFFFFFFFF00", StringUtils.toHexString(b));
+	}
+
+	@Test
+	public void testWriteUint64ValidatesArguments() {
+		try {
+			ByteUtils.writeUint64(0, new byte[7], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint64(0, new byte[8], 1);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
+		try {
+			ByteUtils.writeUint64(-1, new byte[8], 0);
+			fail();
+		} catch (IllegalArgumentException expected) {
+			// Expected
+		}
 	}
 
 	@Test