Search code examples
pythonsshsftpparamikopysftp

Two factor (key and keyboard-interactive) authentication to SFTP server using Python Paramiko


I am trying to connect to a server via secure SFTP in Python but I keep getting authentication errors. I'm sure I have the right details as I can connect via WinSCP fine:

enter image description here

I have also viewed the output of a Paramiko log file and it says the authentication of the password failed but I'm sure the password is right my code.

Below is the code I am using which has been pieced together from various stackoverflow posts. Any ideas why it won't connect?

import paramiko
import pysftp
from base64 import decodebytes

host = '<my server address>'
port = 6671
user = '<my username>'
password = '<my password>'
private_key = '<location of my private key file>'
host_key_data = b"""<contains my host key data>"""
host_key = paramiko.RSAKey(data=decodebytes(host_key_data))
cnopts = pysftp.CnOpts()
cnopts.hostkeys.add(host, 'ssh-rsa', host_key)
pysftp.Connection(host, username=user, password=password, private_key=private_key, port=port, cnopts=cnopts)

Output of log file:

DEB [20190626-12:51:16.270] thr=4   paramiko.transport: Kex agreed: diffie-hellman-group-exchange-sha256
DEB [20190626-12:51:16.271] thr=4   paramiko.transport: HostKey agreed: ssh-rsa
DEB [20190626-12:51:16.272] thr=4   paramiko.transport: Cipher agreed: aes128-ctr
DEB [20190626-12:51:16.273] thr=4   paramiko.transport: MAC agreed: hmac-sha1
DEB [20190626-12:51:16.274] thr=4   paramiko.transport: Compression agreed: none
DEB [20190626-12:51:16.774] thr=4   paramiko.transport: Got server p (1024 bits)
DEB [20190626-12:51:17.029] thr=4   paramiko.transport: kex engine KexGexSHA256 specified hash_algo <built-in function openssl_sha256>
DEB [20190626-12:51:17.030] thr=4   paramiko.transport: Switch to new keys ...
DEB [20190626-12:51:17.032] thr=2   paramiko.transport: Host key verified (ssh-rsa)
DEB [20190626-12:51:17.032] thr=2   paramiko.transport: Attempting password auth...
DEB [20190626-12:51:17.531] thr=4   paramiko.transport: userauth is OK
INF [20190626-12:51:17.532] thr=4   paramiko.transport: Auth banner: b'FactSet File Transfer System (FTS)\n'
INF [20190626-12:51:17.767] thr=4   paramiko.transport: Authentication (password) failed.

VBA code using WinSCP .NET assembly, which works:

' Setup session options
Dim mySessionOptions As New SessionOptions
With mySessionOptions
    .Protocol = Protocol_Sftp
    .HostName = "<hostname>"
    .PortNumber = 6671
    .UserName = "<username>"
    .Password = "<password>"
    .SshHostKeyFingerprint = "ssh-rsa 2048 fingerprintkey"
    .SshPrivateKeyPath = "location of private key file"
End With  

Solution

  • ! 2019-06-27 17:24:18.691 Authenticating with public key "factset-rsa-key-20170717" from agent
    . 2019-06-27 17:24:18.756 Sending Pageant's response
    ! 2019-06-27 17:24:19.260 Further authentication required
    . 2019-06-27 17:24:19.266 Further authentication required
    . 2019-06-27 17:24:19.500 Prompt (5, SSH server: Password Authentication, Using keyboard-interactive authentication., Password: )
    . 2019-06-27 17:24:19.505 Using stored password.
    

    You are using two factor authentication (key and keyboard-interactive) in WinSCP.

    I'm afraid that pysftp does not support two factor authentication.

    Use Paramiko directly instead, which supports multi-factor authentication. pysftp uses Paramiko internally.

    Implementing a full keyboard-interactive authentication can be a bit more complicated than the other authentication types. But as servers commonly use keyboard-interactive authentication to ask for a single fixed password, some SSH libraries offer a shortcut for this scenario. WinSCP .NET assembly does that in your VBA script too (you specify Password, although WinSCP does keyboard-interactive authentication, not password authentication). And Paramiko does the same. If you use the password argument of Paramiko SSHClient.connect, Paramiko will respond to the first keyboard-interactive authentication prompt with the specified password.

    ssh = paramiko.SSHClient()
    # ... 
    ssh.connect(host, username=user, port=port, password=password, key_filename=private_key)
    

    See also Multi-factor authentication (password and key) with Paramiko


    You will also have to verify the server's host key (the same way you do do in pysftp and WinSCP).
    For a Paramiko solution, see Paramiko "Unknown Server".