diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java index d221c85c5faabdd9232a92643ee75d1d24194514..3b12c5cf75ae93f0b9fa0f84f549c88c39c0a3d1 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoConstants.java @@ -5,7 +5,7 @@ public interface CryptoConstants { /** * 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. diff --git a/bramble-core/build.gradle b/bramble-core/build.gradle index af2d70bb945706b522fb358bc53d5e40c518920a..a6189c910fb2c1bd5d057ae65aa97e5f1761bc3e 100644 --- a/bramble-core/build.gradle +++ b/bramble-core/build.gradle @@ -12,6 +12,7 @@ dependencies { implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6 implementation 'org.bitlet:weupnp:0.1.4' 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' @@ -53,6 +54,7 @@ dependencyVerification { 'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80', 'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220', + 'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e', ] } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java index 4a0a3e4a6f8741c1278be10b24702671e413cebf..4161db31ab556f5183cd7476c25e1f64725ad1f6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java @@ -14,15 +14,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.StringUtils; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; import org.spongycastle.crypto.CryptoException; import org.spongycastle.crypto.Digest; -import org.spongycastle.crypto.agreement.ECDHCBasicAgreement; import org.spongycastle.crypto.digests.Blake2bDigest; -import org.spongycastle.crypto.generators.ECKeyPairGenerator; -import org.spongycastle.crypto.params.ECKeyGenerationParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.whispersystems.curve25519.Curve25519; +import org.whispersystems.curve25519.Curve25519KeyPair; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; @@ -35,7 +31,6 @@ import javax.annotation.Nullable; import javax.inject.Inject; 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; @NotNullByDefault @@ -44,7 +39,6 @@ class CryptoComponentImpl implements CryptoComponent { private static final Logger LOG = 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 STORAGE_IV_BYTES = 24; // 196 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 PasswordBasedKdf passwordBasedKdf; - private final ECKeyPairGenerator agreementKeyPairGenerator; + private final Curve25519 curve25519; private final KeyPairGenerator signatureKeyPairGenerator; private final KeyParser agreementKeyParser, signatureKeyParser; private final MessageEncrypter messageEncrypter; @@ -80,15 +74,11 @@ class CryptoComponentImpl implements CryptoComponent { } secureRandom = new SecureRandom(); this.passwordBasedKdf = passwordBasedKdf; - ECKeyGenerationParameters params = new ECKeyGenerationParameters( - PARAMETERS, secureRandom); - agreementKeyPairGenerator = new ECKeyPairGenerator(); - agreementKeyPairGenerator.init(params); + curve25519 = Curve25519.getInstance("java"); signatureKeyPairGenerator = new KeyPairGenerator(); signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS, secureRandom); - agreementKeyParser = new Sec1KeyParser(PARAMETERS, - AGREEMENT_KEY_PAIR_BITS); + agreementKeyParser = new Curve25519KeyParser(); signatureKeyParser = new EdKeyParser(); messageEncrypter = new MessageEncrypter(secureRandom); } @@ -133,16 +123,17 @@ class CryptoComponentImpl implements CryptoComponent { // Package access for testing byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub) throws GeneralSecurityException { - if (!(priv instanceof Sec1PrivateKey)) + if (!(priv instanceof Curve25519PrivateKey)) throw new IllegalArgumentException(); - if (!(pub instanceof Sec1PublicKey)) + if (!(pub instanceof Curve25519PublicKey)) throw new IllegalArgumentException(); - ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey(); - ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey(); long now = System.currentTimeMillis(); - ECDHCBasicAgreement agreement = new ECDHCBasicAgreement(); - agreement.init(ecPriv); - byte[] secret = agreement.calculateAgreement(ecPub).toByteArray(); + byte[] secret = curve25519.calculateAgreement(pub.getEncoded(), + priv.getEncoded()); + // 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; if (LOG.isLoggable(INFO)) LOG.info("Deriving shared secret took " + duration + " ms"); @@ -151,18 +142,10 @@ class CryptoComponentImpl implements CryptoComponent { @Override public KeyPair generateAgreementKeyPair() { - AsymmetricCipherKeyPair keyPair = - agreementKeyPairGenerator.generateKeyPair(); - // Return a wrapper that uses the SEC 1 encoding - ECPublicKeyParameters ecPublicKey = - (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); + Curve25519KeyPair keyPair = curve25519.generateKeyPair(); + PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey()); + PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey()); + return new KeyPair(pub, priv); } @Override @@ -172,7 +155,8 @@ class CryptoComponentImpl implements CryptoComponent { @Override public KeyPair generateSignatureKeyPair() { - java.security.KeyPair keyPair = signatureKeyPairGenerator.generateKeyPair(); + java.security.KeyPair keyPair = + signatureKeyPairGenerator.generateKeyPair(); EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic(); PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte()); EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate(); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519KeyParser.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519KeyParser.java new file mode 100644 index 0000000000000000000000000000000000000000..e7b39ad8d3dfbc8e41fbc1ab6d1337db8399e0f4 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519KeyParser.java @@ -0,0 +1,35 @@ +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(clamp(encodedKey)); + } + + static byte[] clamp(byte[] b) { + byte[] clamped = new byte[32]; + System.arraycopy(b, 0, clamped, 0, 32); + clamped[0] &= 248; + clamped[31] &= 127; + clamped[31] |= 64; + return clamped; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PrivateKey.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PrivateKey.java new file mode 100644 index 0000000000000000000000000000000000000000..b7f1a0cc51f4de7bc62bc6785b58775b6cd7e6eb --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PrivateKey.java @@ -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(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PublicKey.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PublicKey.java new file mode 100644 index 0000000000000000000000000000000000000000..94d703d01e51a7a6588cbb762bd00e23cfbfcbb7 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/Curve25519PublicKey.java @@ -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(); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/EllipticCurveConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/EllipticCurveConstants.java deleted file mode 100644 index e4b4b04e8809ce8a5644c61fd722907d8695ae84..0000000000000000000000000000000000000000 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/EllipticCurveConstants.java +++ /dev/null @@ -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()); - } -} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java index 9be3dc0ddc5b6b8b671759800e67a94d99d4fde9..b4e639047895c3fe7874a0c8605c157199931d0d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurveMultiplicationTest.java @@ -13,11 +13,11 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.math.ec.MontgomeryLadderMultiplier; import java.math.BigInteger; import java.security.SecureRandom; -import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS; import static org.junit.Assert.assertEquals; public class EllipticCurveMultiplicationTest extends BrambleTestCase { @@ -31,15 +31,11 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase { ECPoint defaultG = defaultX9Parameters.getG(); BigInteger defaultN = defaultX9Parameters.getN(); 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( 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 // deterministic PRNG for both sets of parameters byte[] seed = new byte[32]; @@ -47,7 +43,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase { // Montgomery ladder multiplier SecureRandom random = new PseudoSecureRandom(seed); ECKeyGenerationParameters montgomeryGeneratorParams = - new ECKeyGenerationParameters(PARAMETERS, random); + new ECKeyGenerationParameters(montgomeryParameters, random); ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator(); montgomeryGenerator.init(montgomeryGeneratorParams); AsymmetricCipherKeyPair montgomeryKeyPair1 = @@ -107,4 +103,13 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase { assertEquals(sharedSecretMontgomeryMontgomery, 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()); + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java index ac42f290e31c8f5018c2a0af6d06fd24cef81cc2..abf2798b1c18e88cf7ff6bce5a1adbec5856c9ba 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/EllipticCurvePerformanceTest.java @@ -1,17 +1,20 @@ package org.briarproject.bramble.crypto; +import net.i2p.crypto.eddsa.EdDSASecurityProvider; +import net.i2p.crypto.eddsa.KeyPairGenerator; + import org.spongycastle.asn1.sec.SECNamedCurves; import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves; import org.spongycastle.asn1.x9.X9ECParameters; import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.BasicAgreement; import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.agreement.ECDHBasicAgreement; import org.spongycastle.crypto.agreement.ECDHCBasicAgreement; import org.spongycastle.crypto.digests.Blake2bDigest; import org.spongycastle.crypto.generators.ECKeyPairGenerator; import org.spongycastle.crypto.params.ECDomainParameters; import org.spongycastle.crypto.params.ECKeyGenerationParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.params.ParametersWithRandom; import org.spongycastle.crypto.signers.DSADigestSigner; import org.spongycastle.crypto.signers.DSAKCalculator; @@ -20,14 +23,22 @@ import org.spongycastle.crypto.signers.HMacDSAKCalculator; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; import org.spongycastle.math.ec.MontgomeryLadderMultiplier; +import org.whispersystems.curve25519.Curve25519; +import org.whispersystems.curve25519.Curve25519KeyPair; import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.Provider; import java.security.SecureRandom; +import java.security.Signature; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM; + // Not a JUnit test public class EllipticCurvePerformanceTest { @@ -38,8 +49,9 @@ public class EllipticCurvePerformanceTest { "secp256k1", "secp256r1", "secp384r1", "secp521r1"); private static final List<String> BRAINPOOL_NAMES = Arrays.asList( "brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1"); + private static final Provider ED_PROVIDER = new EdDSASecurityProvider(); - public static void main(String[] args) { + public static void main(String[] args) throws GeneralSecurityException { for (String name : SEC_NAMES) { ECDomainParameters params = convertParams(SECNamedCurves.getByName(name)); @@ -52,43 +64,31 @@ public class EllipticCurvePerformanceTest { runTest(name + " default", params); runTest(name + " constant", constantTime(params)); } - runTest("ours", EllipticCurveConstants.PARAMETERS); + runCurve25519Test(); + runEd25519Test(); } private static void runTest(String name, ECDomainParameters params) { // Generate two key pairs using the given parameters - ECKeyGenerationParameters generatorParams = - new ECKeyGenerationParameters(params, random); ECKeyPairGenerator generator = new ECKeyPairGenerator(); - generator.init(generatorParams); + generator.init(new ECKeyGenerationParameters(params, random)); AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair(); - ECPublicKeyParameters public1 = - (ECPublicKeyParameters) keyPair1.getPublic(); - ECPrivateKeyParameters private1 = - (ECPrivateKeyParameters) keyPair1.getPrivate(); AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair(); - ECPublicKeyParameters public2 = - (ECPublicKeyParameters) keyPair2.getPublic(); - // Time some ECDH key agreements - List<Long> samples = new ArrayList<>(); - for (int i = 0; i < SAMPLES; i++) { - ECDHCBasicAgreement agreement = new ECDHCBasicAgreement(); - long start = System.nanoTime(); - agreement.init(private1); - agreement.calculateAgreement(public2); - samples.add(System.nanoTime() - start); - } - long agreementMedian = median(samples); + // Time some ECDH and ECDHC key agreements + long agreementMedian = runAgreementTest(keyPair1, keyPair2, false); + long agreementWithCofactorMedian = + runAgreementTest(keyPair1, keyPair2, true); // Time some signatures + List<Long> samples = new ArrayList<>(); List<byte[]> signatures = new ArrayList<>(); - samples.clear(); for (int i = 0; i < SAMPLES; i++) { Digest digest = new Blake2bDigest(256); DSAKCalculator calculator = new HMacDSAKCalculator(digest); DSADigestSigner signer = new DSADigestSigner(new ECDSASigner( calculator), digest); long start = System.nanoTime(); - signer.init(true, new ParametersWithRandom(private1, random)); + signer.init(true, + new ParametersWithRandom(keyPair1.getPrivate(), random)); signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN); signatures.add(signer.generateSignature()); samples.add(System.nanoTime() - start); @@ -102,17 +102,83 @@ public class EllipticCurvePerformanceTest { DSADigestSigner signer = new DSADigestSigner(new ECDSASigner( calculator), digest); long start = System.nanoTime(); - signer.init(false, public1); + signer.init(false, keyPair1.getPublic()); signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN); if (!signer.verifySignature(signatures.get(i))) throw new AssertionError(); samples.add(System.nanoTime() - start); } long verificationMedian = median(samples); - System.out.println(name + ": " - + agreementMedian + " " - + signatureMedian + " " - + verificationMedian); + System.out.println(String.format("%s: %,d %,d %,d %,d", name, + agreementMedian, agreementWithCofactorMedian, + signatureMedian, verificationMedian)); + } + + private static long runAgreementTest(AsymmetricCipherKeyPair keyPair1, + AsymmetricCipherKeyPair keyPair2, boolean withCofactor) { + List<Long> samples = new ArrayList<>(); + for (int i = 0; i < SAMPLES; i++) { + BasicAgreement agreement = createAgreement(withCofactor); + long start = System.nanoTime(); + agreement.init(keyPair1.getPrivate()); + agreement.calculateAgreement(keyPair2.getPublic()); + samples.add(System.nanoTime() - start); + } + return median(samples); + } + + private static BasicAgreement createAgreement(boolean withCofactor) { + if (withCofactor) return new ECDHCBasicAgreement(); + else return new ECDHBasicAgreement(); + } + + private static void runCurve25519Test() { + Curve25519 curve25519 = Curve25519.getInstance("java"); + Curve25519KeyPair keyPair1 = curve25519.generateKeyPair(); + Curve25519KeyPair keyPair2 = curve25519.generateKeyPair(); + // Time some key agreements + List<Long> samples = new ArrayList<>(); + for (int i = 0; i < SAMPLES; i++) { + long start = System.nanoTime(); + curve25519.calculateAgreement(keyPair1.getPublicKey(), + keyPair2.getPrivateKey()); + samples.add(System.nanoTime() - start); + } + long agreementMedian = median(samples); + System.out.println(String.format("Curve25519: %,d - - -", + agreementMedian)); + } + + private static void runEd25519Test() throws GeneralSecurityException { + KeyPair keyPair = new KeyPairGenerator().generateKeyPair(); + // Time some signatures + List<Long> samples = new ArrayList<>(); + List<byte[]> signatures = new ArrayList<>(); + for (int i = 0; i < SAMPLES; i++) { + Signature signature = + Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER); + long start = System.nanoTime(); + signature.initSign(keyPair.getPrivate(), random); + signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN); + signatures.add(signature.sign()); + samples.add(System.nanoTime() - start); + } + long signatureMedian = median(samples); + // Time some signature verifications + samples.clear(); + for (int i = 0; i < SAMPLES; i++) { + Signature signature = + Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER); + long start = System.nanoTime(); + signature.initVerify(keyPair.getPublic()); + signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN); + if (!signature.verify(signatures.get(i))) + throw new AssertionError(); + samples.add(System.nanoTime() - start); + } + long verificationMedian = median(samples); + System.out.println(String.format("Ed25519: - - %,d %,d", + signatureMedian, verificationMedian)); } private static long median(List<Long> list) { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java index d035bbe4b39f2b72e6c7e92c13b1c21bef05f046..f0505f4f1374d2bb3a45d99c7f559a80b6d26001 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyAgreementTest.java @@ -2,33 +2,80 @@ package org.briarproject.bramble.crypto; import org.briarproject.bramble.api.crypto.CryptoComponent; 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.test.BrambleTestCase; import org.briarproject.bramble.test.TestSecureRandomProvider; import org.junit.Test; +import org.whispersystems.curve25519.Curve25519; +import java.security.GeneralSecurityException; import java.util.Random; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL; import static org.briarproject.bramble.test.TestUtils.getRandomBytes; +import static org.briarproject.bramble.util.StringUtils.fromHexString; import static org.junit.Assert.assertArrayEquals; public class KeyAgreementTest extends BrambleTestCase { - @Test - public void testDeriveSharedSecret() throws Exception { - CryptoComponent crypto = - new CryptoComponentImpl(new TestSecureRandomProvider(), null); - KeyPair aPair = crypto.generateAgreementKeyPair(); - KeyPair bPair = crypto.generateAgreementKeyPair(); + // Test vector from RFC 7748: Alice's private and public keys, Bob's + // private and public keys, and the shared secret + // https://tools.ietf.org/html/rfc7748#section-6.1 + private static final String ALICE_PRIVATE = + "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"; + private static final String ALICE_PUBLIC = + "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"; + private static final String BOB_PRIVATE = + "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"; + private static final String BOB_PUBLIC = + "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"; + private static final String SHARED_SECRET = + "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"; + + private final CryptoComponent crypto = + new CryptoComponentImpl(new TestSecureRandomProvider(), null); + private final byte[][] inputs; + + public KeyAgreementTest() { 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++) 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, bPair.getPublic(), aPair, inputs); SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, aPair.getPublic(), bPair, inputs); 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); + } + + @Test + public void testRfc7748TestVector() throws Exception { + // Private keys need to be clamped because curve25519-java does the + // clamping at key generation time, not multiplication time + byte[] aPriv = Curve25519KeyParser.clamp(fromHexString(ALICE_PRIVATE)); + byte[] aPub = fromHexString(ALICE_PUBLIC); + byte[] bPriv = Curve25519KeyParser.clamp(fromHexString(BOB_PRIVATE)); + byte[] bPub = fromHexString(BOB_PUBLIC); + byte[] sharedSecret = fromHexString(SHARED_SECRET); + Curve25519 curve25519 = Curve25519.getInstance("java"); + assertArrayEquals(sharedSecret, + curve25519.calculateAgreement(aPub, bPriv)); + assertArrayEquals(sharedSecret, + curve25519.calculateAgreement(bPub, aPriv)); + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java index 1f2cad4dc7cda8344aaea99f1f72b699f3e2cc1a..30d62ef8cd28ac85cb3f27b965bdeea0aad44981 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/crypto/KeyEncodingAndParsingTest.java @@ -26,7 +26,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase { public void testAgreementPublicKeyLength() throws Exception { // Generate 10 agreement key pairs for (int i = 0; i < 10; i++) { - KeyPair keyPair = crypto.generateSignatureKeyPair(); + KeyPair keyPair = crypto.generateAgreementKeyPair(); // Check the length of the public key byte[] publicKey = keyPair.getPublic().getEncoded(); assertTrue(publicKey.length <= MAX_AGREEMENT_PUBLIC_KEY_BYTES);