Dmytro Titov
Dmytro Titov

Reputation: 3201

How to generate PublicKey for PrivateKey in X25519?

I'm working with X25519-keys based encryption at the moment.

My question is, basically, how to derive PublicKey from existing X25519 PrivateKey?

I've tried the following:

    protected PublicKey generatePublicKeyFromPrivate(PrivateKey privateKey) throws GeneralSecurityException {
        PublicKey basePublicKey = generatePublicKey(BigInteger.valueOf(9));
        KeyAgreement keyAgreement = KeyAgreement.getInstance(X25519);
        keyAgreement.init(privateKey, new ECGenParameterSpec(X25519));
        keyAgreement.doPhase(basePublicKey, true);
        byte[] bytes = keyAgreement.generateSecret();
        return generatePublicKey(new BigInteger(bytes));
    }

PublicKey is successfully generated. And I even compared this approach with the third-party library approach (Google Tink): generated PubliKeys match.

However, when I try to use Java's KeyPairGenerator to get both PrivateKey and PubliKey at once and then try to generate a PublicKey for that PrivateKey myself, they differ.

    public KeyPair generateX25519KeyPair() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(X25519);
        return keyPairGenerator.generateKeyPair();
    }

I decided to look up the source code of the KeyPairGenerator (XDHKeyPairGenerator implementation) and found the part when they do multiplication:

    /**
     *
     * Multiply an encoded scalar with a point as a BigInteger and return an
     * encoded point. The array k holding the scalar will be pruned by
     * modifying it in place.
     *
     * @param k an encoded scalar
     * @param u the u-coordinate of a point as a BigInteger
     * @return the encoded product
     */
    public byte[] encodedPointMultiply(byte[] k, BigInteger u) {
        pruneK(k);
        ImmutableIntegerModuloP elemU = field.getElement(u);
        return pointMultiply(k, elemU).asByteArray(params.getBytes());
    }

However, the method that is used to separately generate PubliKey uses another multiplication:

    /**
     * Compute a public key from an encoded private key. This method will
     * modify the supplied array in order to prune it.
     */
    public BigInteger computePublic(byte[] k) {
        pruneK(k);
        return pointMultiply(k, this.basePoint).asBigInteger();
    }

As you can see, the difference is that in the first case they apply the asByteArray() method to the result:

     * Returns the little-endian encoding of this' % 2^(8 * len), where this'
     * is the canonical integer value equivalent to this.
     *
     * @param len the length of the desired array
     * @return a byte array of length len containing the result
     */
    default byte[] asByteArray(int len) {
        byte[] result = new byte[len];
        asByteArray(result);
        return result;
    }

So my question really is: why do they do so? Why this "little-endian encoding" is applied to the PublicKey when the KeyPairGenerator is being used, but it's not applied when the PublicKey is derived from the PrivateKey separately.

Upvotes: 0

Views: 695

Answers (1)

Frank Denis
Frank Denis

Reputation: 1501

The public key is computed by multiplying the base point by the "clamped" secret scalar and returning the encoded X coordinate.

Clamping means masking the 3 least significant bits, masking the top bit, and setting the second top bit.

See RFC7748 for details.

But looks like this is more of a programming question, and the actual issue may not have anything to do with X25519. For a given secret, compare the output you get from that code with the same operation implemented with a different library. If the public keys match, the issue is somewhere else.

Upvotes: 2

Related Questions