Question

I want to be able to format the public key of Elliptic Curve Diffie-Hellman in OpenSSL - Ruby into something like this online example (link), as I have been using that JS library.

My code below generates an OpenSSL::PKey::EC public and private keys

#Ruby
ec = OpenSSL::PKey::EC.new('secp128r1')
ec.generate_key

ec.private_key
#--> 205607153615223513963863936713567041725

ec.public_key.to_bn
#--> 499599043529551953518354858381998373780459818901085313561109939106744612770290

Try copying the private key above 205607153615223513963863936713567041725 and pasting it on the online (link) as Alices' Private value. But click the secp1284r1 button first to have same curve parameters, and then click Compute Public button.

That will generate a public key from the inputted private key. However, the Ruby OpenSSL documentation isn't really helpful, and I am stuck on figuring out how to convert the generated public key above:

499599043529551953518354858381998373780459818901085313561109939106744612770290

Into something like this (as seen from the online site):

x: 107060165679262225845922473865530329196
y: 109296969851421346147544217212275741170

I've assumed that by properly converting one, it can somehow become equal to the other since they have same curve parameters. Or am I wrong? (And also because the default format of point_conversion_form is :uncompressed, as I just have tested) Please help.

P.S. You might wonder why I need to convert the public key into the other. No, I don't really have to. I just want to learn how to convert it as I'll be using that method to convert something similar. And this is the simplified question for your testing-convenience.

Was it helpful?

Solution 3

Finally! I somehow managed to convert it properly but it's somehow weird.

#From above code
c.public_key.to_bn
#--> 499599043529551953518354858381998373780459818901085313561109939106744612770290

#irb:
require 'openssl'

key_int = '499599043529551953518354858381998373780459818901085313561109939106744612770290'
key_bn = OpenSSL::BN.new(key_int, 10) #Convert to OpenSSL::BN (Big Number, with 10=Decimal as base)
key_hex = key_bn.to_s(16) #Convert to Hex String (16=Hexadecimal)
#--> "04508B09B35FA8C21820BE19C16B38486C5239D4A932D081DD56B90F91120551F2"

#I don't really know why, but removing '04' above will finally convert it properly
key_hex = key_hex[2..-1] #Remove first 2 chars: '04'
#--> "508B09B35FA8C21820BE19C16B38486C5239D4A932D081DD56B90F91120551F2"

#Split key_hex into halves
key_hexarr = key_hex.chars.each_slice( (key_hex.length/2.0).round ).map(&:join)
#--> ["508B09B35FA8C21820BE19C16B38486C", "5239D4A932D081DD56B90F91120551F2"]

#Convert first value into BN (input: 16=hexadecimal), then convert to string(output: 10=decimal)
key_x_int = OpenSSL::BN.new(key_hexarr[0], 16).to_s(10)
#--> "107060165679262225845922473865530329196"

#Convert second value into BN (input: 16=hexadecimal), then convert to string(output: 10=decimal)
key_y_int = OpenSSL::BN.new(key_hexarr[1], 16).to_s(10)
#--> "109296969851421346147544217212275741170"

Finally, key_x_int and key_y_int now matches the result from the online link

OTHER TIPS

Jay-Ar,

You shouldn't find your solution weird as I think the call to ec.public_key.to_bn might be adhering to rfc5480 section 2.2 re: Subject Public Keys which states:

2.2. Subject Public Key

  • The first octet of the OCTET STRING indicates whether the key is compressed or uncompressed. The uncompressed form is indicated by 0x04 and the compressed form is indicated by either 0x02 or 0x03 (see 2.3.3 in [SEC1]). The public key MUST be rejected if any other value is included in the first octet.

Assuming this is the case and since you stated the format is :uncompressed, your own answer makes total sense to me. Thanks for posting it! :)

ec.public_key.to_bn
#--> 499599043529551953518354858381998373780459818901085313561109939106744612770290

It would probably be better to print that in hex.

The public key is a point on the curve. That is, its an (x,y) coordinate. So that value probably needs to be split (hence the reason for printing in hex). Taking a guess:

x = 499599043529551953518354858381998373780
y = 459818901085313561109939106744612770290

The public key is a point because its derived from the base point G, which is also a point. G is sometimes expanded as (g_x, g_y). The private exponent is a (or b), which is a scalar or integer. So the public key is A=G^a or A=(g_x, g_y)^a, which is a point.


And also because the default format of point_conversion_form is :uncompressed, as I just have tested

Point conversion/compression is just a presentation layer optimization trick. It omits the y portion of the coordinate because you can solve for it given x. Because its a curve, sometimes you need to send +1 or -1 to specify the quadrant the y coordinate lies in. But again, its just an optimization and only matters for interop'ing. After you read the point into a library, both x and y are available.


You might wonder why I need to convert the public key into the other...

Nope :)

But one thing you might want to be aware of is OpenSSL's "named curve" flag. If you want to load an EC key into an OpenSSL based server, you will want to ensure the private key and resulting certificate have the OPENSSL_EC_NAMED_CURVE flag. Otherwise, you'll get bizarre errors like "no shared ciphers" when trying to connect to the server. See ECDH and Named Curves on the OpenSSL wiki for details.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top