mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Merge branch '147-crypto_secretbox' into 'master'
147 crypto secretbox Closes #147 See merge request !27
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
125
briar-core/src/org/briarproject/crypto/XSalsa20Poly1305AC.java
Normal file
125
briar-core/src/org/briarproject/crypto/XSalsa20Poly1305AC.java
Normal file
@@ -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.
|
||||
* <p/>
|
||||
* References:
|
||||
* <ul>
|
||||
* <li>http://nacl.cr.yp.to/secretbox.html</li>
|
||||
* <li>http://cr.yp.to/highspeed/naclcrypto-20090310.pdf</li>
|
||||
* </ul>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user