充气城堡 ECC 密钥对生成为 EC 公钥点的坐标生成不同的大小



我正在使用以下方法与充气城堡生成ECC密钥对:

KeyPairGenerator kpg = null;
try {
    kpg = KeyPairGenerator.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
    throw new CustomException("Exception: " + e.getMessage());
}
try {
    kpg.initialize(paramSpec, new SecureRandom());
} catch (InvalidAlgorithmParameterException e) {
    throw new CustomException("Exception: " + e.getMessage());
}
return kpg.generateKeyPair();

paramSpec的类型是从java.security.spec ECParameterSpec。我正在使用brainpoolP256r1.

效果很好。然后我想将公钥值从密钥对(来自 EC 公共点的 X 和 Y 坐标(转换为八位字节字符串。

为此,我正在使用BigInteger.toByteArray()函数。

我的问题和我试图理解的是为什么坐标的大小并不总是相同的?

每个坐标应为 32 个字节。但有时,我会得到 31 字节或 33 字节。从我所看到的,它与返回一个字节[]的toByteArray()有关,该字节[]包含此BigInteger的两个补码表示形式。并且坐标的值没有完全填充 32 个字节。

对于 33 个字节,我明白如果它的值是 0x00,我可以安全地删除最左边的字节。但是对于 31 字节?我应该在开头添加0x00以具有 32 字节吗?

如果有人能提供一些解释以更好地理解,将不胜感激。 也许它在另一篇文章中已经解决了,但我没有通过我的研究找到我正在寻找的东西。

编辑

提取 BigInteger 值的代码:

KeyPair aKeyPair = generateKeyPair(); //Function above
PublicKey publicKey = aKeyPair.getPublic();
byte[] X = ((ECPublicKey)publicKey).getW().getAffineX().toByteArray();
byte[] Y = ((ECPublicKey)publicKey).getW().getAffineY().toByteArray();

例如,X 和 Y 曾经是:

00XX...XX : 32 字节

00XX...XX : 33 字节

你是对的,EC公钥坐标应该是32字节长(对于基于256位的曲线(。但是,当您使用BigInteger::toByteArray文档时,声明:

数组将包含表示此 BigInteger 所需的最小字节数,包括至少一个符号位,即 {@code (ceil((this.bitLength(( + 1(/8((}

因此,当您收到 33 个字节时,这意味着最高位设置为 1,并将其保留为正值,附加了一个值为 0 的字节。当坐标有 31 个字节时,这意味着可以跳过最高字节,因为它的值为 0,因此生成的字节数组有 31 个值,您应该附加一个值为 0 的字节作为最高字节。我做了一个小应用程序来测试它:

BigInteger affineX = ((ECPublicKey) publicKey).getW().getAffineX();
BigInteger affineY = ((ECPublicKey) publicKey).getW().getAffineY();
printCoordinateInfo(affineX, "X");
System.out.println();
printCoordinateInfo(affineY, "Y");

和实用程序方法打印坐标:

private static void printCoordinateInfo(BigInteger bigInteger, String coordinateName) {
    String bitString = bigInteger.toString(2);
    String binary = fillZeros(bitString, 264);
    byte[] coordinateBytes = bigInteger.toByteArray();
    System.out.println(coordinateName + " byte array length " + coordinateBytes.length);
    System.out.println(coordinateName + " bit string length " + binary.length());
    System.out.println(binary);
}
private static String fillZeros(String text, int size) { //fills with zeros on the left and quits when achieves given length
    StringBuilder builder = new StringBuilder(text);
    while (builder.length() < size) {
        builder.insert(0, '0');
    }
    return builder.toString();
}

输出为:

X byte array length 32
X bit string length 264
𝟎𝟎𝟎𝟎𝟎𝟎𝟎𝟎0111010011001001010010110111101000000110010111001001100101101111101011001010101101000101101100111011000111000110101110010101101011000111100101000000010010110000010110111101100111110100000111010110001100001010111110011101111000010111010110011010000101001010
Y byte array length 33
Y bit string length 264
𝟎𝟎𝟎𝟎𝟎𝟎𝟎𝟎1000011111110111111100111101100101100101111001100110001111110101001101010001000110111011111000111001111010111111000000010011101001100010010010100111000110110001101100101110010110001100111001000001110101011101011100110110110010101001000010011101000000110000

我们可以看到:

  • 坐标 X 的字节数组长度为 32。我们将这个 BigInteger 打印到 264 个值的位字符串,因此通过fillZeros方法在开头附加 8 个零。左起的第 9 位是 0,所以我们不必有额外的 33 字节来保留符号。
  • 坐标 Y 的字节数组长度为 33。我们将其打印为包含 264 个字符的位字符串。fillZeros 方法不会更改BigInteger::toString调用,因为我们已经有长度为 264 的字符串 BigInteger::toString .请注意,左起第 9 位是 1,因此toByteArray附加了一个值为 0 的字节以保留符号

最新更新