Skip to content

Instantly share code, notes, and snippets.

@artem-smotrakov
Last active September 18, 2017 09:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save artem-smotrakov/6d0361ae9514fea5ece4b065dc4c1ecf to your computer and use it in GitHub Desktop.
Save artem-smotrakov/6d0361ae9514fea5ece4b065dc4c1ecf to your computer and use it in GitHub Desktop.
An example of Diffie-Hellman key exchange with Java. For more details see https://codeandsolder.blogspot.com/2017/09/diffie-hellman-key-exchange-in-java.html
package security.keyexchange;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.KeyAgreement;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPrivateKeySpec;
import javax.crypto.spec.DHPublicKeySpec;
/**
* Diffie-Hellman key exchange.
*
* Usage:
*
* javac -d classes DHKeyExchange.java
* java -cp classes security.keyexchange.DHKeyExchange bob init
* java -cp classes security.keyexchange.DHKeyExchange alice init
* java -cp classes security.keyexchange.DHKeyExchange bob complete alice
* java -cp classes security.keyexchange.DHKeyExchange alice complete bob
*/
public class DHKeyExchange {
// this is for bytesToHex() method below
private final static char[] HEX = "0123456789ABCDEF".toCharArray();
// let's use P and G values for ffdhe2048 group from TLS 1.3 spec
private static final BigInteger G = BigInteger.valueOf(2);
private static final BigInteger P = new BigInteger(
"FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" +
"D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" +
"7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" +
"2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" +
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" +
"30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" +
"B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" +
"0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" +
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" +
"3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" +
"886B423861285C97FFFFFFFFFFFFFFFF", 16);
// in Diffie-Hellman key exchange scheme we have two steps
private static enum Stage { init, complete }
/**
* Command line options:
* args[0] - stage (init or complete)
* args[1] - local user's name
* args[2] - remote user's name
*/
public static void main(String[] args) throws Exception {
String name = args[0];
Stage stage = Stage.valueOf(args[1]);
switch (stage) {
case init:
init(args[0]);
break;
case complete:
String from = args[2];
complete(name, from);
break;
default:
throw new RuntimeException("You should not be here!");
}
}
// first step:
// generate public and private values for Diffie-Hellman key exchange,
// and save them to files
private static void init(String name) throws Exception {
KeyPairGenerator gen = KeyPairGenerator.getInstance("DiffieHellman");
gen.initialize(new DHParameterSpec(P, G));
KeyPair keyPair = gen.generateKeyPair();
DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
Path privateFile = Paths.get(name + ".private");
try (OutputStream out = Files.newOutputStream(privateFile)) {
byte[] X = privateKey.getX().toByteArray();
out.write(X);
System.out.printf(">>> %s: private X saved to %s%n", name, privateFile);
}
Path publicFile = Paths.get(name + ".public");
try (OutputStream out = Files.newOutputStream(publicFile)) {
byte[] Y = publicKey.getY().toByteArray();
out.write(Y);
System.out.printf(">>> %s: public Y saved to %s:%n%s%n",
name, publicFile, bytesToHex(Y));
}
}
// second step:
// read a private value from a file
// initialize KeyAgreement with the private value
// read a public value from another person
// complete key exchange with the public value
// generate a shared secret
private static void complete(String name, String from) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance("DiffieHellman");
KeyFactory keyFactory = KeyFactory.getInstance("DiffieHellman");
Path privateFile = Paths.get(name + ".private");
try (InputStream is = Files.newInputStream(privateFile)) {
byte[] X = new byte[(int) Files.size(privateFile)];
is.read(X);
BigInteger x = new BigInteger(X);
PrivateKey privateKey = keyFactory.generatePrivate(
new DHPrivateKeySpec(x, P, G));
keyAgreement.init(privateKey, new DHParameterSpec(P, G));
}
Path publicFile = Paths.get(from + ".public");
try (InputStream is = Files.newInputStream(publicFile)) {
byte[] Y = new byte[(int) Files.size(publicFile)];
is.read(Y);
BigInteger y = new BigInteger(Y);
PublicKey publicKey = keyFactory.generatePublic(
new DHPublicKeySpec(y, P, G));
keyAgreement.doPhase(publicKey, true);
}
byte[] secret = keyAgreement.generateSecret();
System.out.println(">>> shared secret: " + bytesToHex(secret));
}
// converts a byte array to a hex string
private static String bytesToHex(byte[] bytes) {
char[] result = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
result[j * 2] = HEX[v >>> 4];
result[j * 2 + 1] = HEX[v & 0x0F];
}
return new String(result);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment