Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating RSA key with OpenSSL v3 using "modulus" and "exponent" doesn't work in Ruby on Rails

I have an RSA public key modulus and exponent string. I want to create a OpenSSL::PKey::RSA using them in Ruby on Rails. I tried it three different ways and I think something is messed up about my environement, however, I can't spot it. Do you have any idea?

Versions:

ruby 3.2.1 (2023-02-08 revision 31819e82c8) [arm64-darwin22]
Rails 7.0.8
OpenSSL 3.1.2 1 Aug 2023

First Try

I do this:

modulus = k['n']
exponent= k['e']
rsa = OpenSSL::PKey::RSA.new
rsa.set_key_rsa(k['n'], k['e'], 2)

and I get error:

undefined method `set_key_rsa' for #<OpenSSL::PKey::RSA:0x000000010a1468a0 oid=rsaEncryption>

Second try

I do this:

modulus = k['n']
exponent= k['e']
rsa = OpenSSL::PKey::RSA.new
rsa.set_key(k['n'], k['e'], 2) # <---- used set_key

I get error:

rsa#set_key= is incompatible with OpenSSL 3.0

Third

rsa = OpenSSL::PKey::RSA.new
rsa.n = OpenSSL::BN.new(Base64.urlsafe_decode64(k['n']), 2)
rsa.e = OpenSSL::BN.new(Base64.urlsafe_decode64(k['e']), 2)

I get error:

undefined method `n=' for #<OpenSSL::PKey::RSA:0x000000010ab56e08 oid=rsaEncryption>

Ugh, what's happening?

like image 550
dugong Avatar asked Oct 27 '25 06:10

dugong


1 Answers

Jump to solution part if you are not interested in what I was trying.

Context:

I am creating an LTI tool that has a URL to get a keyset for verifying a JWT token sent from an LTI platform. Keys in the keyset has a modulus (n) and an exponent (e) value to create the RSA key that will decode the JWT.

There were two problems,

  • in OpenSSL v3 n and e methods are not working anymore.
  • it is not clear how to format modulus (n) and exponent (e) values properly to use the in certain methods

Solution:

I found the first Gist that worked before OpenSSL v3, however it did not work in the OpenSSL v3. Thankfully, this great dude shared how to achieve this in OpenSSL v3 in the comments. Check out the second link for the Gist or the code below for the solution.

  • ❌ Works in versions of OpenSSL before v3: https://gist.github.com/jooeycheng/0d5fbd039b6ec586c450b7486be5e049

  • ✅ Works in OpenSSL v3: https://gist.github.com/WilliamNHarvey/0e37f84a86e66f9acb7ac8c68b0f996b

Sharing the Gist code here provided in the second link (just in case):

# Given n and e in typical encoding, like that found on a jwks well-known.
# For example for google, from https://www.googleapis.com/oauth2/v3/certs
n = "t0VFy4n4MGtbMWJKk5qfCY2WGBja2WSWQ2zsLziSx9p1QE0QgXtr1x85PnQYaYrAvOBiXm2mrxWnZ42MxaUUu9xyykTDxsNWHK--ufchdaqJwfqd5Ecu-tHvFkMIs2g39pmG8QfXJHKMqczKrvcHHJrpTqZuos1uhYM9gxOLVP8wTAUPNqa1caiLbsszUC7yaMO3LY1WLQST79Z8u5xttKXShXFv1CCNs8-7vQ1IB5DWQSR2um1KV4t42d31Un4-8cNiURx9HmJNJzOXbTG-vDeD6sapFf5OGDsCLO4YvzzkzTsYBIQy_p88qNX0a6AeU13enxhbasSc-ApPqlxBdQ"
e = "AQAB"

rsa = create_rsa_key(n, e)

def create_rsa_key(n, e)
  data_sequence = OpenSSL::ASN1::Sequence([
                                            OpenSSL::ASN1::Integer(base64_to_long(n)),
                                            OpenSSL::ASN1::Integer(base64_to_long(e))
                                          ])
  asn1 = OpenSSL::ASN1::Sequence(data_sequence)
  OpenSSL::PKey::RSA.new(asn1.to_der)
end

def base64_to_long(data)
  decoded_with_padding = Base64.urlsafe_decode64(data) + Base64.decode64("==")
  decoded_with_padding.to_s.unpack("C*").map do |byte|
    byte_to_hex(byte)
  end.join.to_i(16)
end

def byte_to_hex(int)
  int < 16 ? "0" + int.to_s(16) : int.to_s(16)
end

Hope this helped!

like image 143
dugong Avatar answered Oct 28 '25 20:10

dugong



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!