Diffie-Hellman key exchange is a method allows two parties that have no prior knowledge of each other to exchange a shared secret over a public (insecure) channel. This shared secret can then be used to derive a key for a symmetric cipher like AES (from high-level prospective, that's what happens when establishing a TLS connection).
Java supports Diffie-Hellman scheme via KeyAgreement class. Here is an example how Diffie-Hellman key exchange can be implemented with Java.
Diffie-Hellman scheme has two steps. In first step, we generate public and private values for Diffie-Hellman key exchange. The we need to send a public value to our partner. To do that, we just save public values in to a file. We also same a private value to a file, so then we can load it again.
In second step, we read a private value from a file. Then, we initialize KeyAgreement with the private value. Next, we read a public value generated by our partner. Finally, we complete key exchange with the public value, and generate a shared secret.
Here is how it can be done with Java:
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); | |
} | |
} |
And here is how it can be run:
#!/bin/bash mkdir -p classes # compile ${JAVA_HOME}/bin/javac -d classes src/security/keyexchange/DHKeyExchange.java # Bob initializes a key exchange on his side # public value is going to be saved to bob.public ${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange bob init # Alice initializes a key exchange on her side # public value is going to be saved to alice.public ${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange alice init # Bob reads the public value from Alice, finalizes key exchange, and gets a shared secret ${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange bob complete alice # Alice reads the public value from Bob, finalizes key exchange, and gets a shared secret ${JAVA_HOME}/bin/java -cp classes security.keyexchange.DHKeyExchange alice complete bob
And finally, here is what the output looks like:
>>> bob: private X saved to bob.private >>> bob: public Y saved to bob.public: 00FA941A6BBDCE94440B9FB27A0615452520D4D0904142A23A505FD93925AA7DBC2B06830040C80E5427CF83BDDD508BE2035149F11D34785919C67497F269593F760F7C654AA948E9EAE050410DB196833AE95F9456488FD01B065D98144041CF7CBF05C77B2662212226188A9721A570F893A5C2BE2D89A2B2CB446AB555282DD47547D6E9F40A077440ACC33A5B6C945A84A956E4360FF34E7C0E6C54CDEED5489945BD6D9D58297DD72C239256704875A7560939C6105E2B85B35820AAE9A0603CD9EADFD6AF75BA4F4796DC074E0F92AEA5D99D7D777835C355BFB282229C597B70EA5754170DA3BA437882616064D7E65E9FB49F2BB54AE93454B45A46A6 >>> alice: private X saved to alice.private >>> alice: public Y saved to alice.public: 00A7779F9E667F965C51A023181886BB975EBB58268EA67774C1AB9684DE3C61E31A9DD55B704509D0F12EB753E6866AF6EEA96328C7A4AFB4B4676335C88689C2AF4616A044B030AB5AA1C17CDF949A95E36AB5E715D525F361657110DB88A52BE3DC977F692A1EECB23FB23213FA447B4C997AB651950BD1820769B72B587286F88C70675E2E113E95609F077915073CD895253019D47D45DF563B8C9CF107A8ADD343D015ECFEE96B64760F15A8E46DD2D00C961460175C19CB44DE063F57264F3B8B73E832AAA44EDBA9DB95D955066C7E3B44C8497D271F050C258033B364264987ACD7C72AEF59F04F0B6E616F10781FC9261DD94336253E60A195CF929B >>> shared secret: 84464E64007F62CA9E2A458BD9A82E25554620D80ECFADA7FCE9641DD6EA0DF4F26E9E93D7D39D78A3FE1F971B699B5A9AFCB4E8E8B6E24B6E57E24B3639C11F8C8B82B6B93BAA9F370DF530D0D903F85CF2331E270291E9631D96DAF47EC5DAEC7A8EEC98AEA233B162F693BECB2ACBFB6994EFCF4036D57847E6387F86EBC9A9FBCA9F8A88F2FA23414BD23F4CC1ACDC27B1EBA14BF6912AAA1D4C02BC81E243F96B8F18B1DB98554ADFA03638242C553215F41A66635A003A0E0B01A0DB8EBC64D1661558EB128C5283B3DF6B0144CEF3D55371F3479978083E5B99DB3D7F9F5E42EDF62784A3490068C489081EEA8CB7F251C5CE56BAAD688A2623B077AC >>> shared secret: 84464E64007F62CA9E2A458BD9A82E25554620D80ECFADA7FCE9641DD6EA0DF4F26E9E93D7D39D78A3FE1F971B699B5A9AFCB4E8E8B6E24B6E57E24B3639C11F8C8B82B6B93BAA9F370DF530D0D903F85CF2331E270291E9631D96DAF47EC5DAEC7A8EEC98AEA233B162F693BECB2ACBFB6994EFCF4036D57847E6387F86EBC9A9FBCA9F8A88F2FA23414BD23F4CC1ACDC27B1EBA14BF6912AAA1D4C02BC81E243F96B8F18B1DB98554ADFA03638242C553215F41A66635A003A0E0B01A0DB8EBC64D1661558EB128C5283B3DF6B0144CEF3D55371F3479978083E5B99DB3D7F9F5E42EDF62784A3490068C489081EEA8CB7F251C5CE56BAAD688A2623B077AC
You may notice that both Bob and Alice got the same shared secret.
By the way, here is a great explanation of the idea of Diffie-Hellman key exchange algorithm without math. You can even show it to your kids.
https://www.youtube.com/watch?v=YEBfamv-_do&feature=youtu.be&t=138
Enjoy!