0

I'm trying to generate a private/public key pair for an elliptic curve algorithm, from a given public key of a certificate.

I tried to implement it like so:

        ECPublicKeyParameters pubKey = (ECPublicKeyParameters)cert.getPublicKey();
        ECKeyPairGenerator gen = new ECKeyPairGenerator();
        gen.init(new ECKeyGenerationParameters(pubKey.getParameters(), new SecureRandom()));
        AsymmetricCipherKeyPair ks = gen.generateKeyPair();

but I get a runtime casting error because of the first line:

Exception in thread "main" java.lang.ClassCastException: class sun.security.ec.ECPublicKeyImpl cannot be cast to class org.bouncycastle.crypto.params.ECPublicKeyParameters (sun.security.ec.ECPublicKeyImpl is in module jdk.crypto.ec of loader 'platform'; org.bouncycastle.crypto.params.ECPublicKeyParameters is in unnamed module of loader 'app')

cert is an object of type X509Certificate I'm using the package of bouncycastle

EDIT: THIS IS AN IMPLEMENTATION I HAVE TRIED:

public CryptoContext(X509Certificate serverCertificate) {
    ECPublicKeyParameters serverPubKey = getServerPublicKey(serverCertificate);
    AsymmetricCipherKeyPair keyPair = generateKeyPair(serverPubKey);
    byte[] sharedSecret = generateSharedSecret(serverPubKey, keyPair.getPrivate());
}

private ECPublicKeyParameters getServerPublicKey(X509Certificate cert) {
    byte[] public_key_bytes = cert.getPublicKey().getEncoded();
    ECPublicKeyParameters serverPubKey = null;
    try {
        serverPubKey = (ECPublicKeyParameters) PublicKeyFactory.createKey(public_key_bytes);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return serverPubKey;
}

private AsymmetricCipherKeyPair generateKeyPair(ECPublicKeyParameters serverPubKey) {

    ECKeyPairGenerator gen = new ECKeyPairGenerator();
    gen.init(new ECKeyGenerationParameters(serverPubKey.getParameters(), new SecureRandom()));
    AsymmetricCipherKeyPair keyPair = gen.generateKeyPair();

    return keyPair;
}

private byte[] generateSharedSecret(ECPublicKeyParameters serverPubKey, AsymmetricKeyParameter clientPrivate) {
    BasicAgreement agree = new ECDHBasicAgreement();
    agree.init(clientPrivate);
    BigInteger agreementValue = agree.calculateAgreement(serverPubKey);
    byte[] sharedSecret = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), agreementValue);

    return sharedSecret;
}

But I don't get any response from the server when trying to send back the sharedSecret, therefore I think I have something wrong on the way.

Finer
  • 19
  • 1
  • 8

1 Answers1

0

The generation of a sharedSecret is done with the two components of (your own) privateKey and the (third party) public key - here the public key is taken from the (third party) certificate. To get the sharedSecret you don't rebuild a keyPair (because they aren't a pair) but use a KeyAgreement with instance "ECDH".

Below you find a full working sample program that takes the private key and certificate in PEM-format for both parties (usually named as 'alice' and 'bob'). On both sides the PrivateKey and the PublicKey were rebuild and in the end the sharedSecret is generated with "mixed" keys (first shared secret: private key from alice and public key from bob, second shared secret: private key from bob and public key from alice). Both sharedSecrets are identical and are good for a AES-encryption with a key length 32 byte/256 bit.

Another note: this example runs without Bouncy Castle dependencies.

There is no proper exception handling and the using of fixed (build in) private key is UNSECURE.

The keys were generated with Java keytool and exported with openssl into PEM-format:

keytool -genkeypair -keysize 256 -sigalg SHA256withECDSA -keyalg EC -dname "cn=Alice" -alias alice -keypass apassword  -keystore alice_keystore.pkcs12 -storetype pkcs12  -storepass apassword -validity 3650
openssl pkcs12 -in alice_keystore.pkcs12  -nokeys -out alice_crt.pem
openssl pkcs12 -in alice_keystore.pkcs12  -nodes -nocerts -out alice_privatekey.pem
[password: apassword]

result:

Example Key Exchange EC Curves without BC
alice crt: [
  Version: V3
  Subject: CN=Alice
  Signature Algorithm: SHA256withECDSA, OID = 1.2.840.10045.4.3.2
  Key:  Sun EC public key, 256 bits
  public x coord: 53193002007562396012681859968974283073474881849394375511693405764629717680344
  public y coord: 67114127612689688829965470157066506514867660192432128210506355717815080266028
  parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)
  Validity: [From: Sat Aug 08 18:16:47 CEST 2020,
               To: Tue Aug 06 18:16:47 CEST 2030]
  Issuer: CN=Alice
  SerialNumber: [    57c8f4ae]
...

bob crt: [
  Version: V3
  Subject: CN=Bob
  Signature Algorithm: SHA256withECDSA, OID = 1.2.840.10045.4.3.2
  Key:  Sun EC public key, 256 bits
  public x coord: 53218839282383347527983259020322984395517469483615530947798768625444790545484
  public y coord: 50373539334339208285989254642248018747424962690612261530706567696443441910381
  parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)
  Validity: [From: Sat Aug 08 20:22:39 CEST 2020,
               To: Tue Aug 06 20:22:39 CEST 2030]
  Issuer: CN=Bob
  SerialNumber: [    5c814597]
...

alice & bob sharedSecret equals: true
sharedSecret length: 32 data: c4adaea9b1f3cdf8bede4f3f51546c8bb5a33158f8299e6692c20c2de6109c16

code:

import javax.crypto.KeyAgreement;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.regex.Pattern;

public class EllipticCurveKeyExchange {
    public static void main(String[] args) throws GeneralSecurityException, IOException {
        System.out.println("Example Key Exchange EC Curves without BC");
        // all keys are sample keys
        // certificate and private key for alice
        String alice_cert_pem = "-----BEGIN CERTIFICATE-----\n" +
                "MIIBNjCB26ADAgECAgRXyPSuMAwGCCqGSM49BAMCBQAwEDEOMAwGA1UEAxMFQWxp\n" +
                "Y2UwHhcNMjAwODA4MTYxNjQ3WhcNMzAwODA2MTYxNjQ3WjAQMQ4wDAYDVQQDEwVB\n" +
                "bGljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHWaLBYzJU0/WTsIWOXhQrNG\n" +
                "NFypOpTI1UOO3k+S0YDYlGFABWPdALUfjk00SAsW5xpgEW2WRsnKgjO6IVMLKSyj\n" +
                "ITAfMB0GA1UdDgQWBBQN9LQY8o7xlOWtJVmtAg84SZdz7jAMBggqhkjOPQQDAgUA\n" +
                "A0gAMEUCIGwjEeUFk9Qv0dRvUsnJgmokpAfQ9ISfofkskanz8L9CAiEAvh7SbBd5\n" +
                "A/50QbKp+73M+Sy1jmXqD+IEfkI0YDWtDac=\n" +
                "-----END CERTIFICATE-----";
        String alice_privateKey_pem = "-----BEGIN PRIVATE KEY-----\n" +
                "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCB+NAwfARVZ6KymEgd4\n" +
                "D4DQjX0WomJDKZ7fUTDrNJtOqQ==\n" +
                "-----END PRIVATE KEY-----";

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        ByteArrayInputStream bis_alice = new ByteArrayInputStream(loadPEM(alice_cert_pem));
        X509Certificate alice_cert = (X509Certificate) cf.generateCertificate(bis_alice);
        System.out.println("alice crt: " + alice_cert);
        PublicKey alice_publicKey = alice_cert.getPublicKey();
        KeyFactory kf = KeyFactory.getInstance("EC");
        PrivateKey alice_privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(loadPEM(alice_privateKey_pem)));
        System.out.println("alice publicKey: " + alice_publicKey);
        System.out.println("alice privateKey: " + alice_privateKey);

        // certificate and private key for bob
        String bob_cert_pem = "-----BEGIN CERTIFICATE-----\n" +
                "MIIBMzCB16ADAgECAgRcgUWXMAwGCCqGSM49BAMCBQAwDjEMMAoGA1UEAxMDQm9i\n" +
                "MB4XDTIwMDgwODE4MjIzOVoXDTMwMDgwNjE4MjIzOVowDjEMMAoGA1UEAxMDQm9i\n" +
                "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdajLq/yS6RMr+Pj745/mZ3gEDin8\n" +
                "Idea+8VfIMepLExvXmmzTun18700D8tFJAhRk7PgkoRhztGgbzrXIaOSbaMhMB8w\n" +
                "HQYDVR0OBBYEFDJqdrcRZD7noaNlKcYncfO+YdDIMAwGCCqGSM49BAMCBQADSQAw\n" +
                "RgIhAOW7Asmky4fHL4/luoc0F1IuDUhXtS8iASKAaYHL72m8AiEA5reQE+JK93l9\n" +
                "5+oPb2wxhD7QINZxShleE7qjBngT6io=\n" +
                "-----END CERTIFICATE-----";
        String bob_privateKey_pem = "-----BEGIN PRIVATE KEY-----\n" +
                "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDv9KwdBlhxGukLkXp5\n" +
                "UfMTddh+h00pEpMcvR3qsZYVyA==\n" +
                "-----END PRIVATE KEY-----";

        ByteArrayInputStream bis_bob = new ByteArrayInputStream(loadPEM(bob_cert_pem));
        X509Certificate bob_cert = (X509Certificate) cf.generateCertificate(bis_bob);
        System.out.println("bob crt: " + bob_cert);
        PublicKey bob_publicKey = bob_cert.getPublicKey();
        PrivateKey bob_privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(loadPEM(bob_privateKey_pem)));
        System.out.println("bob publicKey: " + bob_publicKey);
        System.out.println("bob privateKey: " + bob_privateKey);

        // generate sharedSecret for alice & bob
        byte[] alice_sharedSecret = createEcdhSharedSecret(alice_privateKey, bob_publicKey);
        byte[] bob_sharedSecret = createEcdhSharedSecret(bob_privateKey, alice_publicKey);
        System.out.println("\nalice & bob sharedSecret equals: " + Arrays.equals(alice_sharedSecret, bob_sharedSecret));
        System.out.println("sharedSecret length: " + alice_sharedSecret.length + " data: " + bytesToHex(alice_sharedSecret));
    }

    // https://stackoverflow.com/a/49753179/8166854
    private static byte[] loadPEM (String resource) throws IOException {
        byte[] input = resource.getBytes(StandardCharsets.ISO_8859_1);
        // URL url = getClass().getResource(resource);
        // InputStream in = url.openStream();
        ByteArrayInputStream in = new ByteArrayInputStream(input);
        String pem = new String(in.readAllBytes(), StandardCharsets.ISO_8859_1);
        Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN.*---*$(.*)^---*END.*---*$.*");
        String encoded = parse.matcher(pem).replaceFirst("$1");
        return Base64.getMimeDecoder().decode(encoded);
    }

    public static byte[] createEcdhSharedSecret(PrivateKey privateKey, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException {
        KeyAgreement keyAgree = KeyAgreement.getInstance("ECDH");
        keyAgree.init(privateKey);
        keyAgree.doPhase(publicKey, true);
        return keyAgree.generateSecret();
    }

    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();
    }
}
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40
  • Thank you for the explanation. I explored it more a bit, and I tried another implementation which doesn't seem to work, but fom an overlook it looks like what you have done, so I am confused why it's not working. I can't use your implementation since I can't create private keys like you did. – Finer Aug 11 '20 at 20:18
  • @Finer: In your question you said you got the certificate from a third party but you never explained/showed in what format you got the key. In my example I used the string/PEM representation of a certificate but you could easily load the certificate from disc. – Michael Fehr Aug 11 '20 at 21:30
  • How can I get the format of the key so I can show it to you? – Finer Aug 12 '20 at 08:19
  • @Finer: does your keys start with '-----BEGIN' or do you the keys in binary form (as encoded keys)? – Michael Fehr Aug 12 '20 at 08:51
  • I got it in binary form – Finer Aug 12 '20 at 10:22
  • You can use openssl for converting a certificate in binary form to "string/PEM"-format as shown in the example: openssl x509 -inform der -in bob_cert.der -out bob_crt2.pem - the file bob_crt2.pem contains the certificate as used above. – Michael Fehr Aug 12 '20 at 11:11