Paramiko 的带 SFTP 的 SSHClient

如何在远程服务器上通过 SSHClient实现 SFTP 传输?我有一个本地主机和两个远程主机。远程主机是备份服务器和 Web 服务器。我需要在备份服务器上找到必要的备份文件,并把它放在网络服务器上的 SFTP。如何让 Paramiko 的 SFTP 传输与 Paramiko 的 SSHClient一起工作?

159065 次浏览

paramiko.SFTPClient

Sample Usage:

import paramiko
paramiko.util.log_to_file("paramiko.log")


# Open a transport
host,port = "example.com",22
transport = paramiko.Transport((host,port))


# Auth
username,password = "bar","foo"
transport.connect(None,username,password)


# Go!
sftp = paramiko.SFTPClient.from_transport(transport)


# Download
filepath = "/etc/passwd"
localpath = "/home/remotepasswd"
sftp.get(filepath,localpath)


# Upload
filepath = "/home/foo.jpg"
localpath = "/home/pony.jpg"
sftp.put(localpath,filepath)


# Close
if sftp: sftp.close()
if transport: transport.close()

If you have a SSHClient, you can also use open_sftp():

import paramiko




# lets say you have SSH client...
client = paramiko.SSHClient()


sftp = client.open_sftp()


# then you can use upload & download as shown above
...

In addition to the first answer which is great but depends on username/password, the following shows how to use an ssh key:

from paramiko import Transport, SFTPClient, RSAKey
key = RSAKey(filename='path_to_my_rsakey')
con = Transport('remote_host_name_or_ip', 22)
con.connect(None,username='my_username', pkey=key)
sftp = SFTPClient.from_transport(con)
sftp.listdir(path='.')

The accepted answer "works". But with its use of the low-level Transport class, it bypasses a host key verification, what is a security flaw, as it makes the code susceptible to Man-in-the-middle attacks.

Better is to use the right Paramiko SSH API, the SSHClient, which does verify the host key:

import paramiko
paramiko.util.log_to_file("paramiko.log")


ssh = paramiko.SSHClient()
ssh.connect(host, username='user', password='password')
# or
# key = paramiko.RSAKey.from_private_key_file('id_rsa')
# ssh.connect(host, username='user', pkey=key)


sftp = ssh.open_sftp()


sftp.get(remotepath, localpath)
# or
sftp.put(localpath, remotepath)

For details about verifying the host key, see:
Paramiko "Unknown Server"

For those anyone need to integrate with an ssh/sftp server that requires a private key and want to perform host key verification for the known host by using a specific public key, here is a snippet code with paramiko:

import paramiko


sftp_hostname = "target.hostname.com"
sftp_username = "tartgetHostUsername"
sftp_private_key = "/path/to/private_key_file.pvt"
sftp_private_key_password = "private_key_file_passphrase_if_it_encrypted"
sftp_public_key = "/path/to/public_certified_file.pub"
sftp_port = 22
remote_path = "."
target_local_path = "/path/to/target/folder"


ssh = paramiko.SSHClient()
    

# Load target host public cert for host key verification
ssh.load_host_keys(sftp_public_key)


# Load encrypted private key and ssh connect
key = paramiko.RSAKey.from_private_key_file(sftp_private_key, sftp_private_key_password)
ssh.connect(host=sftp_hostname, port=sftp_port, username=sftp_username, pkey=key)


# Get the sftp connection
sftp_connection = ssh.open_sftp()


directory_list = sftp_connection.listdir(remote_path)


# ...


if sftp_connection: sftp_connection.close()
if ssh: ssh.close()

Notice that only certificates in classic Openssh format are supported, otherwise needs to be converted with the following commands (also for the latest Openssh formats):

$chmod 400 /path/to/private_key_file.pvt
$ssh-keygen -p -f /path/to/private_key_file.pvt -m pem -P <currentPassphrase> -N <newPassphrase>

In order to avoid man in the middle attack, it is important to do not use paramiko.AutoAddPolicy() and load the public host key programmatically as above or load it from ~/.ssh/known_hosts
The file must be in the format "<host_name> ssh-rsa AAAAB3NzaC1yc2EAAAA..."
In case you don't have the public key and you trust the target host (take care to mitm), you can download it using $ssh-keyscan target.hostname.com command.

The above code is the only way I found to avoid the following error during connection:

paramiko.ssh_exception.SSHException: Server 'x.y.z' not found in known_hosts

This error was prompted also with the following way to load the public certificates:

key = paramiko.RSAKey(data=decodebytes(sftp_public_key))
ssh_client.get_host_keys().add(sftp_hostname, 'ssh-rsa', key)

Also the following code was not able for me to load the certificate (tried also by encoding the certificate in base64):

ssh_client=paramiko.SSHClient()


rsa_key = paramiko.RSAKey.from_private_key_file(sftp_private_key, sftp_private_key_password)
rsa_key.load_certificate(sftp_public_key)

It always ends with:

  File "/usr/local/lib/python3.9/site-packages/paramiko/pkey.py", line 720, in from_string
key_blob = decodebytes(b(fields[1]))
File "/usr/lib64/python3.9/base64.py", line 538, in decodebytes
return binascii.a2b_base64(s)
binascii.Error: Incorrect padding

The above code above worked for the SFTP integration with GoAnywhere.

I hope this is helpful, I've not found any working example and spent many hours in searches and tests. The implementations using pysftp wrapper it is now to be considered as discontinued from 2016.