⇦ Back

Python can help you generate and store passwords in a cryptographically safe way, which is very useful for designing apps and programmes or just generally keeping your accounts secure.

1 The Wrong Way to Do It

What you don’t want is to have a plain text file called “passwords.txt” or something stored on your computer. It would be obvious to a hacker what it is!

passwords = []
with open('passwords.txt') as file:
    for line in file:
        passwords.append(line)
print(passwords[0])
## password123

2 passpy

The passpy package is a password manager made by bfrascher that can be used on any operating system from the terminal or from within Python. It is built on top of a programme called pass from ZX2C4 which in turn uses GNU Privacy Guard (GnuPG or GPG).

When using passpy your passwords get saved in GPG-encrypted files on your computer. As well as being safer than plaintext files these can also be used together with Git which can help you to:

  • Version control your passwords
  • Sync your passwords across multiple computers
  • Back them up in a cloud repository

Find out more:

A useful StackExchange page: https://unix.stackexchange.com/questions/53912/i-try-to-add-passwords-to-the-pass-password-manager-but-my-attempts-fail-with

2.1 Installation

  • Install the passpy Python package from the terminal with python3.11 -m pip install passpy (replace python3.11 with the version you have installed and are using)
  • GnuPG comes pre-installed on some OSs, so check if you already have it with gpg --version. If you don’t have it, install it from here: https://gnupg.org/download/index.html
  • Install gpg2 from the terminal with sudo apt-get install gnupg2 -y
  • Install pass from the terminal with sudo apt install pass

2.2 Setup

  • Create a GPG public key with gpg --gen-key. Follow the prompts to set up a user ID.
    • Your public key will be printed to the console during this process, but if you want to view your key again in the future you can use gpg --list-keys
  • Using the pass programme, initialise a password store (ie a folder in which your encrypted password files will be stored) with pass init <key> where <key> is your GPG public key that you found in the previous step. By default, this will create your password store folder in your home directory (~/.password-store) although you can change this (indeed, you can have multiple password store folders)
    • The easiest way to create the .password-store folder in the same directory as the one your terminal is pointing to by running export PASSWORD_STORE_DIR=$PWD/.password-store before pass init <key>

2.3 Usage

Now that you’ve set up the GPG and the pass components of your password manager, you can interact with it using passpy:

2.3.1 CLI

From the terminal, you can use the following commands:

Command Effect
passpy insert <key> Add a new password. It will be saved under the name <key> (in other words, the ‘key’ is the name of your password, not the password itself).
passpy insert <folder>/<key> Add a new password saved in a sub-folder called <folder>. In general, all keys can be inside folders.
passpy generate <key> <n> Generate a new password of length <n> and save it under the name <key>
passpy rm <key> Delete a password
passpy show Print the folder hierarchy of the default password store (which is located at ~/.password-store/)
passpy show <path> Print the folder hierarchy of the password store located at <path>
passpy show <key> Print the password that is saved under the name <key>
passpy show -c <key> Copy password to clipboard

2.3.2 Script

Assuming you’ve created a password called “example” (passpy insert example) in the password store that is in the default location (~/.password-store) you can access it from within a Python script via the following:

import passpy

store = passpy.Store()
example = store.get_key('example')
print(example)
## password123

The store_dir keyword argument can be used to specify an exact password store:

store = passpy.Store(store_dir='.password-store')
example = store.get_key('example')
print(example)
## password123

2.4 Add version control

Turn your password store into a Git repository by cd’ing into the password store folder in the terminal (cd ~/.password-store) and running git init. From here, you can do all the more advanced Git operations like connecting to a remote and cloning it on any other computers you have.

3 secrets

secrets is a built-in module in Python 3.6 onwards. According to the documentation it “is used for generating cryptographically strong random numbers suitable for managing data such as passwords, account authentication, security tokens, and related secrets.” - https://docs.python.org/3/library/secrets.html

So secrets can be used to generate passwords and passpy can be used to store passwords.

3.1 URL-safe strings

Generate a random text string that can, for example, be used by apps to create hard-to-guess URLs for doing password resets:

import secrets

token = secrets.token_urlsafe()
print(token)
## kWA3fAhHaJBu1S0PXPrdTU5WqVkTKitaCTSe3CLfe6E

Generate a URL-safe string that is a custom number of bytes in length:

token = secrets.token_urlsafe(10)
print(token)
## XekbrcJC-n3NKA

3.2 XKCD-style passwords

Strong yet easy-to-remember passwords such as suggested by XKCD (https://xkcd.com/936/) can be generated by using a list of words. On standard Linux operating systems the dictionary file located at /usr/share/dict/words can be used. On other operating systems, you will need to use your own dictionary file or a list of words.

with open('/usr/share/dict/words') as f:
    words = [word.strip() for word in f]
    password = ' '.join(secrets.choice(words) for i in range(4))
print(password)
## Newcastle's Corina insolvents serviette's

3.3 Generic strong passwords

When you create a password for an account there will often be requirements as to how strong it must be. As an example, a website might say that your password needs to contain:

  • Alphanumerics (numbers and letters)
  • At least twelve characters
  • At least one lowercase character
  • At least one uppercase character
  • At least one special character
  • At least two digits

Here’s a snippet that will generate a password that fits those criteria for you:

import secrets
import string

alphabet = string.ascii_letters + string.digits + string.punctuation
while True:
    password = ''.join(secrets.choice(alphabet) for i in range(12))
    if (
        any(c.islower() for c in password) and
        any(c.isupper() for c in password) and
        any(c in string.punctuation for c in password) and
        sum(c.isdigit() for c in password) >= 3
    ):
        break
print(password)
## |5hb&,2+G3ij

Sometimes the requirements are even stricter than this and go on to stipulate that they must have:

  • No run of three or more consecutive numbers
  • No run of three or more consecutive letters

Here’s how to add in these rules:

import secrets
import string

alphabet = string.ascii_letters + string.digits + string.punctuation
while True:
    password = ''.join(secrets.choice(alphabet) for i in range(12))
    if (
        any(c.islower() for c in password) and
        any(c.isupper() for c in password) and
        any(c in string.punctuation for c in password) and
        sum(c.isdigit() for c in password) >= 3 and
        not any(
            all(
                [
                    password[i].isdigit(),
                    password[i + 1].isdigit(),
                    password[i + 2].isdigit()
                ]
            ) for i, c in enumerate(password[:-2])
        ) and
        not any(
            all(
                [
                    password[i] in string.ascii_letters,
                    password[i + 1] in string.ascii_letters,
                    password[i + 2] in string.ascii_letters
                ]
            ) for i, c in enumerate(password[:-2])
        )
    ):
        break
print(password)
## "_6y)]E4:6`i

3.4 Blinding codes

If you have information that you want to anonymise you can use blinding codes. For example, you might be running a scientific trial where you want to mask the participants’ identities. Or you might want to eliminate bias by hiding whether a particular participant is in the experimental or control group from the experimenter. Of course, you still want there to be somebody who knows who is who so the data can be analysed once collected, so a key should be kept which lists which participant has been given which blinding code. Once the experiment has been run, the data can then be un-blinded to allow for each experimental group to be analysed separately.

Here are some tips for creating good blinding codes:

  • Use letters only. If you include numbers you run the risk of confusing 0s for Os and 1s for ls. And, if you end up with a code that is made up entirely of numbers, it will likely be interpreted by Python/Excel/R/whatever software you are using as a number and re-formatted (eg leading zeroes might be removed or thousand-separators might be added). If you include special characters you run the risk of having codes with “+”, “-” or “=” at the beginning which programs such as Excel might interpret as mathematical statements and convert to “#NAME?”. Alternatively, a code might start with a single quotation mark which Excel might interpret as a string indicator and drop.
  • Make them case insensitive. You don’t want “AaAa” and “aAaA” to represent different things! That can easily lead to confusion. Similarly, Is and ls look the same in many fonts (that’s the capital i and the lowercase L). The easiest way to ensure your codes are case insensitive is to make them all entirely uppercase (or entirely lowercase) and then keep them that way.

Here’s some code that will produce 20 unique strings of length 4:

import secrets
import string

codes = []
while len(codes) < 20:
    code = ''.join(secrets.choice(string.ascii_letters).upper() for i in range(4))
    if code not in codes:
        codes.append(code)
print(codes)
## ['GHAG', 'CYAS', 'MUUM', 'DGBA', 'CDJQ', 'SFPT', 'WHIJ', 'EIVL', 'JKSA', 'NZXC', 'UEVK', 'LLHX', 'DCDK', 'HNIW', 'HZUH', 'VPEW', 'KAJH', 'MOWJ', 'GXAU', 'KAIT']

If you do decide to use both letters and numbers then at least exclude l and o from the selection of letters. Here’s an example that uses:

  • Alphanumerics (both numbers and letters)
  • Five characters
  • Lowercase letters
  • No “l” or “o”

…and generates 10 codes at a time:

import secrets
import string

alphabet = string.ascii_letters + string.digits
codes = []
while len(codes) < 10:
    while True:
        code = ''.join(secrets.choice(alphabet) for i in range(5))
        code = code.lower()
        if ('l' not in code) and ('o' not in code):
            break
    if code not in codes:
        codes.append(code)
print(codes)
## ['jmyrs', '5izxw', 'hexhz', 'jv3pr', 'wav62', 'dmz2i', 'yaa33', '1evaj', 'gv1j5', 'ykzq2']

4 getpass

The getpass module is also built into Python, see its documentation here. It provides two functions:

  • getuser() returns the name of the account currently logged into the computer:
from getpass import getuser

print(getuser())
## rowan
  • getpass() prompts the user to enter a password and does not echo the input the user makes (ie the password that gets typed remains hidden):
from getpass import getpass

password = getpass()
Password:

You can customise the prompt using the prompt keyword argument:

from getpass import getpass

password = getpass(prompt='Enter your password: ')
# or
password = getpass('Enter your password: ')
Enter your password: 

Note that this password can still be printed in Python:

print(password)
## password123

Here’s how you might create a login function for a programme:

def login():
    """Username and password input."""
    global username
    global password

    username = getuser()
    # or
    username = input('Username: ')

    password = getpass('Password: ')


login()

⇦ Back