[Tutor] GPG password entry automation help
rmlibre at riseup.net
rmlibre at riseup.net
Sun Dec 1 18:44:18 EST 2019
I sent a request for help some months ago for automating commands sent
to gpg2 to create Ed-25519 ECC keys on Linux systems. Joe responded with
a very helpful solution. I implemented the solution, but then ran into
another roadblock towards automation: The Ubuntu pin-entry dialog pops
up asking for the password to encrypt the created key.
I worked for a long while trying to re-figure out how to send the
password through the GPG interface, but I just could not understand how
to do it. So I used a wretched hack to avoid requiring user interaction:
I used the pynput library to take control of the keyboard in a separate
process and press enter several times until the dialog disappears. Ew.
I would be incredibly grateful if someone had a full solution which uses
GPG's native functionality for automating password entry as well as
handling responses to the interactive terminal prompt. The constraints
are: creating specifically Ed-25519 curve keys; a generalizable solution
for further working with GPG's native functionality (not only a password
entry solution for key creation). Optionally: A solution which doesn't
save the created keys as system-wide (that aren't available on the
system keyring).
Here's the current code. Either a solution which tweaks this code, or
something general which I'll have to implement, are totally welcome.
>import subprocess
>from time import sleep
>from pynput import keyboard
>from multiprocessing import Process
>
>class GnuPG:
> def __init__(self, username, email, passphrase):
> self.email = email
> self.username = username
> self.passphrase = passphrase
> self._init_gpg_directory()
> self.create_inputs()
> self.create_commands()
> self.gen_key()
>
> def _init_gpg_directory(self):
> # Path to the gpg2 executable
> self.gpg_path = "gpghome/gpg2"
>
> def create_inputs(self):
> # Each stage of the key creation program takes one
> # element from this list in order as a response.
> inputs = [
> "11",
> "Q",
> "1",
> "0",
> "y",
> self.username,
> self.email,
> "none",
> "O",
> ]
> self.input_data = self.encode_input(inputs)
>
> def encode_input(self, user_input):
> return ("\n".join(user_input) + "\n").encode()
>
> def create_commands(self):
> # The commands and options passed to the terminal
> self.commands = [
> self.gpg_path,
> "--expert",
> "--full-gen-key",
> "--with-colons",
> "--command-fd",
> "0",
> "--status-fd",
> "1",
> ]
>
> def gen_key(self, commands=None, user_input=None):
> process = Process(target=self.type_passphrase)
> process.start()
> self.parse_output(self.commands, self.input_data)
>
> def type_passphrase(self):
> # The pin-entry box wasn't accepting text from pynput.
> # But sending newline characters worked for some reason.
> # So, I just skipped password entry....
> key_presser = keyboard.Controller()
> sleep(0.3)
> for _ in range(4):
> sleep(0.2)
> key_presser.type("\n")
>
> def parse_output(self, commands, user_input):
> self._output = subprocess.check_output(
> commands, input=user_input
> )
> try:
> self.output_lines = self._output.split("\n")
> except:
> self.output_lines = self._output
> self.key_id = self._output[-41:-1]
>
> @property
> def output(self):
> return self.output_lines
>
More information about the Tutor
mailing list