【问题标题】:Sending a password over SSH or SCP with subprocess.Popen使用 subprocess.Popen 通过 SSH 或 SCP 发送密码
【发布时间】:2013-02-16 11:59:30
【问题描述】:

我正在尝试使用subprocess.Popen 运行scp(安全复制)命令。登录需要我发送密码:

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.

确定密码是正确的。我通过在 shell 上手动调用 scp 来轻松验证它。那么为什么这不起作用呢?

注意,有很多类似的问题,询问subprocess.Popen和发送自动SSH或FTP登录密码:

How can I set a users password in linux from a python script?
Use subprocess to send a password

这些问题的答案不起作用和/或不适用,因为我使用的是 Python 3。

【问题讨论】:

  • 我不熟悉这段代码,但你尝试使用 -p 参数?也是作为验证方法的密钥交换?
  • -p 我的意思是“-p mypassword user@10.0.1.12:/foo/bar/somefile.txt”我不知道你能不能这样做
  • @diego2k 据我所知,scp 不接受通过命令行输入密码的开关(它的手册页不包含任何内容)。一般来说,在命令行上提供密码不是一个好习惯,因为这将被记录在 .bash_history
  • 或许您可以通过创建authorized_keys 文件来解决此问题。
  • 如果您要复制.(一个目录),scp 不需要-r 吗?如果您正在努力让pexpect 工作,我在下面添加了一个功能应该会有所帮助。

标签: python linux ssh python-3.x subprocess


【解决方案1】:

这是一个使用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 应该可以实现类似的操作。

【讨论】:

  • 谢谢。这帮助了我。我正在查,但你能告诉我这条线是什么意思吗? '-q -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oPubkeyAuthentication=no'
  • @alfonso 这些是您可以在命令行上或在您的.ssh/config 中传递给ssh 的选项,而无需-o。第一个告诉 ssh 如果主机已更改其密钥,则不要抱怨,因为自从您上次使用它以来,它可能已重新安装。第二个说使用 /dev/null 而不是 .ssh/known_hosts 所以如果它被重新安装并带有重复条目警告,它将再次停止抱怨。不使用强制输入密码的密钥的最后几天。
  • 对于python 3,应该使用spawnu而不是spawn,根据stackoverflow.com/a/26835855/2118777来避免write() argument must be str, not bytes
  • import tempfileimport pexpect要加,pip install pexpect或者pip3 install pexpect可以安装需要的包
【解决方案2】:

OpenSSH scp 实用程序调用ssh 程序以建立与远程主机的 SSH 连接,并且 ssh 进程处理身份验证。 ssh 实用程序不接受命令行或其标准输入中的密码。我相信这是 OpenSSH 开发人员深思熟虑的决定,因为他们认为人们应该使用更安全的机制,例如基于密钥的身份验证。任何调用 ssh 的解决方案都将遵循以下方法之一:

  1. 使用SSH key 代替密码进行身份验证。
  2. 使用sshpassexpect 或类似工具自动响应密码提示。
  3. 使用(滥用)SSH_ASKPASS 功能来获取ssh 通过调用另一个命令来获取密码,描述为herehere,或在某些答案中here
  4. 让 SSH 服务器管理员启用 host-based authentication 并使用它。请注意,基于主机的身份验证仅适用于某些网络环境。请参阅附加说明 herehere
  5. 使用 perl、python、java 或您喜欢的语言编写您自己的 ssh 客户端。大多数现代编程语言都有 ssh 客户端库,您可以完全控制客户端获取密码的方式。
  6. 下载ssh source code 并构建ssh 的修改版本,以您想要的方式工作。
  7. 使用不同的 ssh 客户端。有免费的和商业的other ssh clients 可用。其中之一可能比 OpenSSH 客户端更适合您的需求。

在这种特殊情况下,鉴于您已经从 python 脚本调用 scp,似乎其中一种方法是最合理的方法:

  1. 使用 pexpect(python 的 expect 模块)调用 scp 并将密码提供给它。
  2. 使用paramiko(python ssh 实现)来执行此 ssh 任务,而不是调用外部程序。

【讨论】:

    【解决方案3】:

    您链接的第二个答案建议您使用 Pexpect(这通常是与需要输入的命令行程序进行交互的正确方法)。其中有一个fork 适用于您可以使用的python3。

    【讨论】:

    • 注意:常规的pexpect 现在支持 Python 3。
    【解决方案4】:

    Pexpect 有一个专门用于此的库:pxssh

    http://pexpect.readthedocs.org/en/stable/api/pxssh.html

    import pxssh
    import getpass
    try:
        s = pxssh.pxssh()
        hostname = raw_input('hostname: ')
        username = raw_input('username: ')
        password = getpass.getpass('password: ')
        s.login(hostname, username, password)
        s.sendline('uptime')   # run a command
        s.prompt()             # match the prompt
        print(s.before)        # print everything before the prompt. 
        s.logout()
    except pxssh.ExceptionPxssh as e:
        print("pxssh failed on login.")
        print(e)
    

    【讨论】:

    • 导入 pxssh \snap install microk8s
    【解决方案5】:

    我猜有些应用程序使用标准输入与用户交互,有些应用程序使用终端交互。在这种情况下,当我们使用 PIPE 写入密码时,我们正在写入标准输入。但 SCP 应用程序从终端读取密码。由于 subprocess 不能使用终端与用户交互,但只能使用 stdin 交互,我们不能使用 subprocess 模块,我们必须使用 pexpect 使用 scp 复制文件。

    欢迎指正。

    【讨论】:

      【解决方案6】:

      这是我基于 pexpect 的 scp 函数。除了密码,它还可以处理通配符(即多个文件传输)。 要处理多个文件传输(即通配符),我们需要通过 shell 发出命令。参考pexpect FAQ

      import pexpect
      
      def scp(src,user2,host2,tgt,pwd,opts='',timeout=30):
          ''' Performs the scp command. Transfers file(s) from local host to remote host '''
          cmd = f'''/bin/bash -c "scp {opts} {src} {user2}@{host2}:{tgt}"'''
          print("Executing the following cmd:",cmd,sep='\n')
      
          tmpFl = '/tmp/scp.log'
          fp = open(tmpFl,'wb')
          childP = pexpect.spawn(cmd,timeout=timeout)
          try:
              childP.sendline(cmd)
              childP.expect([f"{user2}@{host2}'s password:"])
              childP.sendline(pwd)
              childP.logfile = fp
              childP.expect(pexpect.EOF)
              childP.close()
              fp.close()
      
              fp = open(tmpFl,'r')
              stdout = fp.read()
              fp.close()
      
              if childP.exitstatus != 0:
                  raise Exception(stdout)
          except KeyboardInterrupt:
              childP.close()
              fp.close()
              return
      
          print(stdout)
      

      可以这样使用:

      params = {
          'src': '/home/src/*.txt',
          'user2': 'userName',
          'host2': '192.168.1.300',
          'tgt': '/home/userName/',
          'pwd': myPwd(),
          'opts': '',
      }
      
      scp(**params)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-06
        • 2015-08-08
        • 1970-01-01
        • 2014-09-03
        • 2015-08-06
        • 2017-02-26
        • 2014-09-29
        • 2017-10-13
        相关资源
        最近更新 更多