From 20b2bcb86f730b10a345587eb9a0d6a2a2a7fd18 Mon Sep 17 00:00:00 2001 From: str4d Date: Tue, 15 Dec 2015 13:25:43 +0000 Subject: [PATCH 1/3] Expand JavaDocs for AuthenticatedCipher --- .../crypto/AuthenticatedCipher.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/briar-core/src/org/briarproject/crypto/AuthenticatedCipher.java b/briar-core/src/org/briarproject/crypto/AuthenticatedCipher.java index 3225b65e4..5ee21e3e4 100644 --- a/briar-core/src/org/briarproject/crypto/AuthenticatedCipher.java +++ b/briar-core/src/org/briarproject/crypto/AuthenticatedCipher.java @@ -1,19 +1,41 @@ package org.briarproject.crypto; -import java.security.GeneralSecurityException; - import org.briarproject.api.crypto.SecretKey; +import java.security.GeneralSecurityException; + interface AuthenticatedCipher { /** * Initializes this cipher for encryption or decryption with a key and an * initialisation vector (IV). + * + * @param encrypt whether we are encrypting or decrypting. + * @param key the key material to use. + * @param iv the IV. + * @throws GeneralSecurityException on invalid input. */ void init(boolean encrypt, SecretKey key, byte[] iv) throws GeneralSecurityException; - /** Encrypts or decrypts data in a single-part operation. */ + /** + * Encrypts or decrypts data in a single-part operation. + * + * @param input the input byte array. If encrypting, the plaintext to be + * encrypted. If decrypting, the ciphertext to be decrypted + * including the MAC. + * @param inputOff the offset into the input array where the data to be + * processed starts. + * @param len the number of bytes to be processed. If decrypting, includes + * the MAC length. + * @param output the output buffer the processed bytes go into. If + * encrypting, the ciphertext including the MAC. If + * decrypting, the plaintext. + * @param outputOff the offset into the output byte array the processed + * data starts at. + * @return the number of bytes processed. + * @throws GeneralSecurityException on invalid input. + */ int process(byte[] input, int inputOff, int len, byte[] output, int outputOff) throws GeneralSecurityException; From 3f54657ca029ebf18514bf71f4583e3c5f493a84 Mon Sep 17 00:00:00 2001 From: str4d Date: Tue, 15 Dec 2015 14:19:27 +0000 Subject: [PATCH 2/3] Simple test vectors for XSalsa20/Poly1305 Test vectors taken from NaCl paper. --- .../crypto/XSalsa20Poly1305ACTest.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 briar-tests/src/org/briarproject/crypto/XSalsa20Poly1305ACTest.java diff --git a/briar-tests/src/org/briarproject/crypto/XSalsa20Poly1305ACTest.java b/briar-tests/src/org/briarproject/crypto/XSalsa20Poly1305ACTest.java new file mode 100644 index 000000000..723d7984a --- /dev/null +++ b/briar-tests/src/org/briarproject/crypto/XSalsa20Poly1305ACTest.java @@ -0,0 +1,89 @@ +package org.briarproject.crypto; + +import org.briarproject.BriarTestCase; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.util.StringUtils; +import org.junit.Test; + +import java.security.GeneralSecurityException; + +import static org.junit.Assert.assertArrayEquals; + +public class XSalsa20Poly1305ACTest extends BriarTestCase { + + // Test vectors from the NaCl paper + // http://cr.yp.to/highspeed/naclcrypto-20090310.pdf + private static final byte[] TEST_KEY = StringUtils.fromHexString( + "1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389"); + private static final byte[] TEST_IV = StringUtils.fromHexString( + "69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37"); + private static final byte[] TEST_PLAINTEXT = StringUtils.fromHexString( + "be075fc53c81f2d5cf141316" + + "ebeb0c7b5228c52a4c62cbd4" + + "4b66849b64244ffce5ecbaaf" + + "33bd751a1ac728d45e6c6129" + + "6cdc3c01233561f41db66cce" + + "314adb310e3be8250c46f06d" + + "ceea3a7fa1348057e2f6556a" + + "d6b1318a024a838f21af1fde" + + "048977eb48f59ffd4924ca1c" + + "60902e52f0a089bc76897040" + + "e082f937763848645e0705"); + private static final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString( + "f3ffc7703f9400e52a7dfb4b" + + "3d3305d98e993b9f48681273" + + "c29650ba32fc76ce48332ea7" + + "164d96a4476fb8c531a1186a" + + "c0dfc17c98dce87b4da7f011" + + "ec48c97271d2c20f9b928fe2" + + "270d6fb863d51738b48eeee3" + + "14a7cc8ab932164548e526ae" + + "90224368517acfeabd6bb373" + + "2bc0e9da99832b61ca01b6de" + + "56244a9e88d5f9b37973f622" + + "a43d14a6599b1f654cb45a74" + + "e355a5"); + + @Test + public void testEncrypt() throws Exception { + SecretKey k = new SecretKey(TEST_KEY); + AuthenticatedCipher cipher = new XSalsa20Poly1305AC(); + cipher.init(true, k, TEST_IV); + byte[] output = new byte[TEST_PLAINTEXT.length + cipher.getMacBytes()]; + cipher.process(TEST_PLAINTEXT, 0, TEST_PLAINTEXT.length, output, 0); + assertArrayEquals(TEST_CIPHERTEXT, output); + } + + @Test + public void testDecrypt() throws Exception { + SecretKey k = new SecretKey(TEST_KEY); + AuthenticatedCipher cipher = new XSalsa20Poly1305AC(); + cipher.init(false, k, TEST_IV); + byte[] output = new byte[TEST_CIPHERTEXT.length - cipher.getMacBytes()]; + cipher.process(TEST_CIPHERTEXT, 0, TEST_CIPHERTEXT.length, output, 0); + assertArrayEquals(TEST_PLAINTEXT, output); + } + + @Test(expected = GeneralSecurityException.class) + public void testDecryptFailsWithShortInput() throws Exception { + SecretKey k = new SecretKey(TEST_KEY); + AuthenticatedCipher cipher = new XSalsa20Poly1305AC(); + cipher.init(false, k, TEST_IV); + byte[] input = new byte[8]; + System.arraycopy(TEST_CIPHERTEXT, 0, input, 0, 8); + byte[] output = new byte[TEST_CIPHERTEXT.length - cipher.getMacBytes()]; + cipher.process(input, 0, input.length, output, 0); + } + + @Test(expected = GeneralSecurityException.class) + public void testDecryptFailsWithAlteredCiphertext() throws Exception { + SecretKey k = new SecretKey(TEST_KEY); + AuthenticatedCipher cipher = new XSalsa20Poly1305AC(); + cipher.init(false, k, TEST_IV); + byte[] input = new byte[TEST_CIPHERTEXT.length]; + System.arraycopy(TEST_CIPHERTEXT, 0, input, 0, TEST_CIPHERTEXT.length); + input[TEST_CIPHERTEXT.length - cipher.getMacBytes()] = 42; + byte[] output = new byte[TEST_CIPHERTEXT.length - cipher.getMacBytes()]; + cipher.process(input, 0, input.length, output, 0); + } +} From d9808c48f04a1c4072e534351326532fae23e353 Mon Sep 17 00:00:00 2001 From: str4d Date: Tue, 15 Dec 2015 14:19:57 +0000 Subject: [PATCH 3/3] Implement XSalsa20/Poly1305 --- .../crypto/XSalsa20Poly1305AC.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 briar-core/src/org/briarproject/crypto/XSalsa20Poly1305AC.java diff --git a/briar-core/src/org/briarproject/crypto/XSalsa20Poly1305AC.java b/briar-core/src/org/briarproject/crypto/XSalsa20Poly1305AC.java new file mode 100644 index 000000000..7a4a39d90 --- /dev/null +++ b/briar-core/src/org/briarproject/crypto/XSalsa20Poly1305AC.java @@ -0,0 +1,125 @@ +package org.briarproject.crypto; + +import org.briarproject.api.crypto.SecretKey; +import org.spongycastle.crypto.DataLengthException; +import org.spongycastle.crypto.engines.XSalsa20Engine; +import org.spongycastle.crypto.generators.Poly1305KeyGenerator; +import org.spongycastle.crypto.macs.Poly1305; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; + +import java.security.GeneralSecurityException; + +import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; + +/** + * An authenticated cipher that uses XSalsa20 for encryption and Poly1305 for + * authentication. It is equivalent to the C++ implementation of + * crypto_secretbox in NaCl, and to the C implementations of crypto_secretbox + * in NaCl and libsodium once the zero-padding has been removed. + *

+ * References: + *

    + *
  • http://nacl.cr.yp.to/secretbox.html
  • + *
  • http://cr.yp.to/highspeed/naclcrypto-20090310.pdf
  • + *
+ */ +public class XSalsa20Poly1305AC implements AuthenticatedCipher { + + /** Length of the padding to be used to generate the Poly1305 key */ + private static final int SUBKEY_LENGTH = 32; + + private final XSalsa20Engine xSalsa20Engine; + private final Poly1305 poly1305; + + private boolean encrypting; + + XSalsa20Poly1305AC() { + xSalsa20Engine = new XSalsa20Engine(); + poly1305 = new Poly1305(); + } + + @Override + public void init(boolean encrypt, SecretKey key, byte[] iv) throws GeneralSecurityException { + encrypting = encrypt; + KeyParameter k = new KeyParameter(key.getBytes()); + ParametersWithIV params = new ParametersWithIV(k, iv); + try { + xSalsa20Engine.init(encrypt, params); + } catch (IllegalArgumentException e) { + throw new GeneralSecurityException(e.getMessage()); + } + } + + @Override + public int process(byte[] input, int inputOff, int len, byte[] output, int outputOff) throws GeneralSecurityException { + if (len == 0) + return 0; + else if (!encrypting && len < MAC_LENGTH) + throw new GeneralSecurityException("Invalid MAC"); + + try { + // Generate the Poly1305 subkey from an empty array + byte[] zero = new byte[SUBKEY_LENGTH]; + byte[] subKey = new byte[SUBKEY_LENGTH]; + xSalsa20Engine.processBytes(zero, 0, SUBKEY_LENGTH, subKey, 0); + + // Reverse the order of the Poly130 subkey + // + // NaCl and libsodium use the first 32 bytes of XSalsa20 as the + // subkey for crypto_onetimeauth_poly1305, which interprets it + // as r[0] ... r[15], k[0] ... k[15]. See section 9 of the NaCl + // paper (http://cr.yp.to/highspeed/naclcrypto-20090310.pdf), + // where the XSalsa20 output is defined as (r, s, t, ...). + // + // BC's Poly1305 implementation interprets the subkey as + // k[0] ... k[15], r[0] ... r[15] (per poly1305_aes_clamp in + // the reference implementation). + // + // To be NaCl-compatible, we reverse the subkey. + System.arraycopy(subKey, 0, zero, 0, SUBKEY_LENGTH / 2); + System.arraycopy(subKey, SUBKEY_LENGTH / 2, subKey, 0, SUBKEY_LENGTH / 2); + System.arraycopy(zero, 0, subKey, SUBKEY_LENGTH / 2, SUBKEY_LENGTH / 2); + // Now we can clamp the correct part of the subkey + Poly1305KeyGenerator.clamp(subKey); + + // Initialize Poly1305 with the subkey + KeyParameter k = new KeyParameter(subKey); + poly1305.init(k); + + // If we are decrypting, verify the MAC + if (!encrypting) { + byte[] mac = new byte[MAC_LENGTH]; + poly1305.update(input, inputOff + MAC_LENGTH, len - MAC_LENGTH); + poly1305.doFinal(mac, 0); + // Constant-time comparison + int cmp = 0; + for (int i = 0; i < MAC_LENGTH; i++) + cmp |= mac[i] ^ input[inputOff + i]; + if (cmp != 0) + throw new GeneralSecurityException("Invalid MAC"); + } + + // Invert the stream encryption + int processed = xSalsa20Engine.processBytes( + input, encrypting ? inputOff : inputOff + MAC_LENGTH, + encrypting ? len : len - MAC_LENGTH, + output, encrypting ? outputOff + MAC_LENGTH : outputOff); + + // If we are encrypting, generate the MAC + if (encrypting) { + poly1305.update(output, outputOff + MAC_LENGTH, len); + poly1305.doFinal(output, outputOff); + } + + return processed; + } catch (DataLengthException e) { + throw new GeneralSecurityException(e.getMessage()); + } + } + + @Override + public int getMacBytes() { + return MAC_LENGTH; + } +}