使用Paramiko从字符串读取OpenSSH私钥的报错与正确实现问询
问题:使用Paramiko从字符串读取OpenSSH私钥失败
现象与报错
自定义OPENSSH_creator函数直接传入字符串格式的私钥key_str作为key_blob,实例化RSAKey/DSSKey/ECDSAKey类时触发AttributeError,报错信息如下:
Traceback (most recent call last): File "/home/meretricula/work/taf.transport.ssh/taf/transport/ssh/local.py", line 40, in <module> print(OPENSSH_creator(key_blob=key_str, password=None)) File "/home/meretricula/work/taf.transport.ssh/taf/transport/ssh/local.py", line 20, in OPENSSH_creator key = key_class(key_blob, password) File "/home/meretricula/.pyenv/versions/taf.transport.ssh_3.9.18/lib/python3.9/site-packages/paramiko/rsakey.py", line 71, in __init__ self._check_type_and_load_cert( File "/home/meretricula/.pyenv/versions/taf.transport.ssh_3.9.18/lib/python3.9/site-packages/paramiko/pkey.py", line 792, in _check_type_and_load_cert msg.rewind() AttributeError: 'str' object has no attribute 'rewind' Process finished with exit code 1
传入StringIO(key_str)同样无效,仅当传入paramiko.message.Message()实例作为key_blob时才能正常工作。
报错原因
- 参数类型误解:
RSAKey/DSSKey/ECDSAKey的构造函数,当第一个参数为字符串时,会被当作密钥文件的路径处理,而非密钥内容。内部逻辑会尝试打开该路径读取文件,后续处理中误将字符串当作Message对象调用rewind()方法,导致属性错误。 - 不支持类文件对象直接传入:这些密钥类的构造函数不接受
StringIO这类类文件对象作为第一个参数,因此传入后仍无法正确解析。
正确实现方式
方法1:使用PKey.from_private_key()(推荐)
paramiko.pkey.PKey提供了from_private_key()方法,专门处理类文件对象形式的密钥内容,会自动识别密钥类型,无需手动循环尝试:
from io import StringIO from paramiko.pkey import PKey from paramiko.ssh_exception import SSHException def OPENSSH_creator(key_blob, password): try: # 将字符串包装为类文件对象 key_file = StringIO(key_blob) # 自动解析密钥类型并加载 return PKey.from_private_key(key_file, password=password) except SSHException as e: print(f"密钥加载失败: {str(e)}") return None
方法2:手动构造Message实例(复杂场景)
若需保留循环尝试密钥类的逻辑,需先解析OpenSSH私钥的base64内容,构造Message实例后传入:
import base64 from paramiko.message import Message from paramiko.rsakey import RSAKey from paramiko.dsskey import DSSKey from paramiko.ecdsakey import ECDSAKey from paramiko.ssh_exception import SSHException def OPENSSH_creator(key_blob, password): # 提取OpenSSH私钥中的base64内容 key_lines = key_blob.strip().split('\n') if not (key_lines[0].startswith('-----BEGIN OPENSSH PRIVATE KEY-----') and key_lines[-1].startswith('-----END OPENSSH PRIVATE KEY-----')): raise ValueError("无效的OPENSSH私钥格式") b64_content = ''.join(key_lines[1:-1]) key_bytes = base64.b64decode(b64_content) # 构造Message实例并跳过开头的标识字符串 msg = Message(key_bytes) msg.get_string() # 跳过"openssh-key-v1" # 循环尝试各密钥类 for key_class in (RSAKey, DSSKey, ECDSAKey): try: msg.rewind() msg.get_string() # 重新跳过标识 return key_class(msg, password) except SSHException: continue return None
关于Paramiko尝试多种加密算法的说明
你提到的Paramiko 3.3.1版本client.py第724-732行代码,是密钥加载的核心逻辑:
- 当通过
key_filename加载密钥时,Paramiko会依次尝试RSAKey、DSSKey、ECDSAKey、Ed25519Key四种密钥类 - 每个类会尝试解析文件内容,若解析失败抛出
SSHException,则自动切换到下一个类 - 该设计的目的是兼容不同类型的密钥文件,无需用户手动指定密钥类型,Paramiko会自动兜底尝试所有支持的类型,直到找到可正确解析的类
内容的提问来源于stack exchange,提问作者Jirafey
相关产品推荐
相关产品推荐

