[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