如何使用Python生成SSH密钥对

54

我正在尝试编写一个脚本来生成 SSH 身份验证密钥对。

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

现在,/tmp/my.key 文件看起来很好了。

通过运行 ssh-keygen -y -f /tmp/my.key > /tmp/my.key.pub 我可以提取公钥。

我的问题是如何使用Python提取公钥?使用 key.save_pub_key("/tmp/my.key.pub") 保存的内容类似于:

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

当我在寻找像这样的东西时:

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

请检查pycrypto,因为已经有“OpenSSH”格式的exportKey方法。 - Jorge E. Cardona
1
pycrypto已经不再维护,并且存在已知漏洞(https://github.com/dlitz/pycrypto/issues/173)。`pycryptodome`是一个可直接替换的解决方案。 - Merlijn Sebrechts
我认为你想用的是 65537 而不是 65337。前者更为常见。使用其他数字所带来的风险存在争议,但共识是 65537(即 2^16 + 1)是安全的。65337 不符合 NIST 标准。 - Zenexer
12个回答

92

使用cryptography!pycrypto已经不再进行积极开发,如果可能的话,应该使用cryptography。从6月份开始,可以生成SSH公钥:

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
)

注意:您需要至少 1.4.0 版本。

注意:如果您的 SSH 客户端不理解这个私钥格式,请用 TraditionalOpenSSL 替换 PKCS8替换方法


4
需要注意的几点:需要使用版本不低于cryptography >= 1.4.0的密码学库,并且缺少一个导入语句:from cryptography.hazmat.backends import default_backend as crypto_default_backend - Samveen
谢谢!已经相应地进行了调整。 - Dave Halter
你如何将这个转换成另一种格式,即 ssh-rsa AAAABCASDDBM$%3WEAv/3%$F ..... OSDFKJSL43$%^DFg==' - user592419

45

以防有未来的旅行者想要这样做。RSA模块现在支持以OpenSSH格式输出公钥(可能在早期的帖子发布时不支持)。所以我认为您可以使用以下命令完成您需要的操作:

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'))
文件需要以'wb'模式打开,因为密钥必须以二进制模式写入。显然不要将您的私钥存储在/tmp目录下...

7
key.exportKey('PEM')中,参数表示输出的格式。有三个选项:'DER' - 二进制编码,'PEM' - 文本编码,'OpenSSH' - 根据OpenSSH规范的文本编码。 - signal
@signal 根据文档,OpenSSH导出仅适用于公钥(不适用于私钥)。 - datu-puti
我相信你引用的短语与在exportKey中使用“OpenSSH”格式选项有关,而不是exportKey方法本身。例如,文档中说可以为公钥使用“OpenSSH”格式参数,就像我所做的那样,并且可以为私钥使用“PEM”,也像我所做的那样。 - fruitbeeriswrong
3
建议使用加密技术替代pycrypto;请查看其他答案https://dev59.com/fHE95IYBdhLWcg3wE52C#39126754#1301627。 - fatal_error
2
在Python3中,八进制字面量必须有一个“0o”前缀,因此:chmod("/tmp/private.key", 0600)变成chmod("/tmp/private.key", 0o600) - dangirsh
pubkey.exportKey('OpenSSH')在Python 3中存在问题;似乎该项目不再得到积极维护。https://github.com/dlitz/pycrypto/issues/99 - Bharat Kul Ratan

5

编辑于2012年05月09日:

我刚刚意识到pycrypto已经具备了这个功能:

import os
from Crypto.PublicKey import RSA

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

这段代码对我来说是有效的:
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())), )

我刚刚将这段代码添加到了 pycrypto 的一个分支中,地址为 https://github.com/jorgeecardona/pycrypto。 - Jorge E. Cardona
您介意再详细解释一下密钥生成后的操作吗?特别是与“模数”相关的部分? - Will
1
那里有一个填充,我现在无法很好地解释它,这是3年前的事了,这段代码实际上很丑陋,你应该尝试阅读pycrypto的实现而不是这个,代码在这里:https://github.com/dlitz/pycrypto/blob/master/lib/Crypto/PublicKey/RSA.py#L678 - Jorge E. Cardona

4

ssh使用的密钥只是base64编码,我不太了解M2Crypto,但经过快速概述后,似乎可以通过以下方式实现您想要的功能:

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)

我没有用SSH测试生成的密钥,所以请告诉我它是否有效(我认为应该有效)。


1
这非常接近我要找的东西。不幸的是,我不确定它是否有效。b64编码字符几乎与ssh-keygen输出的匹配,但在第一个AAAA和密钥的其余部分之间还有24个字符。也就是说,b64密钥看起来像“ssh-rsa AAAAabcdef...==”,而ssh-keygen密钥看起来像“ssh-rsa AAAA<24个字母>abcdef...==”。还有更多提示吗? - Lee

1

ssh-keygen 输出的 base64 解码版本转化为 key.pub() 内容,keyfile 的格式如下:

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

仔细观察,前4个字节表示字符串ssh-rsa的长度,后面跟着在key.pub()[0]中找到的字节,因此很容易构造。 - manis

1
如果您愿意,您也可以直接使用ssh-keygen命令。您可以将此扩展到创建文件并稍后使用open读取内容,但我这里重点介绍如何从已有密钥创建.pub密钥文件。
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')

0

我只是猜测...但你尝试过类似这样的东西吗?:

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

0

这里有一个使用Twisted Conch库的示例,它在底层利用了PyCrypto。您可以在http://twistedmatrix.com/documents/current/api/twisted.conch.ssh.keys.html找到API文档:

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

0

我不知道Python自带这样的库。

如果你想寻找第三方库,你可能会发现paramiko库很有用(也可以从PyPI下载)。它实现了SSH协议,并具有处理现有密钥的功能,但不支持生成密钥。

在该库中添加密钥生成功能可能是一个有用的补充(你可以与开发人员合作将其纳入Paramiko库中),比从头开始做要容易得多。


0

如果它是一个对象,你能从中获取AAAA...Dfg==字符串吗?如果可以的话,你可以自己打开一个文件并保存它,而不是使用内置的save_pub_key函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接