== Create a password prompt with Python

Passwords are particularly problematic for programmers.
You're not supposed to store them without encrypting them, and you're not supposed to reveal what's been typed when your user enters one.
This became particularly important to me when I decided that I wanted to boost security on my laptop.
I encrypt my home directory but once I've logged in, any password stored as plain text in a configuration file is potentially exposed to prying eyes.

Specifically, I use an application called Mutt as my email client.
It lets me read and compose email in my Linux terminal, but normally it expects a password in its configuration file.
I restricted permissions on my Mutt config file so that only I could see it, but I'm the only user of my laptop so I wasn't really concerned about authenticated users inadvertently looking at my configs.
Instead, I wanted to protect myself from absent-mindedly posting my config online, either for bragging rights or for version control, with my password exposed.
In addition, although I have no expectation of unwelcomed guests on my system, I did want to ensure that an intruder couldn't obtain my password just by running `cat` on my config.

== Python GnuPG

The Python module `python-gnupg` is a Python wrapper for the `gpg` application.
The module's name is `python-gnupg`, which you must not confuse with a module called `gnupg`.

GnuPG is the default encryption system for Linux, and I've been using it since 2009 or so.
I feel comfortable with it, and have a high level of trust for its security.

I decided that the best way to get my password into Mutt was to store my password inside of an encrypted GPG file, and then to create a prompt for my GPG password to unlock the encrypted file, and hand the password over to Mutt (actually to the `offlineimap` command, which I use to syncronize my laptop with the email server.)

https://opensource.com/article/20/12/learn-python[Getting user input with Python] is pretty easy.
You make a call to `input`, and whatever the user types is stored as a variable:

[source, Python]
----
print("Enter password: ")
myinput = input()

print("You entered: ", myinput)
----

The problem for me, however, was that when I typed a password into the terminal in response to my password prompt, everything I typed was visible to anyone looking over my shoulder or scrolling through my terminal history.

[source, bash]
----
$ ./test.py
Enter password: my-Complex-Passphrase
----

== Invisible password entry with getpass 

As is often the case, there's a Python module that's already solved my problem.
The module is `getpass4`, and from the user's perspective, it behaves exactly like `input` except without displaying what the user is typing.

You can install both modules with https://opensource.com/article/19/11/python-pip-cheat-sheet[pip]:

[source, bash]
----
$ python -m pip install --user \
python-gnupg getpass4
----

Here's my Python script to create a password prompt:

[SOURCE, Python]
----
#!/usr/bin/env python
# by Seth Kenlon
# GPLv3

# install deps:
# python3 -m pip install --user python-gnupg getpass4

import gnupg
import getpass
from pathlib import Path

def get_api_pass():
    homedir = str(Path.home())
    gpg = gnupg.GPG(gnupghome=os.path.join(homedir,".gnupg"), use_agent=True)
    passwd = getpass.getpass(prompt="Enter your GnuPG password: ", stream=None)

    with open(os.path.join(homedir,'.mutt','pass.gpg'), 'rb') as f:
        apipass = (gpg.decrypt_file(f, passphrase=passwd))

    f.close()

    return str(apipass)
    
if __name__ == "__main__":
    apipass = get_api_pass()
    print(apipass)
----

Save the file as `password_prompt.py` if you want to try it out.
If you're using `offlineimap` and want to use this solution for your own password entry, then save it to some location you can point `offlineimap` to in your `.offlineimaprc` file (I use `~/.mutt/password_prompt.py`.)

== Testing the password prompt

To see the script in action, you first must create an encrypted file.
For this article, I assume that you already have GPG set up.

[source,bash]
----
$ echo "hello world" > pass
$ gpg --encrypt pass
$ mv pass.gpg ~/.mutt/pass.gpg
$ rm pass
----

Now run the Python script:

[source,bash]
----
$ python ~/.mutt/password_prompt.py
Enter your GPG password:
hello world
----

Nothing displays as you type, but as long as you enter your GPG passphrase correctly, you see the test message.

== Integrating the password prompt with offlineimap

For me, I needed to integrate my new prompt with the `offlineimap` command.
I chose Python for this script because I knew that `offlineimap` was able to make calls to Python applications.
If you're an `offlineimap` user yourself, you'll appreciate that the only "integration" required is changing two lines in your `.offlineimaprc` file.

First, add a line referencing the Python file:

[source, text]
----
pythonfile = ~/.mutt/password_prompt.py
----

And then replace the `remotepasseval` line in `.offlineimaprc` with a call to the `get_api_pass()` function in `password_prompt.py`:

[source, text]
----
remotepasseval = get_api_pass()
----

No more passwords in your config file!

== Security matters

It sometimes feels almost paranoid to think about security minutiae on your personal computer.
Does your SSH config really need to be restricted to 600?
Does it really matter that your email password is in an inconsequential config file buried within a hidden folder called, of all things, `.mutt`?
Probably not.
And yet knowing that I don't have sensitive data quietly hidden away in my config files makes it a lot easier for me to commit files to public Git repositories, to copy and paste snippets into support forums, and to share my knowledge in the form of actual, known-good configuration files.
For that alone, improved security has made my life easier.
And with so many great Python modules available to help, it's easy to implement.