Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
converts an openssh RSA public key into a format usable by openssl rsautl (if you don't have openssh 5.6 or later with ssh-keygen PEM export format)
#!/usr/bin/env python
# with help and inspiration from
# * ASN1_generate_nconf(3) (specifically the SubjectPublicKeyInfo structure)
# *
# *
# the unix way of doing it:
# Convert ssh-rsa key to pem
# ssh-keygen -f -e -m PKCS8 > outfile.pem
# Encrypt a file using public key pem
# openssl rsautl -encrypt -inkey public.pem -pubin -in file.txt -out file.ssl
# Decrypt using private key
# openssl rsautl -decrypt -inkey private.pem -in file.ssl -out decrypted.txt
import sys
import base64
import struct
from pyasn1.type import univ
from pyasn1.codec.der import encoder as der_encoder, decoder as der_decoder
if len(sys.argv) != 2:
sys.stderr.write("Usage: %s <public key file>\n" % sys.argv[0])
keyfields = open(sys.argv[1]).read().split(None)
if len(keyfields) < 3:
# there might not be a comment, so pad it
keytype, keydata, keycomment = keyfields
if keytype != 'ssh-rsa':
sys.stderr.write("%s: key type does not appear to be ssh-rsa\n")
keydata = base64.b64decode(keydata)
parts = []
while keydata:
# read the length of the data
dlen = struct.unpack('>I', keydata[:4])[0]
# read in <length> bytes
data, keydata = keydata[4:dlen+4], keydata[4+dlen:]
e_val = eval('0x' + ''.join(['%02X' % struct.unpack('B', x)[0] for x in parts[1]]))
n_val = eval('0x' + ''.join(['%02X' % struct.unpack('B', x)[0] for x in parts[2]]))
bitstring = univ.Sequence()
bitstring.setComponentByPosition(0, univ.Integer(n_val))
bitstring.setComponentByPosition(1, univ.Integer(e_val))
bitstring = der_encoder.encode(bitstring)
bitstring = ''.join([('00000000'+bin(ord(x))[2:])[-8:] for x in list(bitstring)])
bitstring = univ.BitString("'%s'B" % bitstring)
pubkeyid = univ.Sequence()
pubkeyid.setComponentByPosition(0, univ.ObjectIdentifier('1.2.840.113549.1.1.1')) # == OID for rsaEncryption
pubkeyid.setComponentByPosition(1, univ.Null(''))
pubkey_seq = univ.Sequence()
pubkey_seq.setComponentByPosition(0, pubkeyid)
pubkey_seq.setComponentByPosition(1, bitstring)
print "-----BEGIN PUBLIC KEY-----"
if keycomment:
print "X-Comment: " + keycomment
base64.MAXBINSIZE = (64//4)*3 # this actually doesn't matter, but it helped with comparing to openssl's output
print base64.encodestring(der_encoder.encode(pubkey_seq)),
print '-----END PUBLIC KEY-----'
Copy link

kellerkind commented Nov 5, 2012

For users of python <=2.5 you need to use additional function to perform byte => bin converting:

def bin(s):
if s<=1:
return "0b%s" % str(s)
return (bin(s>>1) + str(s&1))

Copy link

kellerkind commented Nov 5, 2012

It seems openssl (at least tested with version 0.9.8e) does not eat the "X-Comment" part of the block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment