【问题标题】:Python/POpen/gpg: Supply passphrase and encryption text both through stdin or file descriptorPython/POpen/gpg:通过标准输入或文件描述符提供密码和加密文本
【发布时间】:2012-07-07 04:53:42
【问题描述】:

我正在尝试通过 POpen 的 python 程序远程控制 gpg。
我有一个包含加密数据的文件,我想对其进行解密、修改并重新加密后写回磁盘。
目前我将解密的信息存储在一个临时文件中(程序结束时我shred)。然后我对该文件进行修改,然后使用一个函数重新加密它,该函数将密码短语通过stdin.
代码如下:

def encrypt(source, dest, passphrase, cipher=None):
  """Encrypts the source file.
  @param source Source file, that should be encrypted.
  @param dest Destination file.
  @param passphrase Passphrase to be used.
  @param cipher Cipher to use. If None or empty string gpg's default cipher is
  used.
  """
  phraseecho = Popen(("echo", passphrase), stdout=subprocess.PIPE)

  gpgargs = [
          "gpg",
          "-c",
          "--passphrase-fd", "0", # read passphrase from stdin
          "--output", dest,
          "--batch",
          "--force-mdc"]
  if not cipher is None and len(cipher) > 0:
      gpgargs.extend(("--cipher-algo", cipher))

  gpgargs.append(source)

  encrypter = Popen(
          gpgargs,
          stdin=phraseecho.stdout,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE)
  stdout, stderr = encrypter.communicate()
  rc = encrypter.returncode
  if not rc == 0:
      raise RuntimeError(
              "Calling gpg failed with return code %d: %s" % (rc, stderr))

这工作得很好,但我相当肯定将潜在敏感的解密数据存储在临时文件中是一个相当大的安全漏洞。
所以我想以某种方式重写我的加密/解密函数,使它们能够完全在内存中工作,而不会将敏感数据存储在磁盘上。
还可以通过stdin 传递密码并捕获stdout 以获取解密数据,从而直接进行解密。

另一方面,加密让我发疯,因为我不能只将密码短语和消息传递给“stdin”......至少

encrypter.stdin.write("%s\n%s" % (passphrase, message))

没用。
我的下一个最佳猜测是提供某种内存文件/管道/套接字的文件描述符或任何--passphrase-fd 参数。问题是:我不知道是否存在诸如内存文件之类的东西,或者套接字是否适用,因为我从未使用过它们。

谁能帮助我或为我指出一个更好的解决方案来解决我的问题?
该解决方案不必是可移植的 - 我完全可以使用仅 Linux 的方法。

提前谢谢...

编辑:
非常感谢你们,Lars 和 ryran。两种解决方案都可以完美运行!可惜我只能接受一个

【问题讨论】:

    标签: python ipc pipe gnupg


    【解决方案1】:

    下面是我在Obnam 中用来运行 gpg 的代码, 或许能对你有所帮助。

    def _gpg_pipe(args, data, passphrase):
        '''Pipe things through gpg.
    
        With the right args, this can be either an encryption or a decryption
        operation.
    
        For safety, we give the passphrase to gpg via a file descriptor.
        The argument list is modified to include the relevant options for that.
    
        The data is fed to gpg via a temporary file, readable only by
        the owner, to avoid congested pipes.
    
        '''
    
        # Open pipe for passphrase, and write it there. If passphrase is
        # very long (more than 4 KiB by default), this might block. A better
        # implementation would be to have a loop around select(2) to do pipe
        # I/O when it can be done without blocking. Patches most welcome.
    
        keypipe = os.pipe()
        os.write(keypipe[1], passphrase + '\n')
        os.close(keypipe[1])
    
        # Actually run gpg.
    
        argv = ['gpg', '--passphrase-fd', str(keypipe[0]), '-q', '--batch'] + args
        tracing.trace('argv=%s', repr(argv))
        p = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        out, err = p.communicate(data)
    
        os.close(keypipe[0])
    
        # Return output data, or deal with errors.
        if p.returncode: # pragma: no cover
            raise obnamlib.Error(err)
    
        return out
    
    
    def encrypt_symmetric(cleartext, key):
        '''Encrypt data with symmetric encryption.'''
        return _gpg_pipe(['-c'], cleartext, key)
    
    
    def decrypt_symmetric(encrypted, key):
        '''Decrypt encrypted data with symmetric encryption.'''
        return _gpg_pipe(['-d'], encrypted, key)
    

    【讨论】:

      【解决方案2】:

      克里斯: 由于 Lars 有一个使用 os.pipe 的简单示例,我将提供 Pyrite(我的 gpg 的 GTK 前端)所做的,希望更多的代码示例更好。由于 gui 方面,我的用例比你的要复杂一些——我实际上使用字典进行输入和输出,我有代码以 stdin 作为输入启动 gpg 和以文件作为输入启动它的代码等并发症。

      那个警告说,我和你一样从列表中的 gpg 命令行开始;但是,我没有使用--passphrase-fd 0,而是通过os.pipe() 创建了一个自定义文件描述符以在加载Popen() 实例之前发送密码,该实例具有stdin=subprocess.PIPE 用于输入数据。以下是 pyrite 的 crypt_interface 模块的一些相关(修改)摘录。

      #!/usr/bin/env python
      # Adapted excerpts from Pyrite <http://github.com/ryran/pyrite>
      
      from subprocess import Popen, PIPE, check_output
      ...
       # I/O dictionary obj
       self.io = dict(
          stdin='',   # Stores input text for subprocess
          stdout='',  # Stores stdout stream from subprocess
          stderr=0,   # Stores tuple of r/w file descriptors for stderr stream
          gstatus=0,  # Stores tuple of r/w file descriptors for gpg-status stream
          infile=0,   # Input filename for subprocess
          outfile=0)  # Output filename for subprocess
      ...
      cmd = ['gpg']
      fd_pwd_R, fd_pwd_W = os.pipe()
      os.write(fd_pwd_W, passwd)
      os.close(fd_pwd_W)
      cmd.append('--passphrase-fd')
      cmd.append(str(fd_pwd_R))
      ...
      # If working direct with files, setup our Popen instance with no stdin
      if self.io['infile']:
          self.childprocess = Popen(cmd, stdout=PIPE, stderr=self.io['stderr'][3])
      # Otherwise, only difference for Popen is we need the stdin pipe
      else:
          self.childprocess = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=self.io['stderr'][4])
      
      # Time to communicate! Save output for later
      self.io['stdout'] = self.childprocess.communicate(input=self.io['stdin'])[0]
      
      # Clear stdin from our dictionary asap, in case it's huge
      self.io['stdin'] = ''
      
      # Close os file descriptors
      if fd_pwd_R:
          os.close(fd_pwd_R)
      time.sleep(0.1)  # Sleep a bit to ensure everything gets read
      os.close(self.io['stderr'][5])
      if self.io['gstatus']:
          os.close(self.io['gstatus'][6])
      ...
      

      调用所有等待直到self.childprocess 对象具有returncode 属性并假设返回码是0 并且输入是文本(而不是文件)的函数,然后它从中读取gpg 的标准输出字典并将其打印到屏幕上。

      很高兴回答问题或根据我有限的经验提供帮助。可以通过以下链接找到我的联系信息。

      编辑:您可能还会发现a4crypt 具有指导意义,因为它是 gpg 的一个更简单的前端——这是我为了学习 python 而开始的项目,后来在我“完成”后被封存(如果有这样的东西)黄铁矿。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多