1

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?
Neonocker
  • 13
  • 4
  • 1
    better to use ephemeral keys for ECDH/DH and shared secret agreement, which are signed by a "identity" signature key – Woodstock May 19 '20 at 13:34
  • 1
    A DH public key can appear in a certificate, but then that certificate can't be self-signed. You can create a self-signed CA certificate using a supported signing algorithm like RSA or ECDSA, and then create a certificate containing the DH public key and sign it with the CA private key. However, what is normally done is what @Woodstock comment suggests. Ephemeral ECDH/DH provides important security benefits that static DH cannot provide. – President James K. Polk May 19 '20 at 16:56
  • Thanks for your comments and the heads-up regarding the security benefits of ECDH/DH. I went with this for my program now and that works, however, I assume that if you'd want to store a DH public key in a KeyStore, the approach presented by @PresidentJamesK.Polk is a possible solution. If you'd post this as a short answer I could mark my question resolved. :) – Neonocker May 20 '20 at 07:04

1 Answers1

1

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
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40