Last active
September 18, 2017 09:23
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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