4

を介してPythonプログラムを介してgpgをリモートコントロールしようとしていPOpenます。
暗号化されたデータを含むファイルがあり、これを復号化して変更し、再暗号化してディスクに書き戻します。
現在、復号化された情報を一時ファイルに保存しています(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 の両方に感謝します。どちらのソリューションも完璧に機能します。残念ながら、私は1つしか受け入れることができません

4

2 に答える 2

2

Chris: Lars のおかげで os.pipe を使用した単純な例が得られたので、より多くのコード例がより優れていることを期待して、 Pyrite (私の gpg の GTK フロントエンド) が同様に行うことを提供します。私のユースケースは、GUI の側面により、あなたのユースケースよりも少し複雑です。実際には、入力と出力に辞書を使用しており、stdin を入力として gpg を起動するコードと、ファイルを入力として起動するコードなどがあります。合併症。

その警告によると、私はあなたと同じようにリスト内の gpg コマンドラインから始めます。ただし、を使用する代わりに、入力データ用のインスタンスをロードする前にパスフレーズを送信--passphrase-fd 0するカスタム ファイル記述子を作成します。以下は、pyrite のcrypt_interfaceモジュールからの関連する (変更された) 抜粋です。os.pipe()Popen()stdin=subprocess.PIPE

#!/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属性を持ち、リターンコードがテキスト (ファイルではなく) であると仮定するまで待機する all を呼び出す関数は0、その辞書から gpg の stdout を読み取り、それを画面に出力します。

質問に答えたり、私の限られた経験から喜んでお手伝いします. リンクをたどることで私の連絡先情報を見つけることができます。

編集: a4cryptは gpg のはるかに単純なフロントエンドであるため、有益である場合もあります。これは、Python を学習するために開始したプロジェクトであり、後で (そのようなものがある場合) 黄鉄鉱を「完了」した後に停止しました。

于 2012-07-06T23:53:01.123 に答える
2

以下は、私が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)
于 2012-07-06T22:55:31.683 に答える