diff --git a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java
index 924afbde7c620767b6aa2342117cbf090e38e39c..1806f76d72b91dc9fda9d0ea71c9b72fe8d68995 100644
--- a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java
+++ b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java
@@ -143,6 +143,13 @@ public interface CryptoComponent {
 	 */
 	byte[] hash(byte[]... inputs);
 
+	/**
+	 * Returns a message authentication code with the given key over the
+	 * given inputs. The inputs are unambiguously combined by prefixing each
+	 * input with its length.
+	 */
+	byte[] mac(SecretKey macKey, byte[]... inputs);
+
 	/**
 	 * Encrypts and authenticates the given plaintext so it can be written to
 	 * storage. The encryption and authentication keys are derived from the
diff --git a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
index 459ee6b9280eee3e976e42d2e31f6d546ddad7ca..169bfb7818f9d8002cbeac905dc9451584a00d7b 100644
--- a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
+++ b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
@@ -403,6 +403,20 @@ class CryptoComponentImpl implements CryptoComponent {
 		return digest.digest();
 	}
 
+	@Override
+	public byte[] mac(SecretKey macKey, byte[]... inputs) {
+		Digest mac = new Blake2sDigest(macKey.getBytes());
+		byte[] length = new byte[INT_32_BYTES];
+		for (byte[] input : inputs) {
+			ByteUtils.writeUint32(input.length, length, 0);
+			mac.update(length, 0, length.length);
+			mac.update(input, 0, input.length);
+		}
+		byte[] output = new byte[mac.getDigestSize()];
+		mac.doFinal(output, 0);
+		return output;
+	}
+
 	@Override
 	public byte[] encryptWithPassword(byte[] input, String password) {
 		AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
diff --git a/briar-tests/src/org/briarproject/crypto/MacTest.java b/briar-tests/src/org/briarproject/crypto/MacTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..336d90952ca4c5dd990a72dddcb67862b902e630
--- /dev/null
+++ b/briar-tests/src/org/briarproject/crypto/MacTest.java
@@ -0,0 +1,67 @@
+package org.briarproject.crypto;
+
+import org.briarproject.BriarTestCase;
+import org.briarproject.TestSeedProvider;
+import org.briarproject.TestUtils;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.SecretKey;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+
+public class MacTest extends BriarTestCase {
+
+	private final CryptoComponent crypto;
+
+	public MacTest() {
+		crypto = new CryptoComponentImpl(new TestSeedProvider());
+	}
+
+	@Test
+	public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
+		// Generate a random key and some random input
+		byte[] keyBytes = TestUtils.getRandomBytes(SecretKey.LENGTH);
+		SecretKey k = new SecretKey(keyBytes);
+		byte[] inputBytes = TestUtils.getRandomBytes(123);
+		byte[] inputBytes1 = TestUtils.getRandomBytes(234);
+		byte[] inputBytes2 = new byte[0];
+		// Calculate the MAC twice - the results should be identical
+		byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
+		byte[] mac1 = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
+		assertArrayEquals(mac, mac1);
+	}
+
+	@Test
+	public void testDifferentKeysProduceDifferentMacs() {
+		// Generate two random keys and some random input
+		byte[] keyBytes = TestUtils.getRandomBytes(SecretKey.LENGTH);
+		SecretKey k = new SecretKey(keyBytes);
+		byte[] keyBytes1 = TestUtils.getRandomBytes(SecretKey.LENGTH);
+		SecretKey k1 = new SecretKey(keyBytes1);
+		byte[] inputBytes = TestUtils.getRandomBytes(123);
+		byte[] inputBytes1 = TestUtils.getRandomBytes(234);
+		byte[] inputBytes2 = new byte[0];
+		// Calculate the MAC with each key - the results should be different
+		byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
+		byte[] mac1 = crypto.mac(k1, inputBytes, inputBytes1, inputBytes2);
+		assertFalse(Arrays.equals(mac, mac1));
+	}
+
+	@Test
+	public void testDifferentInputsProduceDifferentMacs() {
+		// Generate a random key and some random input
+		byte[] keyBytes = TestUtils.getRandomBytes(SecretKey.LENGTH);
+		SecretKey k = new SecretKey(keyBytes);
+		byte[] inputBytes = TestUtils.getRandomBytes(123);
+		byte[] inputBytes1 = TestUtils.getRandomBytes(234);
+		byte[] inputBytes2 = new byte[0];
+		// Calculate the MAC with the inputs in different orders - the results
+		// should be different
+		byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
+		byte[] mac1 = crypto.mac(k, inputBytes2, inputBytes1, inputBytes);
+		assertFalse(Arrays.equals(mac, mac1));
+	}
+}