6

I'm trying to use a keypair for the SSH connection to an SFTP server.

I am able to do so if I generate an RSA key via ssh-keygen -t rsa.

When I connect to the server via Paramiko, things work fine:

    private_key = paramiko.RSAKey.from_private_key_file("/path/to/my/private/key")
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    print("Connecting.")
    client.connect(hostname="host.sftp.com", username="user", pkey=private_key)
    print("Connected.")

However, if I try to do this with a ED25519 key, I get the below error:

ssh-keygen -t ed25519
  File "/usr/local/lib/python3.7/site-packages/paramiko/pkey.py", line 235, in from_private_key_file
    key = cls(filename=filename, password=password)
  File "/usr/local/lib/python3.7/site-packages/paramiko/rsakey.py", line 55, in __init__
    self._from_private_key_file(filename, password)
  File "/usr/local/lib/python3.7/site-packages/paramiko/rsakey.py", line 176, in _from_private_key_file
    self._decode_key(data)
  File "/usr/local/lib/python3.7/site-packages/paramiko/rsakey.py", line 192, in _decode_key
    n, e, d, iqmp, q, p = self._uint32_cstruct_unpack(data, "iiiiii")
  File "/usr/local/lib/python3.7/site-packages/paramiko/pkey.py", line 529, in _uint32_cstruct_unpack
    raise SSHException(str(e))
paramiko.ssh_exception.SSHException: unpack requires a buffer of 4 bytes

I'm a bit at a loss here since googling around doesn't seem to yield any relevant solutions. Is this a bug within paramiko? It is an issue with how I am initializing my SSHClient? Or is it actually a theoretical issue (ie. the way ED25519 creates the key, it is not possible to read in via the low-level unpack() call)?

Yu Chen
  • 6,540
  • 6
  • 51
  • 86
  • 1
    `paramiko.RSAKey.from_private_key_file` reads RSA keys. To read an Ed25519 key use `paramiko.ed25519key.from_private_key_file`. (And at least 2.2.0.) – dave_thompson_085 Mar 12 '20 at 20:32
  • :facepalm. Wow. I feel like an idiot. Go ahead and write that as an answer and I'll accept. – Yu Chen Mar 12 '20 at 20:34

2 Answers2

7

This is Dave Thompson's comment as an answer to just close the loop on this:

paramiko.RSAKey.from_private_key_file reads RSA keys. To read an Ed25519 key use paramiko.Ed25519Key.from_private_key_file. (And at least 2.2.0.)

lapinkoira
  • 8,320
  • 9
  • 51
  • 94
Yu Chen
  • 6,540
  • 6
  • 51
  • 86
0

My use case was dealing with unknown key types, which results in the same error. My private key is user supplied and I would not know beforehand what format it would be in. Figured I'd post in case others are searching for a similar solution. This method checks the key type using the cryptography library and returns the corresponding Paramiko PKey.

from io import StringIO
from paramiko import RSAKey, Ed25519Key, ECDSAKey, DSSKey, PKey
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import ed25519, dsa, rsa, ec

def from_private_key(file_obj, password=None) -> PKey:
    private_key = None
    file_bytes = bytes(file_obj.read(), "utf-8")
    try:
        key = crypto_serialization.load_ssh_private_key(
            file_bytes,
            password=password,
        )
        file_obj.seek(0)
    except ValueError:
        key = crypto_serialization.load_pem_private_key(
            file_bytes,
            password=password,
        )
        if password:
            encryption_algorithm = crypto_serialization.BestAvailableEncryption(
                password
            )
        else:
            encryption_algorithm = crypto_serialization.NoEncryption()
        file_obj = StringIO(
            key.private_bytes(
                crypto_serialization.Encoding.PEM,
                crypto_serialization.PrivateFormat.OpenSSH,
                encryption_algorithm,
            ).decode("utf-8")
        )
    if isinstance(key, rsa.RSAPrivateKey):
        private_key = RSAKey.from_private_key(file_obj, password)
    elif isinstance(key, ed25519.Ed25519PrivateKey):
        private_key = Ed25519Key.from_private_key(file_obj, password)
    elif isinstance(key, ec.EllipticCurvePrivateKey):
        private_key = ECDSAKey.from_private_key(file_obj, password)
    elif isinstance(key, dsa.DSAPrivateKey):
        private_key = DSSKey.from_private_key(file_obj, password)
    else:
        raise TypeError
    return private_key
ElJeffe
  • 637
  • 1
  • 8
  • 20