Bouncy Castle ECC Key Pair Generation为ECpublic关键点的坐标产生不同大小
Bouncy Castle ECC Key Pair Generation produces different sizes for the coordinates of EC public key point
我正在使用 Bouncy Castle 生成 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
的类型是 ECParameterSpec
来自 java.security.spec
。我正在使用 brainpoolP256r1
.
效果很好。然后我想将密钥对中的 public 密钥值(来自 EC public 点的 X 和 Y 坐标)转换为八位字节串。
为此,我使用了 BigInteger.toByteArray()
函数。
我的问题和我想了解的是为什么坐标的大小并不总是相同?
每个坐标应该是32字节。但有时,我会得到 31 个字节或 33 个字节。据我所见,它与 toByteArray()
有关 returns 一个包含此 BigInteger 的二进制补码表示的字节 []。而且坐标的值没有填满32字节。
对于 33 个字节,我知道如果最左边的字节的值为 0x00,我可以安全地删除它。但是对于 31 个字节?我应该在开头添加 0x00 以获得 32 个字节吗?
如果有人能提供一些解释以便更好地理解,将不胜感激。
也许它已在另一个 post 中得到解决,但我没有通过我的研究找到我要找的东西。
编辑
提取 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 public 关键坐标应该是 32 字节长(对于基于 256 位的曲线)。但是,当您使用 BigInteger::toByteArray
文档时指出:
The array will contain the minimum number of bytes required to represent this BigInteger, including at least one sign bit, which is {@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
调用,因为我们已经从 BigInteger::toString
获得了长度为 264 的字符串。 注意左起第 9 位为 1,因此 toByteArray
附加了一个值为 0 的字节以保留符号 。
我正在使用 Bouncy Castle 生成 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
的类型是 ECParameterSpec
来自 java.security.spec
。我正在使用 brainpoolP256r1
.
效果很好。然后我想将密钥对中的 public 密钥值(来自 EC public 点的 X 和 Y 坐标)转换为八位字节串。
为此,我使用了 BigInteger.toByteArray()
函数。
我的问题和我想了解的是为什么坐标的大小并不总是相同?
每个坐标应该是32字节。但有时,我会得到 31 个字节或 33 个字节。据我所见,它与 toByteArray()
有关 returns 一个包含此 BigInteger 的二进制补码表示的字节 []。而且坐标的值没有填满32字节。
对于 33 个字节,我知道如果最左边的字节的值为 0x00,我可以安全地删除它。但是对于 31 个字节?我应该在开头添加 0x00 以获得 32 个字节吗?
如果有人能提供一些解释以便更好地理解,将不胜感激。 也许它已在另一个 post 中得到解决,但我没有通过我的研究找到我要找的东西。
编辑
提取 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 public 关键坐标应该是 32 字节长(对于基于 256 位的曲线)。但是,当您使用 BigInteger::toByteArray
文档时指出:
The array will contain the minimum number of bytes required to represent this BigInteger, including at least one sign bit, which is {@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
调用,因为我们已经从BigInteger::toString
获得了长度为 264 的字符串。 注意左起第 9 位为 1,因此toByteArray
附加了一个值为 0 的字节以保留符号 。