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.
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
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:
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
python3.11 -m pip install passpy
(replace python3.11
with the version you have installed and are using)gpg --version
. If you don’t have it, install it from here: https://gnupg.org/download/index.htmlsudo apt-get install gnupg2 -y
sudo apt install pass
gpg --gen-key
. Follow the prompts to set up a user ID.
gpg --list-keys
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)
.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>
Now that you’ve set up the GPG and the pass components of your password manager, you can interact with it using passpy:
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 |
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
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.
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.
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
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
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:
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:
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
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:
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:
…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']
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()