如何防止基于 gpgme 的 Python 脚本中的密码缓存?

How to prevent passphrase-caching from within a gpgme-based Python script?

以下简短的 Python 脚本采用三个命令行参数:密码、输入路径和输出路径。然后它使用密码解密输入路径的内容,并将解密后的内容放在输出路径中。

from gpg import Context
import sys

pp = sys.argv[1]    # passphrase
enc = sys.argv[2]   # input file (assumed to be encrypted)
dec = sys.argv[3]   # output file

with open(enc, 'rb') as reader, open(dec, 'wb') as writer, Context() as ctx:

    try:

        ctx.decrypt(reader, sink=writer, passphrase=pp)

    except Exception as e:
        print(str(e), file=sys.stderr)

只要提供正确的密码,此解密就可以正常工作,但显然会导致缓存此类正确的密码,因此无论提供的密码如何,任何后续的解密尝试都会成功。 (我在本 post 末尾更完整地说明了我的意思,并提供了版本详细信息。)

显然,正在进行一些密码缓存,但我不太了解细节。

我想知道的是:如何修改 Python 脚本以禁用密码缓存?请注意,我对如何在脚本外禁用密码缓存不感兴趣!我希望脚本自动禁用密码缓存。这可能吗?


这是我上面提到的详细示例。脚本 ./demo.py 就是我上面列出的来源。重要提示:下面给出的代码仅在我从命令行执行时才表现得如此。如果我把它放在一个文件中并将它作为脚本执行(或获取它),那么所有使用错误密码的解密都会失败,无论之前是否使用正确的密码成功解密。

# Prologue: preparation

# First, define some variables

% ORIGINAL=/tmp/original.txt
% ENCRYPTED=/tmp/encrypted.gpg
% DECRYPTED=/tmp/decrypted.txt
% PASSPHRASE=yowzayowzayowza

# Next, create a cleartext original:

% echo 'Cool story, bro!' > "$ORIGINAL"

# Next, encrypt the original using /usr/bin/gpg

% rm -f "$ENCRYPTED"
% /usr/bin/gpg --batch --symmetric --cipher-algo=AES256 --compress-algo=zlib --passphrase="$PASSPHRASE" --output="$ENCRYPTED" "$ORIGINAL"

# Confirm encryption

% od -c "$ENCRYPTED"
0000000 214  \r 004  \t 003 002 304 006 020   %   q 353 335 212 361 322
0000020   U 001   w 350 335   K 347 320 260 224 227 025 275 274 033   X
0000040 020 352 002 006 254 331 374 300 221 265 021 376 254   9   $   <
0000060 233 275 361 226 340 177 330   !   c 372 017   & 300 352   $   k
0000100 252 205 244 336 222   N 027 200   | 211 371   r   Z   ] 353   6
0000120 261 177   b 336 026 023 367 220 354 210 265 002   :   r 262 037
0000140 367   L   H 262 370    
0000146


# Now, the demonstration proper.

# Initially, decryption with the wrong passphrase fails:

% rm -f "$DECRYPTED"
% python ./demo.py "certainly the wrong $PASSPHRASE" "$ENCRYPTED" "$DECRYPTED"
gpgme_op_decrypt_verify: GPGME: Decryption failed


# Decryption with the right passphrase succeeds:

% rm -f "$DECRYPTED"
% python ./demo.py "$PASSPHRASE" "$ENCRYPTED" "$DECRYPTED"
% od -c "$DECRYPTED"
0000000   C   o   o   l       s   t   o   r   y   ,       b   r   o   !
0000020  \n
0000021


# After the first successful decryption with the right
# passphrase, decryption with the wrong passphrase always
# succeeds:

% rm -f "$DECRYPTED"
% python ./demo.py "certainly the wrong $PASSPHRASE" "$ENCRYPTED" "$DECRYPTED"
% od -c "$DECRYPTED"
0000000   C   o   o   l       s   t   o   r   y   ,       b   r   o   !
0000020  \n
0000021


# Some relevant version info

% python -c 'import gpg; print((gpg.version.versionstr, gpg.version.gpgme_versionstr))'
('1.10.0', '1.8.0')

% gpg --version
gpg (GnuPG) 2.1.18
libgcrypt 1.7.6-beta
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /home/kj146/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

% python --version
Python 3.5.3

% uname -ar
Linux parakeet 3.16.0-4-amd64 #1 SMP Debian 3.16.43-2 (2017-04-30) x86_64 GNU/Linux

在Cgpgme库中挖掘(就是你用的Python库包装的),有:

https://www.gnupg.org/documentation/manuals/gpgme/Context-Flags.html#Context-Flags

"no-symkey-cache"
For OpenPGP disable the passphrase cache used for symmetrical en- and decryption.
This cache is based on the message specific salt value. Requires at least GnuPG
2.2.7 to have an effect.

我不确定上下文如何与文件系统或 GPG 代理交互,但您的第一次尝试应该是将此标志设置为 true。

没有纯粹的 pythonic 方法来做到这一点。您可以使用的最 pythonic 是将 PYTHONDONTWRITEBYTECODE 环境变量设置为 1。这是设置变量的代码:

import os
os.environ['PYTHONDONTWRITEBYTECODE'] = 1

注意:此代码还将在其他 python 脚本上禁用缓存

  • 根据 sourceContext.decrypt 可以使用 "pinentry" 获取密码,我认为默认情况下上下文使用它(某种 gpg-agent 在你的例)

  • 根据您的桌面环境,某些 "agent" 可能会用作 pinentry 的一部分,因此它 "remembers" 密码。

  • 我认为你应该用 pinentry_mode=gpg.constants.SOME_CONSTANT 初始化上下文(也许 gpg.constants.PINENTRY_MODE_ERROR...我不确定:没有使用 gpgme 的经验,只是调查了文档和代码)模式:see the docs

  • 或者您可以 stop/kill gpg-agent/kde-wallet/gnome-keyring:其中之一正在执行 "caching".

  • 或在~/.gnupg/gpg.conf

  • 中添加一行no-use-agent
  • 也许初始化后调用ctx.set_ctx_flag("no-symkey-cache", "1")会解决你的问题(见

GnuPG documentation, under chapter 9.6, there's a section called "List of all commands and options" 上。

其中显示了一个 --forget 选项,您可以使用它来:

"Flush the passphrase for the given cache ID from the cache."

"GnuPG Made Easy" Reference Manual, under chapter 7.5 key management, there's a section called deleting keys 中包含名为 gpgme_op_delete_ext 的函数的文档,该函数允许您删除 public 键。

您还可以使用 GPGME_DELETE_ALLOW_SECRET 标志删除私钥,根据文档:

"If not set, only public keys are deleted. If set, secret keys are deleted as well, if that is supported."

注意: 要跳过用户确认,您可以使用 GPGME_DELETE_FORCE 标志。

祝你好运。