可以使用Paramiko在不知道私有SSH密钥类型的情况下读取它吗?

huangapple go评论62阅读模式
英文:

Can I read private SSH key without knowing its type with paramiko?

问题

我正在使用paramiko.SSHClient通过SSH密钥对远程服务器进行身份验证。为此,我有几个不同服务器的配置,其中包含SSH密钥的路径。而且碰巧有一些是Ed25519类型,另一些是RSA类型。仅使用密码进行连接是不可接受的选项。

起初,我想使用以下代码进行连接:

keyfilename = build_configuration['sshPrivateKeypath']
keyfilename = os.path.expandvars(keyfilename)
key = paramiko.Ed25519Key.from_private_key_file(keyfilename)

ssh.connect(ssh_host, port=port, username=ssh_user, pkey=key)

但结果是,某些密钥是RSA类型而不是Ed25519类型。

是否有一种方法可以读取任何类型的SSH密钥,而不使用具体的实现?或者也许有一种方法可以确定密钥的类型吗?
还是唯一的方法是将所有密钥更改为已知类型?

文档中无法找到这些信息。

在测试真实配置之前,我尝试从Jupyter上的本地PC上读取SSH密钥。当像这样读取错误类型的密钥时:

keyfilename = "%USERPROFILE%/.ssh/id_rsa"
keyfilename = os.path.expandvars(keyfilename)
privateKey = paramiko.Ed25519Key.from_private_key_file(keyfilename)

它会引发以下错误:

---------------------------------------------------------------------------
SSHException                              Traceback (most recent call last)
Cell In[2], line 6
      4 keyfilename = "%USERPROFILE%/.ssh/id_rsa"
      5 keyfilename = os.path.expandvars(keyfilename)
----> 6 privateKey = paramiko.Ed25519Key.from_private_key_file(keyfilename)
      7 display(key)
      8 display(privateKey)

File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\paramiko\pkey.py:421, in PKey.from_private_key_file(cls, filename, password)
    400 @classmethod
    401 def from_private_key_file(cls, filename, password=None):
    402     """
    403     Create a key object by reading a private key file.  If the private
    404     key is encrypted and ``password`` is not ``None``, the given password
   (...)
    419     :raises: `.SSHException` -- if the key file is invalid
    420     """
--> 421     key = cls(filename=filename, password=password)
    422     return key

File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\paramiko\ed25519key.py:65, in Ed25519Key.__init__(self, msg, data, filename, password, file_obj)
     62     pkformat, data = self._read_private_key("OPENSSH", file_obj)
     64 if filename or file_obj:
---> 65     signing_key = self._parse_signing_key_data(data, password)
     67 if signing_key is None and verifying_key is None:
     68     raise ValueError("need a key")

File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\paramiko\ed25519key.py:114, in Ed25519Key._parse_signing_key_data(self, data, password)
    112     pubkey = Message(message.get_binary())
    113     if pubkey.get_text() != self.name:
--> 114         raise SSHException("Invalid key")
    115     public_keys.append(pubkey.get_binary())
    117 private_ciphertext = message.get_binary()

SSHException: Invalid key

因此,我期望在尝试与真实配置进行相同操作时会引发相同的错误。

英文:

I'm authenticating to remote server with paramiko.SSHClient by SSH key. For it I have several configs for different servers with paths to SSH keys. And it happened so that some of them are Ed25519 and some are RSA. Connecting with just password is not an acceptable option.

At first I wanted to connect with the following code:

keyfilename = build_configuration['sshPrivateKeypath']
keyfilename = os.path.expandvars(keyfilename)
key = paramiko.Ed25519Key.from_private_key_file(keyfilename)

ssh.connect(ssh_host, port=port, username=ssh_user, pkey=key)

But it turned out that some keys are RSA (not Ed25519).

Is there any way of reading SSH key of any type without using concrete implementation? Or maybe there's some way to determine the type?
Or the only way is to change all keys to known type?

Could not figure that of from documentation

Before testing real configs I tried to read SSH key on my local PC from Jupyter.
When reading wrong type like this:

keyfilename = "%USERPROFILE%/.ssh/id_rsa"
keyfilename = os.path.expandvars(keyfilename)
privateKey = paramiko.Ed25519Key.from_private_key_file(keyfilename)

It throws:

---------------------------------------------------------------------------
SSHException                              Traceback (most recent call last)
Cell In[2], line 6
      4 keyfilename = "%USERPROFILE%/.ssh/id_rsa"
      5 keyfilename = os.path.expandvars(keyfilename)
----> 6 privateKey = paramiko.Ed25519Key.from_private_key_file(keyfilename)
      7 display(key)
      8 display(privateKey)

File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\paramiko\pkey.py:421, in PKey.from_private_key_file(cls, filename, password)
    400 @classmethod
    401 def from_private_key_file(cls, filename, password=None):
    402     """
    403     Create a key object by reading a private key file.  If the private
    404     key is encrypted and ``password`` is not ``None``, the given password
   (...)
    419     :raises: `.SSHException` -- if the key file is invalid
    420     """
--> 421     key = cls(filename=filename, password=password)
    422     return key

File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\paramiko\ed25519key.py:65, in Ed25519Key.__init__(self, msg, data, filename, password, file_obj)
     62     pkformat, data = self._read_private_key("OPENSSH", file_obj)
     64 if filename or file_obj:
---> 65     signing_key = self._parse_signing_key_data(data, password)
     67 if signing_key is None and verifying_key is None:
     68     raise ValueError("need a key")

File ~\AppData\Local\Programs\Python\Python311\Lib\site-packages\paramiko\ed25519key.py:114, in Ed25519Key._parse_signing_key_data(self, data, password)
    112     pubkey = Message(message.get_binary())
    113     if pubkey.get_text() != self.name:
--> 114         raise SSHException("Invalid key")
    115     public_keys.append(pubkey.get_binary())
    117 private_ciphertext = message.get_binary()

SSHException: Invalid key

So I expect that it will throw the same thing when I try it with real configs.

答案1

得分: 2

Simplest way is to use key_filename parameter of SSHClient.connect instead of pkey.


If you want to use pkey for whatever reason, you would have to replicate what SSHClient does internally, when handling the key_filename. Roughly something like this (untested):

for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
    try:
        key = pkey_class.from_private_key_file(key_filename)
        break
    except Exception as e:
        pass
英文:

Simplest way is to use key_filename parameter of SSHClient.connect instead of pkey.


If you want to use pkey for whatever reason, you would have to replicate what SSHClient does internally, when handling the key_filename. Roughly something like this (untested):

for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
    try:
        key = pkey_class.from_private_key_file(key_filename)
        break
    except Exception as e:
        pass

答案2

得分: 1

def get_key_class_name(filepath, passphrase):
    for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
        try:
            key = pkey_class.from_private_key_file(filepath, passphrase)
            class_name = str(pkey_class).split('.')[-1].rstrip("'>")
    
            return class_name
    
        except Exception as e:
            # print("An exception occurred: {}".format(e))
            pass
    
def proper_method_call(filepath, passphrase):
    key_class_name = get_key_class_name(filepath, passphrase)
    key_class = getattr(paramiko, key_class_name)
    key_method = getattr(key_class, "from_private_key_file")
    key = key_method(filepath, passphrase)
    return key

rsa_key_with_passphrase = proper_method_call(rsa_key_filename, key_passphrase)

pass it as an argument: `pkey=rsa_key_with_passphrase`
英文:
def get_key_class_name(filepath, passphrase):
    for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
        try:
            key = pkey_class.from_private_key_file(filepath, passphrase)
            class_name = str(pkey_class).split('.')[-1].rstrip(">'")

            return class_name

        except Exception as e:
            # print("An exception occurred: {}".format(e))
            pass

def proper_method_call(filepath, passphrase):
    key_class_name = get_key_class_name(filepath, passphrase)
    key_class = getattr(paramiko, key_class_name)
    key_method = getattr(key_class, "from_private_key_file")
    key = key_method(filepath, passphrase)
    return key

rsa_key_with_passphrase = proper_method_call(rsa_key_filename, key_passphrase)

pass it as an argument: pkey=rsa_key_with_passphrase

huangapple
  • 本文由 发表于 2023年6月15日 00:17:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76475640.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定