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)); + } +}