1

I'm extracting the modulus and exponent from a public SSH key with the goal of generating a PEM public key. Here is my code so far:

require "base64"
require "openssl"


def unpacked_byte_array(ssh_type, encoded_key)
  prefix = [7].pack("N") + ssh_type
  decoded = Base64.decode64(encoded_key)

  # Base64 decoding is too permissive, so we should validate if encoding is correct
  unless Base64.encode64(decoded).gsub("\n", "") == encoded_key && decoded.slice!(0, prefix.length) == prefix
    raise PublicKeyError, "validation error"
  end

  data = []
  until decoded.empty?
    front = decoded.slice!(0,4)
    size = front.unpack("N").first
    segment = decoded.slice!(0, size)
    unless front.length == 4 && segment.length == size
      raise PublicKeyError, "byte array too short"
    end
    data << OpenSSL::BN.new(segment, 2)
  end
  return data
end

module OpenSSL
  module PKey
    class RSA
      def self.new_from_parameters(n, e)
        a = self.new   # self.new(64) for ruby < 1.8.2
        a.n = n        # converted to OpenSSL::BN automatically
        a.e = e
        a
      end
    end
  end
end

e, n = unpacked_byte_array('ssh-rsa', 'AAAAB3NzaC1yc2EAAAABIwAAAQEA3RC8whKGFx+b7BMTFtnIWl6t/qyvOvnuqIrMNI9J8+1sEYv8Y/pJRh0vAe2RaSKAgB2hyzXwSJ1Fh+ooraUAJ+q7P2gg2kQF1nCFeGVjtV9m4ZrV5kZARcQMhp0Bp67tPo2TCtnthPYZS/YQG6u/6Aco1XZjPvuKujAQMGSgqNskhKBO9zfhhkAMIcKVryjKYHDfqbDUCCSNzlwFLts3nJ0Hfno6Hz+XxuBIfKOGjHfbzFyUQ7smYnzF23jFs4XhvnjmIGQJcZT4kQAsRwQubyuyDuqmQXqa+2SuQfkKTaPOlVqyuEWJdG2weIF8g3YP12czsBgNppz3jsnhEgstnQ==')


rsa = OpenSSL::PKey::RSA.new_from_parameters(n, e)
puts rsa

The goal is to have a pure Ruby implementation of what ssh-keygen -f <file> -e -m pem does.

Now, comparing the results, they look very similar, but my code returns a few more bytes at the beginning of the key:

$ ssh-keygen -f ~/.ssh/id_rsa_perso.pub -e -m pem 
-----BEGIN RSA PUBLIC KEY-----
MIIBCAKCAQEA3RC8whKGFx+b7BMTFtnIWl6t/qyvOvnuqIrMNI9J8+1sEYv8Y/pJ
Rh0vAe2RaSKAgB2hyzXwSJ1Fh+ooraUAJ+q7P2gg2kQF1nCFeGVjtV9m4ZrV5kZA
RcQMhp0Bp67tPo2TCtnthPYZS/YQG6u/6Aco1XZjPvuKujAQMGSgqNskhKBO9zfh
hkAMIcKVryjKYHDfqbDUCCSNzlwFLts3nJ0Hfno6Hz+XxuBIfKOGjHfbzFyUQ7sm
YnzF23jFs4XhvnjmIGQJcZT4kQAsRwQubyuyDuqmQXqa+2SuQfkKTaPOlVqyuEWJ
dG2weIF8g3YP12czsBgNppz3jsnhEgstnQIBIw==
-----END RSA PUBLIC KEY-----
$ ruby ssh2x509.rb 
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA3RC8whKGFx+b7BMTFtnI
Wl6t/qyvOvnuqIrMNI9J8+1sEYv8Y/pJRh0vAe2RaSKAgB2hyzXwSJ1Fh+ooraUA
J+q7P2gg2kQF1nCFeGVjtV9m4ZrV5kZARcQMhp0Bp67tPo2TCtnthPYZS/YQG6u/
6Aco1XZjPvuKujAQMGSgqNskhKBO9zfhhkAMIcKVryjKYHDfqbDUCCSNzlwFLts3
nJ0Hfno6Hz+XxuBIfKOGjHfbzFyUQ7smYnzF23jFs4XhvnjmIGQJcZT4kQAsRwQu
byuyDuqmQXqa+2SuQfkKTaPOlVqyuEWJdG2weIF8g3YP12czsBgNppz3jsnhEgst
nQIBIw==
-----END PUBLIC KEY-----

Notice my output has the content of the ssh-keygen output, but with MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0A prepended.

What could cause these extra bytes, and how could I get the proper result?

jww
  • 97,681
  • 90
  • 411
  • 885
raphink
  • 3,625
  • 1
  • 28
  • 39
  • I actually found a duplicate of this http://stackoverflow.com/questions/4635837/invalid-public-keys-when-using-the-ruby-openssl-library – raphink Mar 30 '16 at 07:49
  • Also see [Use OpenSSL RSA key with .Net](http://stackoverflow.com/q/30475758). It discusses how to use OpenSSL to save a key with both PKCS and Traditional encodings (`BEGIN RSA PUBLIC KEY` versus `BEGIN PUBLIC KEY`). Once you see it in C/C++, you will probably be able to make the necessary changes to your Ruby code in no time. – jww Mar 31 '16 at 02:50

2 Answers2

2

It seems the output format for RSA keys in Ruby OpenSSL was changed in 1.9.3 from PKCS#1 (used by OpenSSH) to X509 (used by OpenSSL post 1.9.3):

https://redmine.ruby-lang.org/issues/4421

What is suggested in this bug report is to emulate the PKCS#1 with:

ary = [OpenSSL::ASN1::Integer.new(n), OpenSSL::ASN1::Integer.new(e)]
pub_key = OpenSSL::ASN1::Sequence.new(ary)
base64 = Base64.encode64(pub_key.to_der)

#This is the equivalent to the PKCS#1 encoding used before 1.9.3
pem = "-----BEGIN RSA PUBLIC KEY-----\n#{base64}-----END RSA PUBLIC KEY-----"

The monkey patching of OpenSSL::PKey::RSA is thus not necessary.

raphink
  • 3,625
  • 1
  • 28
  • 39
0

To solve this problem, you can analyze ASN1 structure.

For you output, it is

SEQUENCE(2 elem)
SEQUENCE(2 elem)
OBJECT IDENTIFIER1.2.840.113549.1.1.1
NULL
BIT STRING(1 elem)
SEQUENCE(2 elem)
INTEGER(2048 bit) 279069188856447290054297383130027286257044344789969750715307012565210…
INTEGER35

For ssh output, it is

SEQUENCE(2 elem)
INTEGER(2048 bit) 279069188856447290054297383130027286257044344789969750715307012565210…
INTEGER35

What does this mean? It means your RSA key is structured differently. In SSH, it just contains sequence of 2048 bit integer. Whereas, in your case, it also carries object identification.

Solution? Remove those starting bits which you can calculate by analyzing ASN1 structure.

Or analyze by hexdump that how many bytes are to be removed from your RSA public key.

Your RSA public key:

30 82 01 20 30 0D 06 09  2A 86 48 86 F7 0D 01 01
01 05 00 03 82 01 0D 00  **30 82 01 08 02 82 01 01
00 DD 10 BC C2** 12 86 17  1F 9B EC 13 13 16 D9 C8
5A 5E AD FE AC AF 3A F9  EE A8 8A CC 34 8F 49 F3
ED 6C 11 8B FC 63 FA 49  46 1D 2F 01 ED 91 69 22
80 80 1D A1 CB 35 F0 48  9D 45 87 EA 28 AD A5 00
27 EA BB 3F 68 20 DA 44  05 D6 70 85 78 65 63 B5
… skipping 160 bytes …
0F D7 67 33 B0 18 0D A6  9C F7 8E C9 E1 12 0B 2D
9D 02 01 23 

SSH RSA public key

**30 82 01 08 02 82 01 01  00 DD 10 BC C2** 12 86 17
1F 9B EC 13 13 16 D9 C8  5A 5E AD FE AC AF 3A F9
EE A8 8A CC 34 8F 49 F3  ED 6C 11 8B FC 63 FA 49
46 1D 2F 01 ED 91 69 22  80 80 1D A1 CB 35 F0 48
9D 45 87 EA 28 AD A5 00  27 EA BB 3F 68 20 DA 44
… skipping 160 bytes …
74 6D B0 78 81 7C 83 76  0F D7 67 33 B0 18 0D A6
9C F7 8E C9 E1 12 0B 2D  9D 02 01 23 

By analyzing this, you can see that you have to remove these:

30 82 01 20 30 0D 06 09  2A 86 48 86 F7 0D 01 01
01 05 00 03 82 01 0D 00

Means 24 bytes. Remove 24 bytes from your key.

Or you can use ASN1 parser and just extract sequence.

doptimusprime
  • 9,115
  • 6
  • 52
  • 90