来自模数和指数的SSH SubjectPublicKeyInfo

时间:2016-03-29 17:04:18

标签: ruby openssl rsa ssh-keys

我从公共SSH密钥中提取模数和指数,目的是生成PEM公钥。到目前为止,这是我的代码:

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

目标是拥有ssh-keygen -f <file> -e -m pem所做的纯Ruby实现。

现在,比较结果,它们看起来非常相似,但我的代码在键的开头返回了几个字节:

$ 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-----

请注意,我的输出具有ssh-keygen输出的内容,但前缀为MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0A

什么可能导致这些额外的字节,我怎么能得到正确的结果?

2 个答案:

答案 0 :(得分:2)

似乎Ruby OpenSSL中RSA密钥的输出格式在1.9.3中已从PKCS#1(由OpenSSH使用)更改为X509(由OpenSSL 1.9.3后使用):

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

此错误报告中建议的是emulate the PKCS#1

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-----"

因此不需要修补OpenSSL::PKey::RSA的猴子。

答案 1 :(得分:0)

要解决此问题,您可以分析ASN1结构。

对于你的输出,它是

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

对于ssh输出,它是

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

这是什么意思?这意味着您的RSA密钥结构不同。在SSH中,它只包含2048位整数的序列。然而,在您的情况下,它还带有对象识别。

解决方案?通过分析ASN1结构,删除可以计算的起始位。

或者通过hexdump分析要从RSA公钥中删除多少字节。

您的RSA公钥:

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公钥

**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 

通过分析,你可以看到你必须删除这些:

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

表示24个字节。从密钥中删除24个字节。

或者您可以使用ASN1解析器并只提取序列。