Как предотвратить кеширование парольной фразы из скрипта Python на основе gpgme? - PullRequest
6 голосов
/ 14 апреля 2019

Следующий короткий скрипт 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

Ответы [ 4 ]

3 голосов
/ 18 апреля 2019

Копаясь в библиотеке 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.

1 голос
/ 24 апреля 2019
  • Согласно source : Context.decrypt может получить фразу-пароль, используя "pinentry", я думаю, что по умолчанию ее использует контекст (какой-то gpg-agent в вашем случае)

  • в зависимости от среды вашего рабочего стола некоторый «агент» может использоваться как часть пинентри, поэтому он «запоминает» парольную фразу.

  • Я думаю, чтоВы должны инициализировать контекст с помощью pinentry_mode=gpg.constants.SOME_CONSTANT (может быть gpg.constants.PINENTRY_MODE_ERROR ... Я не уверен: не имею опыта работы с gpgme, только что изучил документы и код) Режимы: см. документы

  • Или вы можете остановить / убить gpg-agent / kde-wallet / gnome-keyring: один из них выполняет "кэширование".

  • или добавьтелиния no-use-agent до ~/.gnupg/gpg.conf

  • Возможно, вызов ctx.set_ctx_flag("no-symkey-cache", "1") после инициализации решит вашу проблему (см. другой ответ )

0 голосов
/ 24 апреля 2019

В документации GnuPG , в разделе , глава 9.6 , есть раздел под названием "Список всех команд и опций" .

Который показывает опцию --forget, которую вы можете использовать для:

"Очистить парольную фразу для данного идентификатора кэша из кэша."

В "GnuPG Made Easy" Справочное руководство , в главе 7.5 Управление ключами , есть раздел под названием Удаление ключей , который содержит документацию для функции с именем gpgme_op_delete_ext, которая позволяет удалять открытые ключи.

Вы также можете удалить приватные ключи, используя флаг GPGME_DELETE_ALLOW_SECRET, который согласно документации:

"Если не установлено, удаляются только открытые ключи. Если установлено, секретные ключи также удаляются, если это поддерживается."

ПРИМЕЧАНИЕ: Чтобы пропустить подтверждение пользователя, вы можете использовать флаг GPGME_DELETE_FORCE.

Удачи.

0 голосов
/ 18 апреля 2019

Нет чистого питонического способа сделать это. Самое питонское, что вы можете сделать, это установить для переменной окружения PYTHONDONTWRITEBYTECODE значение 1. Вот код для установки переменной:

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

ПРИМЕЧАНИЕ. Этот код также отключает кэширование на других сценариях Python

...