Ruby RSA from exponent and modulus strings
Asked Answered
P

4

11

I have an RSA public key modulus and exponent string.

I want to create a OpenSSL::PKey::RSA from these two strings.

Basically they come in as:

  • n = 'long string'
  • e = '4-character string'

How would I do this in Ruby? The end goal is to get this to the JWT gem.

Update

I'm currently in Ruby 2.3.1, so this works:

key = OpenSSL::PKey::RSA.new
key.e = OpenSSL::BN.new(Base64.decode64(e), 2)
key.n = OpenSSL::BN.new(Base64.decode64(n), 2)

However, it won't work during an upgrade.

Pacifist answered 8/9, 2017 at 16:53 Comment(1)
Your update would work perfectly in any case by using Base64.urlsafe_decode64 instead of Base64.decode64 for old ruby version.Dominican
T
5

I got it working this way, based on this python implementation:

https://github.com/jpf/okta-jwks-to-pem/blob/master/jwks_to_pem.py

    key = OpenSSL::PKey::RSA.new
    exponent = kid_header['e']
    modulus = kid_header['n']


    # Voila !
    key.set_key(base64_to_long(modulus), base64_to_long(exponent), nil)

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

    def to_hex(int)
      int < 16 ? '0' + int.to_s(16) : int.to_s(16)
    end
Tillandsia answered 5/11, 2018 at 23:54 Comment(1)
That's perfect!Pacifist
F
3

You can use JSON::JWT gem (https://rubygems.org/gems/json-jwt, https://github.com/nov/json-jwt)

# can be found somewhere in `.well-known` space on the server
key_hash = {
  "kty": "RSA",
  "use": "sig",
  "kid": ...,
  "e": ...,
  "n": ...,
  "alg": "RS256"
}
jwk = JSON::JWK.new(key_hash)
JSON::JWT.decode token, jwk.to_key
# voila!

The same can be achieved with Ruby JWT (https://rubygems.org/gems/jwt, https://github.com/jwt/ruby-jwt/blob/master/lib/jwt/jwk/rsa.rb)

public_key = JWT::JWK::RSA.import(key_hash).public_key
JWT.decode token, public_key, true, { algorithm: key_hash[:alg] }
Fiduciary answered 7/8, 2019 at 21:43 Comment(1)
In case someone struggle like me... JWK support was added on jwt-ruby only on version 2.2.1 Beware of the version you are using in your project (I was using 1.5)Daffodil
A
2

For Ruby 2.4+ you should use :

key = OpenSSL::PKey::RSA.new
key.set_key(n, e, d)

if you do not have d you can set it to nil.

Abomination answered 9/6, 2018 at 9:29 Comment(0)
A
0

Accepted answer did not work for me in OpenSSL v3. Check out this thread for a solution that works in OpenSSL v3.

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

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.

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!

Adelaideadelaja answered 2/10, 2023 at 18:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.