通过subprocess.Popen在SSH或SCP中发送密码

15 投票
7 回答
78619 浏览
提问于 2025-04-17 17:38

我正在尝试使用 scp(安全复制)命令,通过 subprocess.Popen 来执行。这个登录需要我输入一个密码:

from subprocess import Popen, PIPE

proc = Popen(['scp', "user@10.0.1.12:/foo/bar/somefile.txt", "."], stdin = PIPE)
proc.stdin.write(b'mypassword')
proc.stdin.flush()

但是这马上就返回了一个错误:

user@10.0.1.12's password:
Permission denied, please try again.

确定密码是正确的。我可以通过手动在命令行中运行 scp 来轻松验证这一点。那么为什么这个方法不行呢?

需要注意的是,有很多类似的问题在讨论 subprocess.Popen 和如何发送密码以实现自动化的 SSH 或 FTP 登录:

如何通过 Python 脚本在 Linux 中设置用户密码?
使用 subprocess 发送密码

这些问题的答案不适用或者不管用,因为我使用的是 Python 3。

7 个回答

10

OpenSSH的scp工具会调用ssh程序来建立与远程主机的SSH连接,而ssh进程负责身份验证。ssh工具不接受命令行或标准输入中的密码。我认为这是OpenSSH开发者的一个故意选择,因为他们认为人们应该使用更安全的方式,比如基于密钥的身份验证。任何调用ssh的解决方案都会遵循以下几种方法:

  1. 使用SSH密钥进行身份验证,而不是使用密码。
  2. 使用sshpassexpect或类似工具来自动响应密码提示。
  3. 使用(滥用)SSH_ASKPASS功能,通过调用另一个命令来获取ssh的密码,具体描述可以在这里这里找到,或者在一些答案中这里也有提到。
  4. 让SSH服务器管理员启用基于主机的身份验证并使用它。请注意,基于主机的身份验证只适合某些网络环境。更多信息可以在这里这里找到。
  5. 使用perl、python、java或你喜欢的语言编写自己的ssh客户端。大多数现代编程语言都有ssh客户端库,你可以完全控制客户端如何获取密码。
  6. 下载ssh源代码,构建一个符合你需求的修改版ssh
  7. 使用其他ssh客户端。还有其他ssh客户端可供选择,包括免费和商业版本。也许其中一个更适合你的需求,而不是OpenSSH客户端。

在这种情况下,考虑到你已经在python脚本中调用scp,以下两种方法似乎是最合理的选择:

  1. 使用pexpect,这是一个python的expect模块,来调用scp并将密码传递给它。
  2. 使用paramiko,这是python的ssh实现,直接完成这个ssh任务,而不是调用外部程序。
14

这里有一个用 pexpect 来通过密码进行 ssh 连接的函数:

import pexpect
import tempfile

def ssh(host, cmd, user, password, timeout=30, bg_run=False):                                                                                                 
    """SSH'es to a host using the supplied credentials and executes a command.                                                                                                 
    Throws an exception if the command doesn't return 0.                                                                                                                       
    bgrun: run command in the background"""                                                                                                                                    
                                                                                                                                                                               
    fname = tempfile.mktemp()                                                                                                                                                  
    fout = open(fname, 'w')                                                                                                                                                    
                                                                                                                                                                               
    options = '-q -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oPubkeyAuthentication=no'                                                                         
    if bg_run:                                                                                                                                                         
        options += ' -f'                                                                                                                                                       
    ssh_cmd = 'ssh %s@%s %s "%s"' % (user, host, options, cmd)                                                                                                                 
    child = pexpect.spawn(ssh_cmd, timeout=timeout)  #spawnu for Python 3                                                                                                                          
    child.expect(['[pP]assword: '])                                                                                                                                                                                                                                                                                               
    child.sendline(password)                                                                                                                                                   
    child.logfile = fout                                                                                                                                                       
    child.expect(pexpect.EOF)                                                                                                                                                  
    child.close()                                                                                                                                                              
    fout.close()                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                        
    fin = open(fname, 'r')                                                                                                                                                     
    stdout = fin.read()                                                                                                                                                        
    fin.close()                                                                                                                                                                
                                                                                                                                                                               
    if 0 != child.exitstatus:                                                                                                                                                  
        raise Exception(stdout)                                                                                                                                                
                                                                                                                                                                               
    return stdout

scp 也应该能做到类似的事情。

7

你链接的第二个答案建议你使用 Pexpect 这个工具(通常来说,这是与需要输入的命令行程序进行交互的正确方法)。

撰写回答