javacryptographyelliptic-curvediffie-hellmanshared-secret

EC Client and Server shared secret mismatch ( could be due to client pub key not in correct format while generating server shs)


I am trying to generate shared secret using EC named curve and finding mismatch in client vs server shared secret.

Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

// Client

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");  
    ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(ecCurveName);  
    kpg.initialize(ecGenParameterSpec, new SecureRandom());  
    ECPublicKey ephemeralPublicKey = (ECPublicKey) kpg.generateKeyPair().getPublic();  
    ECPrivateKey clientEphemeralPrivateKey =(ECPrivateKey) kpg.generateKeyPair().getPrivate();  

    BigInteger  pointx = ephemeralPublicKey.getW().getAffineX();  
    BigInteger  pointy = ephemeralPublicKey.getW().getAffineY();  
    String eCClientEphemeralPublicKeyString = ("04"+pointx.toString(16)+pointy.toString(16)).toUpperCase();  

    byte[] remoteECCPkBytes = DatatypeConverter.parseBase64Binary(remoteECCPkBase64);  

    KeyFactory keyFactory= KeyFactory.getInstance("EC","BC");  
    X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(remoteECCPkBytes);  
    PublicKey serverECCPublicKey = keyFactory.generatePublic(pkSpec);  

    KeyAgreement ka = KeyAgreement.getInstance("ECDH","BC");  
    ka.init(clientEphemeralPrivateKey);  
    ka.doPhase(serverECCPublicKey, true);  
    SecretKey agreedKey = ka.generateSecret("AES[256]");  
    byte[] sharedSecret  = agreedKey.getEncoded();  

// Server

    String clientEphemeralPKBase64  = java.util.Base64.getEncoder().encodeToString(new BigInteger(eCClientEphemeralPublicKeyString, 16).toByteArray());  
    byte[] clientECPublicKeybytes = DatatypeConverter.parseBase64Binary(clientEphemeralPKBase64);  



    ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec(ecCurveName);  
    ECCurve curve = ecParameterSpec.getCurve();  
    ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(curve.decodePoint(clientECPublicKeybytes), ecParameterSpec);  
    KeyFactory kf = KeyFactory.getInstance("EC","BC");  
    ECPublicKey ecClientPublicKey = (ECPublicKey)kf.generatePublic(pubKeySpec);  

    PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( Base64.decodeBase64(serverprivateKeyBase64));  
    PrivateKey ecServerPrivateKey = kf.generatePrivate(privateKeySpec);  


    KeyAgreement ka = KeyAgreement.getInstance("ECDH","BC");  
    ka.init(ecServerPrivateKey);  
    ka.doPhase(ecClientPublicKey, true);  
    SecretKey agreedKey = ka.generateSecret("AES[256]");  
    byte[] sharedSecret  = agreedKey.getEncoded();  

Solution

  • This is of course deadly:

    ECPublicKey ephemeralPublicKey = (ECPublicKey) kpg.generateKeyPair().getPublic();  
    ECPrivateKey clientEphemeralPrivateKey =(ECPrivateKey) kpg.generateKeyPair().getPrivate();  
    

    If you call generateKeyPair twice your public and private key will not be part of the same key pair.

    You need to create two key pairs, one for the server, one for the client and then communicate the public keys. Creating a public key and immediately tossing away the private key cannot be useful, other than to retrieve the domain parameters in a roundabout way.

    Instead you should do:

    KeyPair clientEphemeralKeyPair = kpg.generateKeyPair();
    ECPublicKey clientEphemeralPublicKey = (ECPublicKey) clientEphemeralKeyPair.getPublic();  
    ECPrivateKey clientEphemeralPrivateKey = (ECPrivateKey) clientEphemeralKeyPair.getPrivate();