57

I'm attempting to write a script to generate SSH Identity key pairs for me.

from M2Crypto import RSA
key = RSA.gen_key(1024, 65337)
key.save_key("/tmp/my.key", cipher=None)

The file /tmp/my.key looks great now.

By running ssh-keygen -y -f /tmp/my.key > /tmp/my.key.pub I can extract the public key.

My question is how can I extract the public key from python? Using key.save_pub_key("/tmp/my.key.pub") saves something like:

-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADASDASDASDASDBarYRsmMazM1hd7a+u3QeMP
...
FZQ7Ic+BmmeWHvvVP4Yjyu1t6vAut7mKkaDeKbT3yiGVUgAEUaWMXqECAwEAAQ==
-----END PUBLIC KEY-----

When I'm looking for something like:

ssh-rsa AAAABCASDDBM$%3WEAv/3%$F ..... OSDFKJSL43$%^DFg==
3

11 Answers 11

98

Use cryptography! pycrypto is not in active development anymore and if possible you should be using cryptography. Since June it's possible to generate SSH public keys as well:

from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend

key = rsa.generate_private_key(
    backend=crypto_default_backend(),
    public_exponent=65537,
    key_size=2048
)

private_key = key.private_bytes(
    crypto_serialization.Encoding.PEM,
    crypto_serialization.PrivateFormat.PKCS8,
    crypto_serialization.NoEncryption()
)

public_key = key.public_key().public_bytes(
    crypto_serialization.Encoding.OpenSSH,
    crypto_serialization.PublicFormat.OpenSSH
)

Note: You need at least version 1.4.0.

Note: If your SSH client does not understand this private key format, replace PKCS8 with TraditionalOpenSSL.

Sign up to request clarification or add additional context in comments.

3 Comments

Points of note: Version requirement of cryptography >= 1.4.0 and missing an import statement: from cryptography.hazmat.backends import default_backend as crypto_default_backend
Thanks! Adjusted accordingly.
How do you convert this to the other format, i.e. `ssh-rsa AAAABCASDDBM$%3WEAv/3%$F ..... OSDFKJSL43$%^DFg=='
45

Just in case there are any future travellers looking to do this. The RSA module support writing out the public key in OpenSSH format now (possibly didn't at the time of earlier posts). So I think you can do what you need with:

from os import chmod
from Crypto.PublicKey import RSA

key = RSA.generate(2048)
with open("/tmp/private.key", 'wb') as content_file:
    chmod("/tmp/private.key", 0o600) #use an 0o prefix for octal integers
    content_file.write(key.exportKey('PEM'))
pubkey = key.publickey()
with open("/tmp/public.key", 'wb') as content_file:
    content_file.write(pubkey.exportKey('OpenSSH'))

The files are opened with a 'wb' as the keys must be written in binary mode. Obviously don't store you're private key in /tmp...

6 Comments

In key.exportKey('PEM'), the argument indicates output format. There are three options: 'DER' - binary encoding, 'PEM' - texture encoding, 'OpenSSH' - texture encoding according to OpenSSH spec.
@signal According to the documentation, OpenSSH for export is "only suitable for public keys (not private keys)".
I believe the phrase you quote relates to using the 'OpenSSH' format option with exportKey rather than the exportKey method itself eg the docs say you can use 'OpenSSH' format arg for the public key, as I have done, and 'PEM' for the private key as I have done.
Use cryptography instead of pycrypto for this; see other answer stackoverflow.com/a/39126754#1301627
In Python3, octal literals must have a "0o" prefix, so: chmod("/tmp/private.key", 0600) becomes chmod("/tmp/private.key", 0o600)
|
5

Edit 05/09/2012:

I just realized that pycrypto already has this:

import os
from Crypto.PublicKey import RSA

key = RSA.generate(2048, os.urandom)
print key.exportKey('OpenSSH')

This code works for me:

import os
from Crypto.PublicKey import RSA

key = RSA.generate(2048, os.urandom)

# Create public key.                                                                                                                                               
ssh_rsa = '00000007' + base64.b16encode('ssh-rsa')

# Exponent.                                                                                                                                                        
exponent = '%x' % (key.e, )
if len(exponent) % 2:
    exponent = '0' + exponent

ssh_rsa += '%08x' % (len(exponent) / 2, )
ssh_rsa += exponent

modulus = '%x' % (key.n, )
if len(modulus) % 2:
    modulus = '0' + modulus

if modulus[0] in '89abcdef':
    modulus = '00' + modulus

ssh_rsa += '%08x' % (len(modulus) / 2, )
ssh_rsa += modulus

public_key = 'ssh-rsa %s' % (
    base64.b64encode(base64.b16decode(ssh_rsa.upper())), )

3 Comments

I just added this code to a fork of pycrypto at github.com/jorgeecardona/pycrypto
Would you mind explaining a bit more about whats being done to the key after generation? Especially with respect to modulus?
There is a padding in there, I can't explain it pretty well at this moment, this was 3 years ago, this code actually is ugly, and you should try to read pycrypto implementation instead this, the code is here: github.com/dlitz/pycrypto/blob/master/lib/Crypto/PublicKey/…
4

The key used by ssh is just base64 encoded, i don't know M2Crypto very much, but after a quick overview it seems you could do what you want this way:

import os
from base64 import b64encode
from M2Crypto import RSA            

key = RSA.gen_key(1024, 65537)
raw_key = key.pub()[1]
b64key = b64encode(raw_key)

username = os.getlogin()
hostname = os.uname()[1]
keystring = 'ssh-rsa %s %s@%s' % (b64key, username, hostname)

with open(os.getenv('HOME')+'/.ssh/id_rsa.pub') as keyfile:
    keyfile.write(keystring)

I didn't test the generated key with SSH, so please let me know if it works (it should i think)

1 Comment

this is oh so close to what I'm looking for. Unfortunately, I'm not sure it works. The b64encoded characters almost match what ssh-keygen outputs, but there are 24 more characters between the first AAAA and the rest of the key. ie, the b64 key looks like "ssh-rsa AAAAabcdef...==" and the ssh-keygen key looks like "ssh-rsa AAAA<24 letters>abcdef...==" Any more tips?
1

The base64 decoded version of ssh-keygen output to the contents of key.pub() the format of the keyfile is

b64encode('\x00\x00\x00\x07ssh-rsa%s%s' % (key.pub()[0], key.pub()[1]))

1 Comment

Looking at it more the first 4 bytes represent the length of the string ssh-rsa, followed by the bytes found in key.pub()[0] so this is easy to construct.
1

If you want, you could just also use ssh-keygen itself. You can extend this to also create your file, and just use open to read the content later, but i focused on creating a .pub key from an already existing key here.

from subprocess import Popen, PIPE
import os

home = f'{os.path.expanduser("~")}'
cert_pos = f'{home}/.ssh/my_key'
your_key_pw = ''

cmd = ['ssh-keygen', '-y', '-f', cert_pos]
if your_key_pw:
    cmd.append('-P')
    cmd.append(your_key_pw)

p = Popen(cmd, stdout=PIPE)
p.wait()
res, err = p.communicate()

cert_content = res.decode('utf-8')

Comments

0

Just guessing... but have you tried something like this?:

print "ssh-rsa " + "".join([ l.strip() for l in open('/tmp/my.key.pub') if not l.startswith('-----')])

Comments

0

Can you get the AAAA...Dfg== string out of it while it's an object? If so, you could simply open a file yourself and save that instead of using the built in save_pub_key function.

Comments

0

Here is an example using the Twisted Conch library which leverages PyCrypto under the covers. You can find the API documentation at http://twistedmatrix.com/documents/current/api/twisted.conch.ssh.keys.html:

from twisted.conch.ssh import keys

# one-time use key
k="""-----BEGIN RSA PRIVATE KEY-----
PRIVATE KEY STUFF
-----END RSA PRIVATE KEY-----"""

# create pycrypto RSA object
rsa = keys.RSA.importKey(k)

# create `twisted.conch.ssh.keys.Key` instance which has some nice helpers
key = keys.Key(rsa)

# pull the public part of the key and export an openssh version
ssh_public = key.public().toString("openssh")
print ssh_public

Comments

0

You can use pycryptodome as described in documentation:

from Crypto.PublicKey import RSA

key = RSA.generate(2048)
private_key = key.export_key()
file_out = open("private.pem", "wb")
file_out.write(private_key)

public_key = key.publickey().export_key()
file_out = open("receiver.pem", "wb")
file_out.write(public_key)

Comments

-1

pip install ssh-key-maker

import ssh_key_maker

#for windows users

ssh_key_maker.generate_ssh_key()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.