Use Curve25519 for key agreement.

This commit is contained in:
akwizgran
2018-02-02 17:52:18 +00:00
parent ad241a14e3
commit 56a5b8df87
11 changed files with 120 additions and 88 deletions

View File

@@ -5,7 +5,7 @@ public interface CryptoConstants {
/** /**
* The maximum length of an agreement public key in bytes. * The maximum length of an agreement public key in bytes.
*/ */
int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 65; int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 32;
/** /**
* The maximum length of a signature public key in bytes. * The maximum length of a signature public key in bytes.

View File

@@ -12,6 +12,7 @@ dependencies {
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6 implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.4.1'
apt 'com.google.dagger:dagger-compiler:2.0.2' apt 'com.google.dagger:dagger-compiler:2.0.2'
@@ -23,7 +24,6 @@ dependencies {
testImplementation "org.jmock:jmock-legacy:2.8.2" testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3" testImplementation "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3" testImplementation "org.hamcrest:hamcrest-core:1.3"
testImplementation "org.whispersystems:curve25519-java:0.4.1"
testApt 'com.google.dagger:dagger-compiler:2.0.2' testApt 'com.google.dagger:dagger-compiler:2.0.2'
} }

View File

@@ -14,15 +14,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.CryptoException; import org.spongycastle.crypto.CryptoException;
import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.Blake2bDigest; import org.spongycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.generators.ECKeyPairGenerator; import org.whispersystems.curve25519.Curve25519;
import org.spongycastle.crypto.params.ECKeyGenerationParameters; import org.whispersystems.curve25519.Curve25519KeyPair;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@@ -35,7 +31,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@NotNullByDefault @NotNullByDefault
@@ -44,7 +39,6 @@ class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(CryptoComponentImpl.class.getName()); Logger.getLogger(CryptoComponentImpl.class.getName());
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
private static final int SIGNATURE_KEY_PAIR_BITS = 256; private static final int SIGNATURE_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits private static final int STORAGE_IV_BYTES = 24; // 196 bits
private static final int PBKDF_SALT_BYTES = 32; // 256 bits private static final int PBKDF_SALT_BYTES = 32; // 256 bits
@@ -52,7 +46,7 @@ class CryptoComponentImpl implements CryptoComponent {
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final PasswordBasedKdf passwordBasedKdf; private final PasswordBasedKdf passwordBasedKdf;
private final ECKeyPairGenerator agreementKeyPairGenerator; private final Curve25519 curve25519;
private final KeyPairGenerator signatureKeyPairGenerator; private final KeyPairGenerator signatureKeyPairGenerator;
private final KeyParser agreementKeyParser, signatureKeyParser; private final KeyParser agreementKeyParser, signatureKeyParser;
private final MessageEncrypter messageEncrypter; private final MessageEncrypter messageEncrypter;
@@ -80,15 +74,11 @@ class CryptoComponentImpl implements CryptoComponent {
} }
secureRandom = new SecureRandom(); secureRandom = new SecureRandom();
this.passwordBasedKdf = passwordBasedKdf; this.passwordBasedKdf = passwordBasedKdf;
ECKeyGenerationParameters params = new ECKeyGenerationParameters( curve25519 = Curve25519.getInstance("java");
PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator();
agreementKeyPairGenerator.init(params);
signatureKeyPairGenerator = new KeyPairGenerator(); signatureKeyPairGenerator = new KeyPairGenerator();
signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS, signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
secureRandom); secureRandom);
agreementKeyParser = new Sec1KeyParser(PARAMETERS, agreementKeyParser = new Curve25519KeyParser();
AGREEMENT_KEY_PAIR_BITS);
signatureKeyParser = new EdKeyParser(); signatureKeyParser = new EdKeyParser();
messageEncrypter = new MessageEncrypter(secureRandom); messageEncrypter = new MessageEncrypter(secureRandom);
} }
@@ -133,16 +123,17 @@ class CryptoComponentImpl implements CryptoComponent {
// Package access for testing // Package access for testing
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub) byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException { throws GeneralSecurityException {
if (!(priv instanceof Sec1PrivateKey)) if (!(priv instanceof Curve25519PrivateKey))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (!(pub instanceof Sec1PublicKey)) if (!(pub instanceof Curve25519PublicKey))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement(); byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
agreement.init(ecPriv); priv.getEncoded());
byte[] secret = agreement.calculateAgreement(ecPub).toByteArray(); // If the shared secret is all zeroes, the public key is invalid
byte allZero = 0;
for (byte b : secret) allZero |= b;
if (allZero == 0) throw new GeneralSecurityException();
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Deriving shared secret took " + duration + " ms"); LOG.info("Deriving shared secret took " + duration + " ms");
@@ -151,18 +142,10 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public KeyPair generateAgreementKeyPair() { public KeyPair generateAgreementKeyPair() {
AsymmetricCipherKeyPair keyPair = Curve25519KeyPair keyPair = curve25519.generateKeyPair();
agreementKeyPairGenerator.generateKeyPair(); PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
// Return a wrapper that uses the SEC 1 encoding PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
ECPublicKeyParameters ecPublicKey = return new KeyPair(pub, priv);
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
} }
@Override @Override
@@ -172,7 +155,8 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public KeyPair generateSignatureKeyPair() { public KeyPair generateSignatureKeyPair() {
java.security.KeyPair keyPair = signatureKeyPairGenerator.generateKeyPair(); java.security.KeyPair keyPair =
signatureKeyPairGenerator.generateKeyPair();
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic(); EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte()); PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate(); EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
@NotNullByDefault
class Curve25519KeyParser implements KeyParser {
@Override
public PublicKey parsePublicKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PublicKey(encodedKey);
}
@Override
public PrivateKey parsePrivateKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PrivateKey(encodedKey);
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class Curve25519PrivateKey extends Bytes implements PrivateKey {
Curve25519PrivateKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
class Curve25519PublicKey extends Bytes implements PublicKey {
Curve25519PublicKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.bramble.crypto;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECMultiplier;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger;
/**
* Parameters for curve brainpoolp256r1 - see RFC 5639.
*/
class EllipticCurveConstants {
static final ECDomainParameters PARAMETERS;
static {
// Start with the default implementation of the curve
X9ECParameters x9 = TeleTrusTNamedCurves.getByName("brainpoolp256r1");
// Use a constant-time multiplier
ECMultiplier monty = new MontgomeryLadderMultiplier();
ECCurve curve = x9.getCurve().configure().setMultiplier(monty).create();
BigInteger gX = x9.getG().getAffineXCoord().toBigInteger();
BigInteger gY = x9.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(gX, gY);
// Convert to ECDomainParameters using the new multiplier
PARAMETERS = new ECDomainParameters(curve, g, x9.getN(), x9.getH());
}
}

View File

@@ -13,11 +13,11 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint; import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.SecureRandom; import java.security.SecureRandom;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
public class EllipticCurveMultiplicationTest extends BrambleTestCase { public class EllipticCurveMultiplicationTest extends BrambleTestCase {
@@ -31,15 +31,11 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
ECPoint defaultG = defaultX9Parameters.getG(); ECPoint defaultG = defaultX9Parameters.getG();
BigInteger defaultN = defaultX9Parameters.getN(); BigInteger defaultN = defaultX9Parameters.getN();
BigInteger defaultH = defaultX9Parameters.getH(); BigInteger defaultH = defaultX9Parameters.getH();
// Check that the default parameters are equal to our parameters
assertEquals(PARAMETERS.getCurve(), defaultCurve);
assertEquals(PARAMETERS.getG(), defaultG);
assertEquals(PARAMETERS.getN(), defaultN);
assertEquals(PARAMETERS.getH(), defaultH);
// ECDomainParameters doesn't have an equals() method, but it's just a
// container for the parameters
ECDomainParameters defaultParameters = new ECDomainParameters( ECDomainParameters defaultParameters = new ECDomainParameters(
defaultCurve, defaultG, defaultN, defaultH); defaultCurve, defaultG, defaultN, defaultH);
// Instantiate an implementation using the Montgomery ladder multiplier
ECDomainParameters montgomeryParameters =
constantTime(defaultParameters);
// Generate two key pairs with each set of parameters, using the same // Generate two key pairs with each set of parameters, using the same
// deterministic PRNG for both sets of parameters // deterministic PRNG for both sets of parameters
byte[] seed = new byte[32]; byte[] seed = new byte[32];
@@ -47,7 +43,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
// Montgomery ladder multiplier // Montgomery ladder multiplier
SecureRandom random = new PseudoSecureRandom(seed); SecureRandom random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams = ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(PARAMETERS, random); new ECKeyGenerationParameters(montgomeryParameters, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator(); ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
montgomeryGenerator.init(montgomeryGeneratorParams); montgomeryGenerator.init(montgomeryGeneratorParams);
AsymmetricCipherKeyPair montgomeryKeyPair1 = AsymmetricCipherKeyPair montgomeryKeyPair1 =
@@ -107,4 +103,13 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
assertEquals(sharedSecretMontgomeryMontgomery, assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretDefaultDefault); sharedSecretDefaultDefault);
} }
private static ECDomainParameters constantTime(ECDomainParameters in) {
ECCurve curve = in.getCurve().configure().setMultiplier(
new MontgomeryLadderMultiplier()).create();
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(x, y);
return new ECDomainParameters(curve, g, in.getN(), in.getH());
}
} }

View File

@@ -38,7 +38,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM; import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
// Not a JUnit test // Not a JUnit test
public class EllipticCurvePerformanceTest { public class EllipticCurvePerformanceTest {
@@ -65,7 +64,6 @@ public class EllipticCurvePerformanceTest {
runTest(name + " default", params); runTest(name + " default", params);
runTest(name + " constant", constantTime(params)); runTest(name + " constant", constantTime(params));
} }
runTest("ours", PARAMETERS);
runCurve25519Test(); runCurve25519Test();
runEd25519Test(); runEd25519Test();
} }

View File

@@ -2,11 +2,13 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider; import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.junit.Test; import org.junit.Test;
import java.security.GeneralSecurityException;
import java.util.Random; import java.util.Random;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
@@ -15,20 +17,33 @@ import static org.junit.Assert.assertArrayEquals;
public class KeyAgreementTest extends BrambleTestCase { public class KeyAgreementTest extends BrambleTestCase {
@Test private final CryptoComponent crypto =
public void testDeriveSharedSecret() throws Exception { new CryptoComponentImpl(new TestSecureRandomProvider(), null);
CryptoComponent crypto = private final byte[][] inputs;
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
KeyPair aPair = crypto.generateAgreementKeyPair(); public KeyAgreementTest() {
KeyPair bPair = crypto.generateAgreementKeyPair();
Random random = new Random(); Random random = new Random();
byte[][] inputs = new byte[random.nextInt(10) + 1][]; inputs = new byte[random.nextInt(10) + 1][];
for (int i = 0; i < inputs.length; i++) for (int i = 0; i < inputs.length; i++)
inputs[i] = getRandomBytes(random.nextInt(256)); inputs[i] = getRandomBytes(random.nextInt(256));
}
@Test
public void testDerivesSharedSecret() throws Exception {
KeyPair aPair = crypto.generateAgreementKeyPair();
KeyPair bPair = crypto.generateAgreementKeyPair();
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
bPair.getPublic(), aPair, inputs); bPair.getPublic(), aPair, inputs);
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
aPair.getPublic(), bPair, inputs); aPair.getPublic(), bPair, inputs);
assertArrayEquals(aShared.getBytes(), bShared.getBytes()); assertArrayEquals(aShared.getBytes(), bShared.getBytes());
} }
@Test(expected = GeneralSecurityException.class)
public void testRejectsInvalidPublicKey() throws Exception {
KeyPair keyPair = crypto.generateAgreementKeyPair();
PublicKey invalid = new Curve25519PublicKey(new byte[32]);
crypto.deriveSharedSecret(SHARED_SECRET_LABEL, invalid, keyPair,
inputs);
}
} }

View File

@@ -26,7 +26,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
public void testAgreementPublicKeyLength() throws Exception { public void testAgreementPublicKeyLength() throws Exception {
// Generate 10 agreement key pairs // Generate 10 agreement key pairs
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateSignatureKeyPair(); KeyPair keyPair = crypto.generateAgreementKeyPair();
// Check the length of the public key // Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded(); byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_AGREEMENT_PUBLIC_KEY_BYTES); assertTrue(publicKey.length <= MAX_AGREEMENT_PUBLIC_KEY_BYTES);