javakotlined25519java-15

Ed25519 in JDK 15, Parse public key from byte array and verify


Since Ed25519 has not been around for long (in JDK), there are very few resources on how to use it.

While their example is very neat and useful, I have some trouble understanding what am I doing wrong regarding key parsing.

They Public Key is being read from a packet sent by an iDevice.

(Let's just say, it's an array of bytes)

From the searching and trying my best to understand how the keys are encoded, I stumbled upon this message.

   4.  The public key A is the encoding of the point [s]B.  First,
       encode the y-coordinate (in the range 0 <= y < p) as a little-
       endian string of 32 octets.  The most significant bit of the
       final octet is always zero.  To form the encoding of the point
       [s]B, copy the least significant bit of the x coordinate to the
       most significant bit of the final octet.  The result is the
       public key.

That means that if I want to get y and isXOdd I have to do some work. (If I understood correctly)

Below is the code for it, yet the verifying still fails.

I think, I did it correctly by reversing the array to get it back into Big Endian for BigInteger to use.

My questions are:

  1. Is this the correct way to parse the public key from byte arrays
  2. If it is, what could possibly be the reason for it to fail the verifying process?

// devicePublicKey: ByteArray
val lastIndex = devicePublicKey.lastIndex
val lastByte = devicePublicKey[lastIndex]
val lastByteAsInt = lastByte.toInt()
val isXOdd = lastByteAsInt.and(255).shr(7) == 1

devicePublicKey[lastIndex] = (lastByteAsInt and 127).toByte()

val y = devicePublicKey.reversedArray().asBigInteger

val keyFactory = KeyFactory.getInstance("Ed25519")
val nameSpec = NamedParameterSpec.ED25519
val point = EdECPoint(isXOdd, y)
val keySpec = EdECPublicKeySpec(nameSpec, point)
val key = keyFactory.generatePublic(keySpec)

Signature.getInstance("Ed25519").apply {
    initVerify(key)
    update(deviceInfo)
    println(verify(deviceSignature))
}

And the data (before manipulation) (all in HEX):

Device identifier: 34444432393531392d463432322d343237442d414436302d444644393737354244443533
Device public key: e0a611c84db0ae91abfe2e6db91b6a457a4b41f9d8e09afdc7207ce3e4942e94
Device signature: a0383afb3bcbd43d08b04274a9214036f16195dc890c07a81aa06e964668955b29c5026d73d8ddefb12160529eeb66f843be4a925b804b575e6a259871259907
Device info: a86a71d42874b36e81a0acc65df0f2a84551b263b80b61d2f70929cd737176a434444432393531392d463432322d343237442d414436302d444644393737354244443533e0a611c84db0ae91abfe2e6db91b6a457a4b41f9d8e09afdc7207ce3e4942e94
// Device info is simply concatenated [hkdf, identifier, public key]

And the public key after the manipulation:

e0a611c84db0ae91abfe2e6db91b6a457a4b41f9d8e09afdc7207ce3e4942e14

Thank you very much, and every bit of help is greatly appreciated. This will help many more who will stumble upon this problem at a later point, when the Ed25519 implementation will not be so fresh.


Solution

  • Actually, the whole encoding and decoding is correct. The one thing in the end, that was the problem was that I (by mistake) reversed the array I read one too many times.

    Reversing arrays since certain keys are encoded in little endian, while in order to represent it as a BigInteger in JVM, you have to reverse the little endian so it becomes big endian.

    Hopefully this helps everyone in the future who will get stuck on any similar problems.

    If there will be any questions, simply comment here or send me a message here. I'll do my best to help you out.