blob: c9fc9f4b6357cd1935d0667543f7fc4428725f60 [file] [log] [blame]
/*
* Copyright (c) 2012-2015 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.net4j.util.security;
import org.eclipse.net4j.util.io.ExtendedDataInput;
import org.eclipse.net4j.util.io.ExtendedDataOutput;
import org.eclipse.net4j.util.io.IORuntimeException;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.AlgorithmParameterGenerator;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
/**
* Executes the Diffie-Hellman key agreement protocol between 2 parties: {@link Server} and {@link Client}.
*
* @author Eike Stepper
* @since 3.3
*/
public class DiffieHellman
{
/**
* Executes the server-side of the Diffie-Hellman key agreement protocol.
*
* @author Eike Stepper
*/
public static class Server
{
public static final String DEFAULT_SECRET_ALGORITHM = "DES";
public static final String DEFAULT_CYPHER_TRANSFORMATION = "DES/CBC/PKCS5Padding";
private final String realm;
private final PrivateKey privateKey;
private final Challenge challenge;
public Server(String realm, DHParameterSpec dhParamSpec, String secretAlgorithm, String cypherTransformation)
{
this.realm = realm;
try
{
// Create DH key pair, using the passed DH parameters
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
keyPairGenerator.initialize(dhParamSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
privateKey = keyPair.getPrivate();
// Encode public key
byte[] pubKeyEnc = keyPair.getPublic().getEncoded();
// Create and remember Challenge object
challenge = new Challenge(realm, secretAlgorithm, cypherTransformation, pubKeyEnc);
}
catch (GeneralSecurityException ex)
{
throw new SecurityException(ex);
}
}
public Server(String realm, DHParameterSpec dhParamSpec)
{
this(realm, dhParamSpec, DEFAULT_SECRET_ALGORITHM, DEFAULT_CYPHER_TRANSFORMATION);
}
public Server(String realm)
{
this(realm, SkipParameterSpec.INSTANCE);
}
public final String getRealm()
{
return realm;
}
public final Challenge getChallenge()
{
return challenge;
}
public byte[] handleResponse(Client.Response response)
{
try
{
// Instantiate a DH public key from the client's encoded key material.
KeyFactory keyFactory = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(response.getClientPubKeyEnc());
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// Create and initialize DH KeyAgreement object
KeyAgreement keyAgree = KeyAgreement.getInstance("DH");
keyAgree.init(privateKey);
// Use Client's public key for the first (and only) phase of her version of the DH protocol.
keyAgree.doPhase(pubKey, true);
SecretKey sharedSecret = keyAgree.generateSecret(challenge.getSecretAlgorithm());
// Prepare the cipher used to decrypt
Cipher serverCipher = Cipher.getInstance(challenge.getCypherTransformation());
byte[] encodedParams = response.getParamsEnc();
if (encodedParams == null)
{
serverCipher.init(Cipher.DECRYPT_MODE, sharedSecret);
}
else
{
// Instantiate AlgorithmParameters object from parameter encoding obtained from client
AlgorithmParameters params = AlgorithmParameters.getInstance(challenge.getSecretAlgorithm());
params.init(encodedParams);
serverCipher.init(Cipher.DECRYPT_MODE, sharedSecret, params);
}
// Decrypt
return serverCipher.doFinal(response.getCipherText());
}
catch (GeneralSecurityException ex)
{
throw new SecurityException(ex);
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
}
/**
* @author Eike Stepper
*/
public static final class Challenge
{
private final String serverRealm;
private final String secretAlgorithm;
private final String cypherTransformation;
private final byte[] serverPubKeyEnc;
public Challenge(String serverRealm, String secretAlgorithm, String cypherTransformation, byte[] serverPubKeyEnc)
{
this.serverRealm = serverRealm;
this.secretAlgorithm = secretAlgorithm;
this.cypherTransformation = cypherTransformation;
this.serverPubKeyEnc = serverPubKeyEnc;
}
public Challenge(ExtendedDataInput in) throws IOException
{
serverRealm = in.readString();
secretAlgorithm = in.readString();
cypherTransformation = in.readString();
serverPubKeyEnc = in.readByteArray();
}
public void write(ExtendedDataOutput out) throws IOException
{
out.writeString(serverRealm);
out.writeString(secretAlgorithm);
out.writeString(cypherTransformation);
out.writeByteArray(serverPubKeyEnc);
}
public String getServerRealm()
{
return serverRealm;
}
public String getSecretAlgorithm()
{
return secretAlgorithm;
}
public String getCypherTransformation()
{
return cypherTransformation;
}
public byte[] getServerPubKeyEnc()
{
return serverPubKeyEnc;
}
}
}
/**
* Executes the client-side of the Diffie-Hellman key agreement protocol.
*
* @author Eike Stepper
*/
public static class Client
{
public Client()
{
}
public Response handleChallenge(Server.Challenge challenge, byte[] clearText)
{
try
{
KeyFactory keyFactory = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(challenge.getServerPubKeyEnc());
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// Use the DH parameters associated with the server's public key to generate own key pair
DHParameterSpec dhParamSpec = ((DHPublicKey)pubKey).getParams();
// Create own DH key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
keyPairGenerator.initialize(dhParamSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// Create and initialize DH KeyAgreement object
KeyAgreement clientKeyAgree = KeyAgreement.getInstance("DH");
clientKeyAgree.init(keyPair.getPrivate());
// Encode public key
byte[] pubKeyEnc = keyPair.getPublic().getEncoded();
// Use server's public key for the first (and only) phase of his version of the DH protocol
clientKeyAgree.doPhase(pubKey, true);
SecretKey sharedSecret = clientKeyAgree.generateSecret(challenge.getSecretAlgorithm());
// Encrypt
Cipher clientCipher = Cipher.getInstance(challenge.getCypherTransformation());
clientCipher.init(Cipher.ENCRYPT_MODE, sharedSecret);
byte[] ciphertext = clientCipher.doFinal(clearText);
// Retrieve the parameter that was used, and transfer it to the server in encoded format
AlgorithmParameters params = clientCipher.getParameters();
byte[] paramsEnc = params == null ? null : params.getEncoded();
return new Response(pubKeyEnc, ciphertext, paramsEnc);
}
catch (GeneralSecurityException ex)
{
throw new SecurityException(ex);
}
catch (IOException ex)
{
throw new IORuntimeException(ex);
}
}
/**
* @author Eike Stepper
*/
public static final class Response
{
private final byte[] clientPubKeyEnc;
private final byte[] cipherText;
private final byte[] paramsEnc;
public Response(byte[] clientPubKeyEnc, byte[] cipherText, byte[] paramsEnc)
{
this.clientPubKeyEnc = clientPubKeyEnc;
this.cipherText = cipherText;
this.paramsEnc = paramsEnc;
}
public Response(ExtendedDataInput in) throws IOException
{
clientPubKeyEnc = in.readByteArray();
cipherText = in.readByteArray();
paramsEnc = in.readByteArray();
}
public void write(ExtendedDataOutput out) throws IOException
{
out.writeByteArray(clientPubKeyEnc);
out.writeByteArray(cipherText);
out.writeByteArray(paramsEnc);
}
public byte[] getClientPubKeyEnc()
{
return clientPubKeyEnc;
}
public byte[] getCipherText()
{
return cipherText;
}
public byte[] getParamsEnc()
{
return paramsEnc;
}
}
}
/**
* @author Eike Stepper
*/
public static final class SkipParameterSpec
{
/**
* The 1024 bit Diffie-Hellman modulus values used by SKIP
*/
private static final byte skip1024ModulusBytes[] = { (byte)0xF4, (byte)0x88, (byte)0xFD, (byte)0x58, (byte)0x4E,
(byte)0x49, (byte)0xDB, (byte)0xCD, (byte)0x20, (byte)0xB4, (byte)0x9D, (byte)0xE4, (byte)0x91, (byte)0x07,
(byte)0x36, (byte)0x6B, (byte)0x33, (byte)0x6C, (byte)0x38, (byte)0x0D, (byte)0x45, (byte)0x1D, (byte)0x0F,
(byte)0x7C, (byte)0x88, (byte)0xB3, (byte)0x1C, (byte)0x7C, (byte)0x5B, (byte)0x2D, (byte)0x8E, (byte)0xF6,
(byte)0xF3, (byte)0xC9, (byte)0x23, (byte)0xC0, (byte)0x43, (byte)0xF0, (byte)0xA5, (byte)0x5B, (byte)0x18,
(byte)0x8D, (byte)0x8E, (byte)0xBB, (byte)0x55, (byte)0x8C, (byte)0xB8, (byte)0x5D, (byte)0x38, (byte)0xD3,
(byte)0x34, (byte)0xFD, (byte)0x7C, (byte)0x17, (byte)0x57, (byte)0x43, (byte)0xA3, (byte)0x1D, (byte)0x18,
(byte)0x6C, (byte)0xDE, (byte)0x33, (byte)0x21, (byte)0x2C, (byte)0xB5, (byte)0x2A, (byte)0xFF, (byte)0x3C,
(byte)0xE1, (byte)0xB1, (byte)0x29, (byte)0x40, (byte)0x18, (byte)0x11, (byte)0x8D, (byte)0x7C, (byte)0x84,
(byte)0xA7, (byte)0x0A, (byte)0x72, (byte)0xD6, (byte)0x86, (byte)0xC4, (byte)0x03, (byte)0x19, (byte)0xC8,
(byte)0x07, (byte)0x29, (byte)0x7A, (byte)0xCA, (byte)0x95, (byte)0x0C, (byte)0xD9, (byte)0x96, (byte)0x9F,
(byte)0xAB, (byte)0xD0, (byte)0x0A, (byte)0x50, (byte)0x9B, (byte)0x02, (byte)0x46, (byte)0xD3, (byte)0x08,
(byte)0x3D, (byte)0x66, (byte)0xA4, (byte)0x5D, (byte)0x41, (byte)0x9F, (byte)0x9C, (byte)0x7C, (byte)0xBD,
(byte)0x89, (byte)0x4B, (byte)0x22, (byte)0x19, (byte)0x26, (byte)0xBA, (byte)0xAB, (byte)0xA2, (byte)0x5E,
(byte)0xC3, (byte)0x55, (byte)0xE9, (byte)0x2F, (byte)0x78, (byte)0xC7 };
/**
* The SKIP 1024 bit modulus
*/
private static final BigInteger skip1024Modulus = new BigInteger(1, skip1024ModulusBytes);
/**
* The base used with the SKIP 1024 bit modulus
*/
private static final BigInteger skip1024Base = BigInteger.valueOf(2);
public static final DHParameterSpec INSTANCE = new DHParameterSpec(skip1024Modulus, skip1024Base);
}
/**
* Creates Diffie-Hellman parameters.
*
* @author Eike Stepper
*/
public static final class ParameterSpecGenerator
{
/**
* Create Diffie-Hellman parameters.
* <p>
* Takes VERY long...
*/
public static DHParameterSpec generate(int bits)
{
try
{
AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DH");
paramGen.init(bits);
AlgorithmParameters params = paramGen.generateParameters();
return params.getParameterSpec(DHParameterSpec.class);
}
catch (GeneralSecurityException ex)
{
throw new SecurityException(ex);
}
}
}
}