Techioz Blog

OpenSSL v3 で「モジュラス」と「指数」を使用して RSA キーを作成すると、Ruby on Rails で機能しない

概要

RSA 公開キーの係数と指数文字列を持っています。 Ruby on Rails で OpenSSL::PKey::RSA を使用して OpenSSL::PKey::RSA を作成したいと考えています。 3 つの異なる方法で試してみましたが、環境に何か問題があると思われますが、特定できません。何かアイデアはありますか?

バージョン:

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

私はこれをします:

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

そしてエラーが発生します:

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

私はこれをします:

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

エラーが発生します:

rsa#set_key= is incompatible with OpenSSL 3.0
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)

エラーが発生します:

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

うーん、何が起こっているのですか?

解決策

私が試したことに興味がない場合は、解決策の部分に進んでください。

LTI プラットフォームから送信された JWT トークンを検証するためのキーセットを取得するための URL を含む LTI ツールを作成しています。キーセット内のキーには、JWT をデコードする RSA キーを作成するための係数 (n) と指数 (e) の値が含まれています。

問題が2つあったのですが、

OpenSSL v3 以前で動作する最初の Gist を見つけましたが、OpenSSL v3 では動作しませんでした。ありがたいことに、この素晴らしい人が OpenSSL v3 でこれを実現する方法をコメントで共有してくれました。解決策については、2 番目のリンクの Gist または以下のコードを確認してください。

2 番目のリンクで提供されている Gist コードを共有します (念のため)。

# 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

これがお役に立てば幸いです!