| /* |
| * 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); |
| } |
| } |
| } |
| } |