Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bouncy Castle ECC Key Pair Generation produces different sizes for the coordinates of EC public key point

I'm generating an ECC key pair with Bouncy Castle using :

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();

The type of paramSpec is ECParameterSpec from java.security.spec. I'm using brainpoolP256r1.

It works well. Then I want to convert the public key value from the key pair (X and Y coordinates from EC public point) to octet string.

To do that, I'm using BigInteger.toByteArray() function.

My question and what I'm trying to understand is why the size of coordinates is not always the same ?

It should be 32 bytes for each coordinate. But sometimes, I get 31 bytes or 33 bytes. From what I saw, it has to do with toByteArray() that returns a byte[] containing the two's-complement representation of this BigInteger. And the value of the coordinate is not filling fully 32 bytes.

For 33 bytes, I understood that I can safely get rid of the leftmost byte if its value is 0x00. But for 31 bytes ? Should I add 0x00 at the beginning to have 32 bytes ?

If someone can provide some explanation to understand better, it would be much appreciated. Maybe it has been addressed in another post, but I did not find through my research what I was looking for.

EDIT

The code that extracts the BigInteger values :

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

For example, X and Y once were :

00XX...XX : 32 bytes

00XX...XX : 33 bytes

like image 804
Christophe Boubel Avatar asked Jan 30 '26 06:01

Christophe Boubel


1 Answers

You are right that EC public key coordinates should be 32 bytes long(for 256 bits based curves). However when you are using BigInteger::toByteArray documentations states that :

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

So when you receive the 33 bytes it means that the highest bit was set to 1 and to preserve it as positive value, a byte with value 0 was appended. When there are 31 bytes for the coordinate it means that the highest byte could be skipped because it's value was 0 so the resulting byte array has 31 values and you should append one byte with value 0 as the highest byte. I made a small app to test it :

BigInteger affineX = ((ECPublicKey) publicKey).getW().getAffineX();

BigInteger affineY = ((ECPublicKey) publicKey).getW().getAffineY();

printCoordinateInfo(affineX, "X");
System.out.println();
printCoordinateInfo(affineY, "Y");

And utility methods to print coordinates :

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();
}

The output was :

X byte array length 32
X bit string length 264
𝟎𝟎𝟎𝟎𝟎𝟎𝟎𝟎0111010011001001010010110111101000000110010111001001100101101111101011001010101101000101101100111011000111000110101110010101101011000111100101000000010010110000010110111101100111110100000111010110001100001010111110011101111000010111010110011010000101001010

Y byte array length 33
Y bit string length 264
𝟎𝟎𝟎𝟎𝟎𝟎𝟎𝟎1000011111110111111100111101100101100101111001100110001111110101001101010001000110111011111000111001111010111111000000010011101001100010010010100111000110110001101100101110010110001100111001000001110101011101011100110110110010101001000010011101000000110000


What we can see :

  • Coordinate X has byte array length 32. We print this BigInteger to bit string on 264 values so 8 zeros are appended on the beggining by fillZeros method. The 9'th bit from the left was 0 so we did not have to have addidional 33 byte for preserving sign.
  • Coordinate Y has byte array length 33. We print it to bit string that will have 264 characters. The fillZeros method will not change the BigInteger::toString call as we already have string of length 264 from BigInteger::toString. Notice that 9'th bit from left is 1 so toByteArray appended a byte with value 0 to preserve sign.
like image 198
MichaΕ‚ KrzywaΕ„ski Avatar answered Feb 01 '26 18:02

MichaΕ‚ KrzywaΕ„ski