ssh public keys and Crypto::OpenSSL::RSA crap

Jan 14, 2009 13:38


Since these are either poorly documented or, in the case of the perl library, not all, I'm posting it here for some search engine love. For the purposes of this post, RSA will always refer to RSA v2.

SSH public key format

For an ssh public key in the form of:

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA4G/JdBcsyBYwjLm... root@host This is a 3 field white-space delimited entry. The important datum is the 2nd field which is a base64 encoded blob. This blob's format once decoded will contain 3 length-terminated byte streams:
  • key format (which should be the string "ssh-rsa" again)
  • RSA exponent (aka "e")
  • RSA modulus (aka "n")
The length is 32-bit in network order.

e.g., 0 0 0 7, followed by the 7 bytes of "ssh-rsa", 0 0 0 1 23 (a one byte exponent of 23), 0 0 1 1 and 257 bytes (0x101 in hex) worth of modulus.

The following perl can extract that:
use MIME::Base64 ; sub extract { my ($len, $in) = unpack("N a*", shift) ; unpack "a$len a*", $in ; } sub ssh_decode { my ($rest, $id, $exponent, $mod) ; ($id, $rest) = extract(decode_base64 $_[0]) ; ($exponent, $rest) = extract($rest) ; ($mod, $rest) = extract($rest) ; return $exponent, $mod; } open KEY, "/etc/ssh/ssh_host_rsa_key.pub" ; my ($e, $n) = ssh_decode( map { (split /\s+/)[1] } ; close KEY ;
Why was this needed? Well...

Crypt::OpenSSL::RSA

There's two OpenSSL libraries in CPAN. This one is a wrapper to the actual openssl utilities (the other is a pure perl implementation), so it's much faster. The problem is there's no built-in method of reading ssh-format public keys. Hence, the above code was required.

While the library does give a method of creating RSA key objects from scratch (not using the file reading or the new key generator functions) called new_key_from_parameters(), none of the documentation exactly states how to fucking use it!

Eventually I figured out it just takes the n and e in Bignum format (257 bytes = 2056 bits. Yeah, that's a big num, alright). So, we can sign something using the private key from ssh (which is already in a friendly format for openssl to slurp up) and then verify the signature with a perl script:
use MIME::Base64 ; use Crypt::OpenSSL::RSA ; use Crypt::OpenSSL::Bignum ; sub extract { my ($len, $in) = unpack("N a*", shift) ; unpack "a$len a*", $in ; } sub ssh_decode { my ($rest, $id, $exponent, $mod) ; ($id, $rest) = extract(decode_base64 $_[0]) ; ($exponent, $rest) = extract($rest) ; ($mod, $rest) = extract($rest) ; return $exponent, $mod; } # obtain the pubkey somehow - I have it in a database, # which is why I couldn't use the file reading functions my $rsa_string = ... ; my @rsa_keyparts = map {Crypt::OpenSSL::Bignum->new_from_bin($_)} ssh_decode base64_decode $rsa_string ; # Note having to swap the values from how they were given to us. my $rsa_pubkey= Crypt::OpenSSL::RSA->new_key_from_parameters( @rsa_keyparts[1,0] ) ; my $message = ... ; # Whatever got signed. my $signature = ... ; # Base64 encoded signature for the above message. if ($rsa_pubkey->verify($message, base64_decode $signature) ) { print "Signature is good.\n" ; } else { print "Signature is bad.\n" ; }
FWIW, I originally went with DSA thinking it was the more secure and would be handled the same way as RSA. Turns out RSA is just as secure and is much easier to deal with.

rsa, crypto, perl

Previous post Next post
Up