From 0bcb82c407b334b6c3dd0e243cabcfa2913a2f5a Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Sat, 23 Jan 2016 15:44:40 -0800 Subject: Add curve25519-sha256@libssh.org exchange method --- sshlib/build.gradle | 1 + .../trilead/ssh2/crypto/dh/Curve25519Exchange.java | 71 +++++++++++++++++++ .../trilead/ssh2/crypto/dh/GenericDhExchange.java | 3 + .../com/trilead/ssh2/transport/KexManager.java | 8 ++- .../ssh2/crypto/dh/Curve25519ExchangeTest.java | 80 ++++++++++++++++++++++ 5 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java create mode 100644 sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java diff --git a/sshlib/build.gradle b/sshlib/build.gradle index d43c56e..fb266ff 100644 --- a/sshlib/build.gradle +++ b/sshlib/build.gradle @@ -15,6 +15,7 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.jcraft:jzlib:1.1.3' compile 'org.connectbot:simplesocks:1.0.1' + compile 'org.zeromq:curve25519-java:0.1.0' testCompile 'junit:junit:4.12' } diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java new file mode 100644 index 0000000..d28393b --- /dev/null +++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java @@ -0,0 +1,71 @@ +package com.trilead.ssh2.crypto.dh; + +import djb.Curve25519; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * Created by Kenny Root on 1/23/16. + */ +public class Curve25519Exchange extends GenericDhExchange { + public static final String NAME = "curve25519-sha256@libssh.org"; + + private final byte[] clientPublic = new byte[Curve25519.KEY_SIZE]; + private final byte[] clientPrivate = new byte[Curve25519.KEY_SIZE]; + private final byte[] serverPublic = new byte[Curve25519.KEY_SIZE]; + + public Curve25519Exchange() { + super(); + } + + /* + * Used to test known vectors. + */ + public Curve25519Exchange(byte[] secret) { + if (secret.length != Curve25519.KEY_SIZE) { + throw new AssertionError("secret must be key size"); + } + System.arraycopy(secret, 0, clientPrivate, 0, secret.length); + Curve25519.keygen(clientPublic, null, clientPrivate); + } + + @Override + public void init(String name) throws IOException { + if (!NAME.equals(name)) { + throw new IOException("Invalid name " + name); + } + + SecureRandom sr = new SecureRandom(); + sr.nextBytes(clientPrivate); + Curve25519.keygen(clientPublic, null, clientPrivate); + } + + @Override + public byte[] getE() { + return clientPublic.clone(); + } + + @Override + protected byte[] getServerE() { + return serverPublic.clone(); + } + + @Override + public void setF(byte[] f) throws IOException { + if (f.length != serverPublic.length) { + throw new IOException("Server sent invalid key length " + f.length + " (expected " + + serverPublic.length + ")"); + } + System.arraycopy(f, 0, serverPublic, 0, f.length); + byte[] sharedSecretBytes = new byte[Curve25519.KEY_SIZE]; + Curve25519.curve(sharedSecretBytes, clientPrivate, serverPublic); + sharedSecret = new BigInteger(1, sharedSecretBytes); + } + + @Override + public String getHashAlgo() { + return "SHA-256"; + } +} diff --git a/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java index 039ff75..1ad2554 100644 --- a/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java +++ b/sshlib/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java @@ -28,6 +28,9 @@ public abstract class GenericDhExchange } public static GenericDhExchange getInstance(String algo) { + if (algo.startsWith("curve25519-sha256@libssh.org")) { + return new Curve25519Exchange(); + } if (algo.startsWith("ecdh-sha2-")) { return new EcDhExchange(); } else { diff --git a/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java b/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java index ee0784a..741268b 100644 --- a/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java +++ b/sshlib/src/main/java/com/trilead/ssh2/transport/KexManager.java @@ -21,6 +21,7 @@ import com.trilead.ssh2.crypto.CryptoWishList; import com.trilead.ssh2.crypto.KeyMaterial; import com.trilead.ssh2.crypto.cipher.BlockCipher; import com.trilead.ssh2.crypto.cipher.BlockCipherFactory; +import com.trilead.ssh2.crypto.dh.Curve25519Exchange; import com.trilead.ssh2.crypto.dh.DhGroupExchange; import com.trilead.ssh2.crypto.dh.GenericDhExchange; import com.trilead.ssh2.crypto.digest.MAC; @@ -75,6 +76,7 @@ public class KexManager private static final Set KEX_ALGS = new LinkedHashSet(); static { + KEX_ALGS.add(Curve25519Exchange.NAME); if (supportsEc) { KEX_ALGS.add("ecdh-sha2-nistp256"); KEX_ALGS.add("ecdh-sha2-nistp384"); @@ -495,7 +497,8 @@ public class KexManager || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || kxs.np.kex_algo.equals("ecdh-sha2-nistp384") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) { + || kxs.np.kex_algo.equals("ecdh-sha2-nistp521") + || kxs.np.kex_algo.equals(Curve25519Exchange.NAME)) { kxs.dhx = GenericDhExchange.getInstance(kxs.np.kex_algo); kxs.dhx.init(kxs.np.kex_algo); @@ -633,7 +636,8 @@ public class KexManager || kxs.np.kex_algo.equals("diffie-hellman-group14-sha1") || kxs.np.kex_algo.equals("ecdh-sha2-nistp256") || kxs.np.kex_algo.equals("ecdh-sha2-nistp384") - || kxs.np.kex_algo.equals("ecdh-sha2-nistp521")) + || kxs.np.kex_algo.equals("ecdh-sha2-nistp521") + || kxs.np.kex_algo.equals(Curve25519Exchange.NAME)) { if (kxs.state == 1) { diff --git a/sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java b/sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java new file mode 100644 index 0000000..2c9d795 --- /dev/null +++ b/sshlib/src/test/java/com/trilead/ssh2/crypto/dh/Curve25519ExchangeTest.java @@ -0,0 +1,80 @@ +package com.trilead.ssh2.crypto.dh; + +import djb.Curve25519; +import org.junit.Test; + +import javax.xml.bind.DatatypeConverter; +import java.math.BigInteger; +import java.security.SecureRandom; + +import static org.junit.Assert.*; + +/** + * Created by Kenny Root on 1/23/16. + */ +public class Curve25519ExchangeTest { + private static final byte[] ALICE_PRIVATE = toByteArray("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); + private static final byte[] ALICE_PUBLIC = toByteArray("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); + + private static final byte[] BOB_PRIVATE = toByteArray("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"); + private static final byte[] BOB_PUBLIC = toByteArray("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"); + + private static final byte[] KNOWN_SHARED_SECRET = toByteArray("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"); + private static final BigInteger KNOWN_SHARED_SECRET_BI = new BigInteger(1, KNOWN_SHARED_SECRET); + + private static byte[] toByteArray(String s) { + return DatatypeConverter.parseHexBinary(s); + } + + @Test + public void selfAgreement() throws Exception { + SecureRandom sr = new SecureRandom(); + + byte[] alicePrivKey = new byte[Curve25519.KEY_SIZE]; + sr.nextBytes(alicePrivKey); + byte[] alicePubKey = new byte[Curve25519.KEY_SIZE]; + Curve25519.keygen(alicePubKey, null, alicePrivKey); + + byte[] bobPrivKey = new byte[Curve25519.KEY_SIZE]; + sr.nextBytes(bobPrivKey); + byte[] bobPubKey = new byte[Curve25519.KEY_SIZE]; + Curve25519.keygen(bobPubKey, null, bobPrivKey); + + Curve25519Exchange alice = new Curve25519Exchange(alicePrivKey); + alice.setF(bobPubKey); + + Curve25519Exchange bob = new Curve25519Exchange(bobPrivKey); + bob.setF(alicePubKey); + + assertNotNull(alice.sharedSecret); + assertEquals(alice.sharedSecret, bob.sharedSecret); + } + + @Test + public void deriveAlicePublicKey() throws Exception { + byte[] pubKey = new byte[Curve25519.KEY_SIZE]; + Curve25519.keygen(pubKey, null, ALICE_PRIVATE); + assertArrayEquals(ALICE_PUBLIC, pubKey); + } + + @Test + public void deriveBobPublicKey() throws Exception { + byte[] pubKey = new byte[Curve25519.KEY_SIZE]; + Curve25519.keygen(pubKey, null, BOB_PRIVATE); + assertArrayEquals(BOB_PUBLIC, pubKey); + } + + @Test + public void knownValues_Alice() throws Exception { + Curve25519Exchange ex = new Curve25519Exchange(ALICE_PRIVATE); + ex.setF(BOB_PUBLIC); + assertEquals(KNOWN_SHARED_SECRET_BI, ex.sharedSecret); + } + + @Test + public void knownValues_Bob() throws Exception { + Curve25519Exchange ex = new Curve25519Exchange(BOB_PRIVATE); + ex.setF(ALICE_PUBLIC); + assertEquals(KNOWN_SHARED_SECRET_BI, ex.sharedSecret); + } +} \ No newline at end of file -- cgit v1.2.3