53

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==
Lee
  • 571
  • 1
  • 4
  • 4
  • Check pycryto, since already have format 'OpenSSH' for the exportKey method. – Jorge E. Cardona May 09 '12 at 15:52
  • 1
    pycrypto [is unmaintained and has known vulnerabilities](https://github.com/dlitz/pycrypto/issues/173). `pycryptodome` is a drop-in replacement. – Merlijn Sebrechts Aug 05 '16 at 08:39
  • I think you meant `65537` rather than `65337`. The former is much more common. [The risks associated with using other numbers are disputed](http://security.stackexchange.com/questions/2335/should-rsa-public-exponent-be-only-in-3-5-17-257-or-65537-due-to-security-c), but the consensus is that 65537 (that is, 2^16 + 1) is secure. 65337 is not NIST-compliant. – Zenexer Feb 13 '17 at 05:55

11 Answers11

90

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.

Ben Davis
  • 13,112
  • 10
  • 50
  • 65
Dave Halter
  • 15,556
  • 13
  • 76
  • 103
  • 4
    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` – Samveen Sep 08 '16 at 09:19
  • Thanks! Adjusted accordingly. – Dave Halter Sep 09 '16 at 08:47
  • How do you convert this to the other format, i.e. `ssh-rsa AAAABCASDDBM$%3WEAv/3%$F ..... OSDFKJSL43$%^DFg==' – user592419 Feb 22 '18 at 01:48
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", 0600)
    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...

  • 7
    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 Aug 08 '16 at 06:37
  • @signal According to [the documentation](https://pythonhosted.org/pycrypto/Crypto.PublicKey.RSA._RSAobj-class.html#exportKey), OpenSSH for export is "only suitable for public keys (not private keys)". – datu-puti Feb 24 '17 at 20:39
  • 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. – fruitbeeriswrong Mar 21 '17 at 15:08
  • 3
    Use cryptography instead of pycrypto for this; see other answer http://stackoverflow.com/a/39126754#1301627 – fatal_error May 19 '17 at 19:44
  • 2
    In Python3, octal literals must have a "0o" prefix, so: `chmod("/tmp/private.key", 0600)` becomes `chmod("/tmp/private.key", 0o600)` – dangirsh Sep 19 '18 at 17:30
  • pubkey.exportKey('OpenSSH') is having issues in python 3; It seems project is not being actively maintained anymore. https://github.com/dlitz/pycrypto/issues/99 – Bharat Kul Ratan Sep 22 '18 at 18:28
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())), )
Matt Clegg
  • 584
  • 1
  • 4
  • 16
Jorge E. Cardona
  • 92,161
  • 3
  • 37
  • 44
  • I just added this code to a fork of pycrypto at https://github.com/jorgeecardona/pycrypto – Jorge E. Cardona Jun 24 '11 at 22:44
  • Would you mind explaining a bit more about whats being done to the key after generation? Especially with respect to `modulus`? – Will Apr 17 '14 at 18:22
  • 1
    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: https://github.com/dlitz/pycrypto/blob/master/lib/Crypto/PublicKey/RSA.py#L678 – Jorge E. Cardona Apr 18 '14 at 21:25
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)

mdeous
  • 17,513
  • 7
  • 56
  • 60
  • 1
    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? – Lee Apr 08 '10 at 16:26
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]))
manis
  • 19
  • 3
  • 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. – manis Oct 15 '10 at 05:40
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')
NegatioN
  • 667
  • 2
  • 9
  • 24
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.

saramah
  • 168
  • 8
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('-----')])
MattH
  • 37,273
  • 11
  • 82
  • 84
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)
rominf
  • 2,719
  • 3
  • 21
  • 39
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
casperOne
  • 73,706
  • 19
  • 184
  • 253
gabrtv
  • 3,558
  • 2
  • 23
  • 28
-1

pip install ssh-key-maker

import ssh_key_maker

#for windows users

ssh_key_maker.generate_ssh_key()

grey
  • 209
  • 3
  • 6