Search code examples
javacryptographykeystorex509diffie-hellman

Storing a Diffie-Hellman key pair for reuse in a KeyStore in Java


I'm currently writing a cryptographic Java program for which I implement a key exchange, so that two users with a running instance of the program (which don't have to run simultaneously) can agree on a shared secret key for AES encryption. I planned to use the Diffie Hellman key exchange protocol for this.

Therefore I generally followed this example by Oracle, with the addition of implementing Alice's and Bob's parts in different methods of the program. In this example, what Alice and Bob exchange is their encoded public keys

byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();
byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();

respectively. In order to transmit these encoded public keys, I saved these byte arrays as files for each user to transmit it to the other user.

Now I want to handle the case that the user initiating the key exchange, say Alice, closes the program while waiting for the response of the other user, sending back their encoded public key as a file. On restarting the program, Alice would like to compute the shared secret key based on the public key received from Bob, and her own private key, which has to be stored somewhere while she had closed the program. Because my program already uses a PKCS12-KeyStore, I thought I could save the Diffie-Hellman key pair to that KeyStore.

Therefore, I followed the answer to this question with the approach of using a self-signed X509 certificate to store a RSA key pair. However, this obviously throws the error org.bouncycastle.operator.OperatorCreationException: cannot create signer: Supplied key (com.sun.crypto.provider.DHPrivateKey) is not a RSAPrivateKey instance for the RSA signature algorithm:

String signatureAlgorithm = "SHA256WithRSA";

ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm)
        .setProvider(bcProvider).build(keyPair.getPrivate());

After I initialized the key pair with

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DH");
keyPairGen.initialize(bitLength);
KeyPair keyPair = keyPairGen.generateKeyPair();

Now to solve this, is there a way to either:

  1. Sign a X509 certificate differently, so that an Diffie-Hellman key pair can be stored?
  2. Store a Diffie-Hellman key pair in a KeyStore using a different approach?
  3. Store a Diffie-Hellman key pair securely elsewhere than in a KeyStore?
  4. Or use another way of key exchange protocol together with the requirement of storing the intermediate values in a KeyStore?

Solution

  • I used this full example solution in my project so it might be usefull for others. It's using a EC-Keypair (curve "secp256r1") and ECDH for KeyExchange. You need BouncyCastle and beware that there is no proper exception handling and that existing keystores will be overwritten without notice.

    import org.bouncycastle.asn1.x500.X500Name;
    import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
    import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.operator.ContentSigner;
    import org.bouncycastle.operator.OperatorCreationException;
    import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
    
    import javax.crypto.KeyAgreement;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.math.BigInteger;
    import java.security.*;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import java.security.spec.ECGenParameterSpec;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Arrays;
    import java.util.Calendar;
    import java.util.Date;
    
    public class StoreEcdhKeyInPKCS12KeystoreSO {
        public static void main(String[] args) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, OperatorCreationException, CertificateException, KeyStoreException, IOException, UnrecoverableKeyException, InvalidKeySpecException, InvalidKeyException {
            System.out.println("Storing a ECDH Keypair in a PKCS12 Keystore");
            // you need BouncyCastle, get it from https://www.bouncycastle.org/latest_releases.html
            Security.addProvider(new BouncyCastleProvider());
            System.out.println("\nJava version: " + Runtime.version() + " BouncyCastle Version: " + Security.getProvider("BC"));
            // ### WARNING: no exception handling, existing keystores will be overwritten without notice ###
            String ecCurvename = "secp256r1";
    
            // alice's credentials
            String aliceKeystoreFilename = "alicekeystore.p12";
            char[] aliceKeystoreEntryPassword = "aliceEntryPassword".toCharArray();
            String aliceKeypairAlias = "aliceKeypairAlias";
            char[] aliceKeypairPassword = "aliceKeypairPassword".toCharArray();
            KeyPair aliceKeyPairGenerated;
            PrivateKey alicePrivateKeyLoaded;
            byte[] aliceReceivedPublicKeyFromBob;
            byte[] aliceSharedSecret;
    
            // bob's credentials
            String bobKeystoreFilename = "bobkeystore.p12";
            char[] bobKeystoreEntryPassword = "bobEntryPassword".toCharArray();
            String bobKeypairAlias = "bobKeypairAlias";
            char[] bobKeypairPassword = "bobKeypairPassword".toCharArray();
            KeyPair bobKeyPairGenerated;
            PrivateKey bobPrivateKeyLoaded;
            byte[] bobReceivedPublicKeyFromAlice;
            byte[] bobSharedSecret;
    
            // alice start keypair generation, comment out if you still have a keystore with the keys
            aliceKeyPairGenerated = generateEcdhKeyPair(ecCurvename);
            // save keypair
            storeEcdhKeypairInPKCS12Keystore(aliceKeystoreFilename, aliceKeystoreEntryPassword, aliceKeypairAlias, aliceKeypairPassword, aliceKeyPairGenerated);
            System.out.println("alice has a new keystore: " + aliceKeystoreFilename);
    
            // bob start keypair generation, comment out if you still have a keystore with the keys
            bobKeyPairGenerated = generateEcdhKeyPair(ecCurvename);
            storeEcdhKeypairInPKCS12Keystore(bobKeystoreFilename, bobKeystoreEntryPassword, bobKeypairAlias, bobKeypairPassword, bobKeyPairGenerated);
            System.out.println("bob has a new keystore: " + bobKeystoreFilename);
    
            // alice sends her public key to bob -e.g. you could code it with base64, here we're just cloning the key
            bobReceivedPublicKeyFromAlice = aliceKeyPairGenerated.getPublic().getEncoded().clone();
    
            // later on - bob received the the public key from alice and loads his key from keystore
            bobPrivateKeyLoaded = loadEcdhPrivateKeyFromPKCS12Keystore(bobKeystoreFilename, bobKeystoreEntryPassword, bobKeypairAlias, bobKeypairPassword);
            // bob creates the shared secret with public key from alice
            bobSharedSecret = createEcdhSharedSecret(bobPrivateKeyLoaded, bobReceivedPublicKeyFromAlice);
            System.out.println("SharedSecret Bob:   " + bytesToHex(bobSharedSecret));
    
            // bob sends his public key to alice -e.g. you could code it with base64, here we're just cloning the key
            aliceReceivedPublicKeyFromBob = loadEcdhPublicKeyFromPKCS12Keystore(bobKeystoreFilename, bobKeystoreEntryPassword, bobKeypairAlias, bobKeypairPassword).getEncoded().clone();
    
            // alice loads her private key from keystore and generates the SecretShare
            alicePrivateKeyLoaded = loadEcdhPrivateKeyFromPKCS12Keystore(aliceKeystoreFilename, aliceKeystoreEntryPassword, aliceKeypairAlias, aliceKeypairPassword);
            aliceSharedSecret = createEcdhSharedSecret(alicePrivateKeyLoaded, aliceReceivedPublicKeyFromBob);
            System.out.println("SharedSecret Alice: " + bytesToHex(aliceSharedSecret));
    
            // check that both SecretShare's are equal
            System.out.println("Compare aliceSharedSecret and bobSharedSecret: " + Arrays.equals(aliceSharedSecret, bobSharedSecret));
            // do what ever you want with your SharedSecret, e.g. shorten it using SHA256 for a AES encryption key
        }
    
        public static KeyPair generateEcdhKeyPair(String curvenameString) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "SunEC");
            ECGenParameterSpec ecParameterSpec = new ECGenParameterSpec(curvenameString);
            keyPairGenerator.initialize(ecParameterSpec);
            return keyPairGenerator.genKeyPair();
        }
    
        public static void storeEcdhKeypairInPKCS12Keystore(String filename, char[] entryPassword, String keypairAlias, char[] keypairPassword, KeyPair keypair) throws CertificateException, OperatorCreationException, KeyStoreException, IOException, NoSuchAlgorithmException {
            // --- create the self signed cert
            java.security.cert.Certificate cert = createSelfSigned(keypair);
            // --- create a new pkcs12 key store in memory
            KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
            pkcs12.load(null, null);
            // --- create entry in PKCS12
            pkcs12.setKeyEntry(keypairAlias, keypair.getPrivate(), keypairPassword, new Certificate[]{cert});
            // --- store PKCS#12 as file
            try (FileOutputStream p12 = new FileOutputStream(filename)) {
                pkcs12.store(p12, entryPassword);
            } catch (NoSuchAlgorithmException | IOException e) {
                e.printStackTrace();
            }
        }
    
        public static PrivateKey loadEcdhPrivateKeyFromPKCS12Keystore(String filename, char[] entryPassword, String keypairAlias, char[] keypairPassword) throws CertificateException, OperatorCreationException, KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
            // --- read PKCS#12 as file
            KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
            try (FileInputStream p12 = new FileInputStream(filename)) {
                pkcs12.load(p12, entryPassword);
            }
            // --- retrieve private key
            return (PrivateKey) pkcs12.getKey(keypairAlias, keypairPassword);
        }
    
        public static PublicKey loadEcdhPublicKeyFromPKCS12Keystore(String filename, char[] entryPassword, String keypairAlias, char[] keypairPassword) throws CertificateException, OperatorCreationException, KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
            // --- read PKCS#12 as file
            KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
            try (FileInputStream p12 = new FileInputStream(filename)) {
                pkcs12.load(p12, entryPassword);
            }
            // --- retrieve public key
            Certificate cert = pkcs12.getCertificate(keypairAlias);
            return cert.getPublicKey();
        }
    
        public static byte[] createEcdhSharedSecret(PrivateKey privateKey, byte[] publicKeyByte) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
            KeyAgreement keyAgree = KeyAgreement.getInstance("ECDH");
            keyAgree.init(privateKey);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyByte);
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
            keyAgree.doPhase(publicKey, true);
            return keyAgree.generateSecret();
        }
    
        private static X509Certificate createSelfSigned(KeyPair pair) throws OperatorCreationException, CertificateException {
            // source: https://stackoverflow.com/a/50801856/8166854, author Maarten Bodewes
            X500Name dnName = new X500Name("CN=publickeystorageonly");
            BigInteger certSerialNumber = BigInteger.ONE;
            Date startDate = new Date(); // now
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(startDate);
            calendar.add(Calendar.YEAR, 100); // 100 years validity
            Date endDate = calendar.getTime();
            ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithECDSA").build(pair.getPrivate());
            JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, pair.getPublic());
            return new JcaX509CertificateConverter().getCertificate(certBuilder.build(contentSigner));
        }
    
        private static String bytesToHex(byte[] bytes) {
            StringBuffer result = new StringBuffer();
            for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
            return result.toString();
        }
    }
    

    This is the small output:

    Storing a ECDH Keypair in a PKCS12 Keystore
    
    Java version: 11.0.6+8-b520.43 BouncyCastle Version: BC version 1.65
    alice has a new keystore: alicekeystore.p12
    bob has a new keystore: bobkeystore.p12
    SharedSecret Bob:   ab457b66687fcaefca6d648d428a66b1642355be6c8fb5190624043a7de2215c
    SharedSecret Alice: ab457b66687fcaefca6d648d428a66b1642355be6c8fb5190624043a7de2215c
    Compare aliceSharedSecret and bobSharedSecret: true