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

6
以下简短的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)

这种解密方式在提供正确密码的情况下可以正常工作,但显然会缓存这个正确密码,因此任何随后的解密尝试都将成功,而不管提供的密码是否正确。(我在本帖末尾给出了更详细的说明和版本细节。)
显然,这里有一些密码缓存正在进行,但我真的不理解细节。
我想知道的是:我如何修改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

2
Python脚本中没有缓存。它通过口令作为命令行参数调用。如果您使用相同的参数多次调用它,脚本无法处理。 - zvone
print(dir(ctx)) 显示什么? - Nick Vitha
@kjo 你需要在 pygpgme_context_init 中使用 self->ctx->no_symkey_cache = 1; 来安装 pygpgme。我在我的回答中添加了一个注释,说明如何操作。 - Filip Dimitrovski
4个回答

4
在深入研究 C 语言的 gpgme 库(这是 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。


这一行插入 self->ctx->no_symkey_cache = 1; 并重新编译。 - Filip Dimitrovski
请确保您拥有最新版本的gpgme源代码,并且上下文结构具有该标志。实际上,最新的源代码默认将其设置为1。 - Filip Dimitrovski
GPGME将始终调用gpg-agent来处理密码,Python绑定只是告诉GPGME做同样的事情。 - Ben

1
  • 根据来源:通过"pinentry",Context.decrypt可以获取密语。我认为默认情况下上下文使用它(在您的情况下是某种gpg-agent)

  • 根据您的桌面环境,可能会使用一些“代理”作为pinentry的一部分,因此它“记住”了密语。

  • 我认为您应该使用pinentry_mode=gpg.constants.SOME_CONSTANT来初始化上下文(也许是gpg.constants.PINENTRY_MODE_ERROR...我不确定:没有使用gpgme的经验,只是查看了文档和代码)。模式:请参阅文档

  • 或者您可以停止/杀死gpg-agent/kde-wallet/gnome-keyring之一:它们中的一个正在执行“缓存”操作。

  • 或者在~/.gnupg/gpg.conf中添加一行no-use-agent

  • 也许在初始化后调用ctx.set_ctx_flag("no-symkey-cache", "1")将解决您的问题(请参见其他答案


0

没有纯粹的Python方法来做到这一点。最Pythonic的方法是将PYTHONDONTWRITEBYTECODE环境变量设置为1。以下是设置变量的代码:

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

注意:此代码还将禁用其他Python脚本的缓存


0

GnuPG文档 上,第9.6章中有一个名为"所有命令和选项列表"的部分。

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

"从缓存中清除给定缓存ID的密码短语。"

“GnuPG Made Easy”参考手册中,第7.5密钥管理章节下有一个称为删除密钥的部分,其中包含名为gpgme_op_delete_ext的函数文档,该函数允许您删除公钥。
您也可以使用GPGME_DELETE_ALLOW_SECRET标志删除私钥,根据文档:

"如果未设置,则仅删除公钥。如果设置,则支持情况下也会删除秘密密钥。"

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

1
这不是Python/C的解决方案。OP明确表示不建议使用子进程调用。 - Filip Dimitrovski
1
@LogicalBranch:你知道吗,你是正确的。我已经删除了我的评论。请接受我的道歉。 - kjo
@kjo 别担心 :) 在你的第二条评论之后,我明白了你的意思和你想要表达的内容。 - Malekai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接