DEV Community

Cover image for Python Secure Password Management: Hashing and Encryption #οΈβƒ£πŸ”βœ¨
Deon Pillsbury
Deon Pillsbury

Posted on

Python Secure Password Management: Hashing and Encryption #οΈβƒ£πŸ”βœ¨

When building an application that validates user passwords or needs to store tokens for future use it is critical to not store these values anywhere in clear text. If there is a security breach you want to be confident that your user’s data is protected. Hashing and Encryption are a couple of techniques which can be used to achieve this and we will take a look at how to implement these with Python.


If your application needs to allow users to register an account and create a password then you need to store the values they singed up with in order to authenticate them later. Rather than storing the passwords in clear text, this is where a hashing algorithm should be used. Hashing algorithms are one-way functions which produce the same result for the input data but given the output data are nearly impossible to reverse. There are many types of hash algorithms but SHA-256 is a strong and NIST Approved modern algorithm that fits the need of most applications in terms of strength and performance.

Create a simple Python script file to take an input and generate the SHA-256 hash with the hashlib standard library.


import hashlib

password = input("Password: ")
password_hash = hashlib.sha256(password.encode("utf-8")).hexdigest()
print(f"Password Hash: {password_hash}")
Enter fullscreen mode Exit fullscreen mode

Run the script and give it a few password inputs.

$ python3
Password: test123
Password Hash: ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae

$ python3
Password: test123
Password Hash: ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae

$ python3
Password: test1234
Password Hash: 937e8d5fbb48bd4949536cd65b8d35c426b80d2f830c5c308e2cdec422ae2244
Enter fullscreen mode Exit fullscreen mode

We can see that the same input produces the same hash result but any change such as an additional character completely changes it. The resulting hash value is what you should store in your database to later validate the user’s password.


Hashing is a great option when you do not need use the password value. For use cases where you need to use the actual password value such as storing a long term access token to authenticate on the user’s behalf to an external application then encryption is the best option. Encryption allows you to store the values securely and decrypt them in memory when you need to use them. Python has the cryptography library which includes Fernet Symmetric Encryption to achieve this. Symmetric encryption means we will have a secret key which we can store in our environment variables and use to decrypt stored values.

Install the cryptography and dotenv library.

$ poetry add cryptography python-dotenv
Using version ^41.0.5 for cryptography
Using version ^1.0.0 for python-dotenv

Updating dependencies
Resolving dependencies... (0.1s)

Package operations: 4 installs, 0 updates, 0 removals

  β€’ Installing pycparser (2.21)
  β€’ Installing cffi (1.16.0)
  β€’ Installing cryptography (41.0.5)
  β€’ Installing python-dotenv (1.0.0)
Enter fullscreen mode Exit fullscreen mode

Run an inline python command to generate the Fernet secret key.

$ python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key())"
Enter fullscreen mode Exit fullscreen mode

Add this value to a .env file.

πŸ“Β .env

Enter fullscreen mode Exit fullscreen mode

Create a new python script which will load our secret key from the environment variables, instantiate the Fernet client with the key, and allow a new password to be encrypted and stored in a simple text file or print out the decrypted value of an existing stored password.


import os
import sys

from cryptography.fernet import Fernet
from dotenv import load_dotenv


SECRET_KEY = os.getenv("SECRET_KEY")

if len(sys.argv) > 1 and sys.argv[1] == "decrypt":
    with open("pw.txt") as f:
        stored_password =

    stored_dec_password = FERNET.decrypt(stored_password).decode()
    print(f"Decrypted Password: {stored_dec_password}")
    new_password = input("New Password: ")
    new_enc_password = FERNET.encrypt(new_password.encode()).decode()

    with open("pw.txt", "w") as f:

    print(f"Encrypted Password Stored: {new_enc_password}")
Enter fullscreen mode Exit fullscreen mode

Test it out to validate it is working as expected.

$ python3
New Password: Test123!!
Encrypted Password Stored: gAAAAABlR7V0TLTZMT_ZHEoPtqbW3B9LYgohYdUNG6Lukx9M2NSLgrFN6MUZKCNPP3Hq_KuuEPpJPPqqIktUkZTBh3qenKnQAA==

$ python3 decrypt
Decrypted Password: Test123!!
Enter fullscreen mode Exit fullscreen mode

Awesome! πŸŽ‰Β This shows the core concepts of encrypting/decrypting values and in a production environment rather than storing them in a simple text file you would just store and retrieve the values from a database.

I hope you have found this article helpful for building your next amazing (and secure) application! 😊

Top comments (3)

dotenv profile image

Nice use of sha256. We also use it in our .env.vault mechanism - successor to .env files.

Have you seen python-dotenv-vault - different use case than what you are doing here, but as a fellow cryptography fan, you might find it interesting intellectually:

dpills profile image
Deon Pillsbury

@dotenv Very nice, I had not seen the dotenv vault functionality, it is a separate use case but it does address a challenge we have with keeping .env files in-sync across our smaller teams. I love the idea of committing encrypted environment variables to the repo with a .env.vault file and only needing to manage the DOTENV_KEY. πŸ˜ƒ Thanks for sharing this and amazing work on the Dotenv ecosystem! ❀️

mohammadparsajavidi profile image