JDK 15 中的 Ed25519,从字节数组中解析 public 键并验证

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

由于 Ed25519 出现时间不长(JDK),关于如何使用它的资源非常少。

虽然他们的示例非常简洁有用,但我很难理解我在密钥解析方面做错了什么。

他们Public正在从 iDevice 发送的数据包中读取密钥。

(这么说吧,是一个字节数组)

通过搜索并尽力了解密钥的编码方式,我偶然发现了这条消息。

   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.

这意味着如果我想获得 yisXOdd 我必须做一些工作。 (如果我没理解错的话)

下面是它的代码,仍然验证失败

我认为,我正确地通过反转数组将其返回到 Big Endian 供 BigInteger 使用。

我的问题是:

  1. 这是从字节数组
  2. 解析public键的正确方法吗
  3. 如果是,验证过程失败的可能原因是什么?

// 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))
}

和数据(操作前)(全部为十六进制):

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

和public操作后的键值:

e0a611c84db0ae91abfe2e6db91b6a457a4b41f9d8e09afdc7207ce3e4942e14

非常感谢,非常感谢您提供的每一点帮助。 这将帮助更多以后偶然发现此问题的人,届时 Ed25519 实施将不再那么新鲜。

其实整个编码解码都是正确的。 最后的一件事,就是我(错误地)颠倒了我读了太多次的数组。

反转数组,因为某些键以小端编码,而为了在 JVM 中将其表示为 BigInteger,您必须反转小端,使其成为大端。

希望这对以后遇到类似问题的每个人都有帮助。

如果有任何问题,请在这里评论或给我发消息。 我会尽力帮助你。

对我帮助很大。没有你的例子永远不会弄明白。 我在 java.

完成了
public static PublicKey getPublicKey(byte[] pk)
            throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidParameterSpecException {
        // key is already converted from hex string to a byte array.
        KeyFactory kf = KeyFactory.getInstance("Ed25519");
        // determine if x was odd.
        boolean xisodd = false;
        int lastbyteInt = pk[pk.length - 1];
        if ((lastbyteInt & 255) >> 7 == 1) {
            xisodd = true;
        }
        // make sure most significant bit will be 0 - after reversing.
        pk[pk.length - 1] &= 127;
        // apparently we must reverse the byte array...
        pk = ReverseBytes(pk);
        BigInteger y = new BigInteger(1, pk);

        NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
        EdECPoint ep = new EdECPoint(xisodd, y);
        EdECPublicKeySpec pubSpec = new EdECPublicKeySpec(paramSpec, ep);
        PublicKey pub = kf.generatePublic(pubSpec);
        return pub;
    

我这里的代码比您可能需要的要多得多。我对此进行了调查,并提出了我认为等效的方法,只是简单得多。无论如何,这是博客文章:https://www.tbray.org/ongoing/When/202x/2021/04/19/PKI-Detective. and here is the Java code: https://github.com/timbray/blueskidjava