4

I get a 64 uncompressed public key and need to run ECDH to generate a shared secret. In order to call ECDH I need to convert the byte array to PublicKey and I am using the following code I have found in this forum:

public static void setOtherPublicKey(byte[] publicKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException
{       

    try {
        //EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
        KeyFactory generator = KeyFactory.getInstance("EC");
        //PrivateKey privateKey = generator.generatePrivate(privateKeySpec);

        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        blePubKey = generator.generatePublic(publicKeySpec);
    } catch (Exception e) {
        throw new IllegalArgumentException("Failed to create KeyPair from provided encoded keys", e);
    }
}

This code throws an InvalidKeySpecException.

As example, the public key of the other party is:

9b5e9a5a971877530c9cadbbea93c2ee2483d65052678f745bad79f110173520
54019832e11376537a76c4defd0b3dfdc667a974239147f323cdcfd2baa39892

Adding the code after getting the answers below:

public static void setOtherPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException
{
    // first generate key pair of your own   
    ECPublicKey pubKey = (ECPublicKey) SecPage.g_kpA.getPublic();
    ECParameterSpec params = pubKey.getParams();
    int keySizeBytes = params.getOrder().bitLength() / Byte.SIZE;

    // get the other party 64 bytes
    //byte [] otherPub = crypto.getBlePubKeyBytes();

    byte[] otherPub = hexStringToByteArray("ac2bdd28fce5c7b181b34f098b0934742281246ed907a5f646940c1edcb724e7c7358356aebea810322a8e324cc77f376df4cabd754110ad41ec178c0a6b8e5f");
    ByteArrayBuffer xBytes = new ByteArrayBuffer(33);
    ByteArrayBuffer yBytes = new ByteArrayBuffer(33);

    byte[] zero = {(byte)0x00};
    xBytes.append(zero, 0, 1);
    xBytes.append(otherPub, 0, 32);
    yBytes.append(zero, 0, 1);
    yBytes.append(otherPub, 32, 32);


    // generate the public key point    
    BigInteger x = new BigInteger(xBytes.buffer());
    BigInteger y = new BigInteger(yBytes.buffer());

    ECPoint w  = new ECPoint(x, y);

    // generate the key of the other side
    ECPublicKeySpec otherKeySpec = new ECPublicKeySpec(w  , params);
    KeyFactory keyFactory = KeyFactory.getInstance("EC");
    blePubKey = (ECPublicKey) keyFactory.generatePublic(otherKeySpec);
}
Simon
  • 509
  • 7
  • 25
  • 1
    Can you include the public key in your question, please. – Duncan Jones Aug 22 '14 at 10:17
  • updated the question with the public key – Simon Aug 22 '14 at 21:13
  • The other side uses NIST_P256. I get the 64 bytes and got this error. I tried adding 0x04 at the beginning so it's 65 bytes with the 04 but still get the same error. The other curve that the other party can use is BrainPoolP256r1. On my side I am using secp256r1 – Simon Aug 23 '14 at 09:09
  • Note that your domain parameters need to match. Bouncy castle has the brain pool parameters. – Maarten Bodewes Aug 25 '14 at 20:37

1 Answers1

9

Well, whaty'know, you can actually do this... explanation in the comments.

public class ECDHPub {

    private static ECPublicKey decodeECPublicKey(ECParameterSpec params,
            final byte[] pubkey) throws NoSuchAlgorithmException,
            InvalidKeySpecException {
        int keySizeBytes = params.getOrder().bitLength() / Byte.SIZE;

        int offset = 0;
        BigInteger x = new BigInteger(1, Arrays.copyOfRange(pubkey, offset,
                offset + keySizeBytes));
        offset += keySizeBytes;
        BigInteger y = new BigInteger(1, Arrays.copyOfRange(pubkey, offset,
                offset + keySizeBytes));
        ECPoint w = new ECPoint(x, y);

        ECPublicKeySpec otherKeySpec = new ECPublicKeySpec(w, params);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        ECPublicKey otherKey = (ECPublicKey) keyFactory
                .generatePublic(otherKeySpec);
        return otherKey;
    }

    private static byte[] encodeECPublicKey(ECPublicKey pubKey) {
        int keyLengthBytes = pubKey.getParams().getOrder().bitLength()
                / Byte.SIZE;
        byte[] publicKeyEncoded = new byte[2 * keyLengthBytes];

        int offset = 0;

        BigInteger x = pubKey.getW().getAffineX();
        byte[] xba = x.toByteArray();
        if (xba.length > keyLengthBytes + 1 || xba.length == keyLengthBytes + 1
                && xba[0] != 0) {
            throw new IllegalStateException(
                    "X coordinate of EC public key has wrong size");
        }

        if (xba.length == keyLengthBytes + 1) {
            System.arraycopy(xba, 1, publicKeyEncoded, offset, keyLengthBytes);
        } else {
            System.arraycopy(xba, 0, publicKeyEncoded, offset + keyLengthBytes
                    - xba.length, xba.length);
        }
        offset += keyLengthBytes;

        BigInteger y = pubKey.getW().getAffineY();
        byte[] yba = y.toByteArray();
        if (yba.length > keyLengthBytes + 1 || yba.length == keyLengthBytes + 1
                && yba[0] != 0) {
            throw new IllegalStateException(
                    "Y coordinate of EC public key has wrong size");
        }

        if (yba.length == keyLengthBytes + 1) {
            System.arraycopy(yba, 1, publicKeyEncoded, offset, keyLengthBytes);
        } else {
            System.arraycopy(yba, 0, publicKeyEncoded, offset + keyLengthBytes
                    - yba.length, yba.length);
        }

        return publicKeyEncoded;
    }

    public static void main(String[] args) throws Exception {

        // (only) required for named curves other than those used in JCE
        Security.addProvider(new BouncyCastleProvider());

        // create local and remote key
        KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "BC");
        ECGenParameterSpec genspec = new ECGenParameterSpec("brainpoolp256r1");
        kpgen.initialize(genspec);
        KeyPair localKeyPair = kpgen.generateKeyPair();
        KeyPair remoteKeyPair = kpgen.generateKeyPair();

        // test generation
        byte[] encodedRemotePublicKey = encodeECPublicKey((ECPublicKey) remoteKeyPair
                .getPublic());
        // test creation
        ECPublicKey remoteKey = decodeECPublicKey(
                ((ECPublicKey) localKeyPair.getPublic()).getParams(),
                encodedRemotePublicKey);

        // local key agreement
        KeyAgreement localKA = KeyAgreement.getInstance("ECDH");
        localKA.init(localKeyPair.getPrivate());
        localKA.doPhase(remoteKey, true);
        byte[] localSecret = localKA.generateSecret();

        // remote key agreement
        KeyAgreement remoteKA = KeyAgreement.getInstance("ECDH");
        remoteKA.init(remoteKeyPair.getPrivate());
        remoteKA.doPhase((ECPublicKey) localKeyPair.getPublic(), true);
        byte[] remoteSecret = localKA.generateSecret();

        // validation
        System.out.println(Arrays.equals(localSecret, remoteSecret));
    }
}
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • I have a very similar code working. Now I need to get the 64 bytes of public key from the other party via network and use it for ECDH. How can I generate an ECPublicKey with those params and the external 64 bytes? – Simon Aug 26 '14 at 06:05
  • 1
    In short, get the BigInteger values and convert them to unsigned, big endian, fixed length octet strings (byte arrays) of the key size (size of the order, see above). Don't forget, they may be bigger (single 00h byte) or smaller than key size. – Maarten Bodewes Aug 26 '14 at 07:22
  • Owlstead, my understanding is that the shared secret should notbe used directly. Can you please send a reference to the best practice to exchange randoms so both sides generate the same AES key per session? – Simon Aug 28 '14 at 17:53
  • The best way is to feed the result into a KDF. Java is rather void of good KDF's bar PBKDF2. You could use that with a static salt for each key you want to derive from a secret, and an iteration count of 1. Otherwise use HKDF from Bouncy (you only need the expand part). This will require more research though. – Maarten Bodewes Aug 28 '14 at 17:59
  • Owlstead, how strong is taking the shared secret (aSecret) and generating AES key by calling SecretKeySpec secret = new SecretKeySpec(aSecret, "AES"); – Simon Sep 17 '14 at 12:54
  • 1
    I think that the default Java output is actually a DER structure (check if the first byte is 30h). If it is, you could use the X coordinate. Otherwise it should be quite safe. But running it through a KDF or if thats not abailable, SHA256, should be preferred. – Maarten Bodewes Sep 17 '14 at 15:02
  • Once I have a common AES key for both sides, does sharing the same IV for encryption in an open way that anyone can see put in risk the encrypted data? – Simon Sep 23 '14 at 04:29
  • 1
    In short: no. Note that for CBC, the IV should be unpredictable to an attacker though. Although there are other schemes, in general `SecureRandom` data should be used. – Maarten Bodewes Sep 23 '14 at 07:41