Reputation: 3201
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
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