Python for Cryptography: Building a Password Manager

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setup
  4. Creating the User Interface
  5. Generating a Secure Password
  6. Encrypting and Decrypting Passwords
  7. Storing Passwords Safely
  8. Summary

Introduction

In this tutorial, we will learn how to build a password manager using Python. We will explore cryptographic techniques and libraries available in Python to generate secure passwords, encrypt and decrypt sensitive information, and store passwords safely.

By the end of this tutorial, you will have a functional password manager that can generate strong passwords, encrypt and decrypt password data, and securely store the passwords.

Prerequisites

To follow along with this tutorial, you should have a basic understanding of Python programming language, including variables, functions, loops, and basic file handling. It is also helpful to have some knowledge of cryptography concepts, although it is not mandatory.

Setup

Before we begin, we need to make sure that Python is installed on our system. You can download and install the latest version of Python from the official Python website.

Additionally, we will be using the cryptography library, which can be installed via pip. Open your terminal or command prompt and run the following command: python pip install cryptography With Python and the required library installed, we are ready to start building our password manager.

Creating the User Interface

The first step in building our password manager is to create a user-friendly command-line interface. We will use the argparse module to handle command-line arguments and options.

Create a new file called password_manager.py and add the following code: ```python import argparse

def create_password():
    pass

def retrieve_password():
    pass

def main():
    parser = argparse.ArgumentParser(description='Password Manager')
    subparsers = parser.add_subparsers(title='subcommands', dest='command')

    # Create sub-command
    create_parser = subparsers.add_parser('create', help='Create a new password')
    create_parser.set_defaults(func=create_password)

    # Retrieve sub-command
    retrieve_parser = subparsers.add_parser('retrieve', help='Retrieve an existing password')
    retrieve_parser.set_defaults(func=retrieve_password)

    args = parser.parse_args()
    args.func()

if __name__ == '__main__':
    main()
``` In the above code, we import the `argparse` module and define three functions: `create_password()`, `retrieve_password()`, and `main()`. The `create_password()` and `retrieve_password()` functions will hold the logic for creating and retrieving passwords, respectively.

We then create an instance of the ArgumentParser class and add two sub-commands: create and retrieve. Each sub-command has its own help message and set of arguments. Finally, we parse the command-line arguments and execute the corresponding function based on the sub-command.

Save the file and open your terminal or command prompt. Navigate to the directory where the password_manager.py file is saved and run the following command: python python password_manager.py --help You should see the help message along with the available sub-commands. Try running the following commands: python python password_manager.py create python password_manager.py retrieve You will notice that nothing happens when running the commands at the moment. We will now implement the functionality for creating and retrieving passwords in the next sections.

Generating a Secure Password

To create a strong and secure password, we will use the secrets module, which is part of the Python standard library. The secrets module provides functions for generating cryptographically strong random numbers and strings.

Let’s update the create_password() function to generate a random password: ```python import string import secrets

def create_password():
    length = 12
    characters = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(secrets.choice(characters) for _ in range(length))
    print(f"Generated Password: {password}")
``` In the above code, we define the `length` of the password (12 characters in this case) and specify the `characters` to choose from. We concatenate the lowercase letters, uppercase letters, digits, and punctuation marks to create a comprehensive character set.

Using a loop, we then select length number of random characters from the provided character set using secrets.choice(). The selected characters are then joined together to form the password.

Save the file and try running the following command: python python password_manager.py create You should see a randomly generated password each time you run the command.

Encrypting and Decrypting Passwords

Next, we will implement the encryption and decryption functionalities for securing the passwords. We will use the Fernet symmetric key encryption algorithm provided by the cryptography library.

Update the create_password() and retrieve_password() functions as follows: ```python import string import secrets from cryptography.fernet import Fernet

def load_key():
    key = b'abcdefghijklmnop'  # Replace with your own key
    return key

def encrypt_password(password, key):
    cipher_suite = Fernet(key)
    encrypted_password = cipher_suite.encrypt(password.encode())
    return encrypted_password

def decrypt_password(encrypted_password, key):
    cipher_suite = Fernet(key)
    decrypted_password = cipher_suite.decrypt(encrypted_password)
    return decrypted_password.decode()

def create_password():
    length = 12
    characters = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(secrets.choice(characters) for _ in range(length))
    key = load_key()
    encrypted_password = encrypt_password(password, key)
    print(f"Generated Password: {password}")
    print(f"Encrypted Password: {encrypted_password}")

def retrieve_password():
    encrypted_password = b'...'  # Replace with an actual encrypted password
    key = load_key()
    decrypted_password = decrypt_password(encrypted_password, key)
    print(f"Decrypted Password: {decrypted_password}")
``` In the above code, we first define a hardcoded `key` in the `load_key()` function. In a real-world scenario, you should generate a randomized key and securely store it. We use the same key for both encryption and decryption.

The encrypt_password() function takes a plain-text password and the encryption key as input. It creates a Fernet cipher object with the key and encrypts the password using the encrypt() method. The encrypted password is then returned.

Similarly, the decrypt_password() function takes an encrypted password and the decryption key as input. It creates a Fernet cipher object with the key and decrypts the password using the decrypt() method. The decrypted password is returned as a byte string, which we decode to convert it back to plain text.

In the create_password() function, we generate a random password as before. We then load the encryption key, encrypt the password using encrypt_password(), and print the original and encrypted passwords.

In the retrieve_password() function, we define a placeholder encrypted_password. You should replace it with an actual encrypted password in your implementation. We load the encryption key, decrypt the password using decrypt_password(), and print the decrypted password.

Save the file and try running the following commands: python python password_manager.py create python password_manager.py retrieve You should now see your password being encrypted and decrypted properly.

Storing Passwords Safely

To securely store the passwords, we will use a file-based approach where each password entry is stored in a separate file. The file name will be a unique identifier for each password.

Update the create_password() and retrieve_password() functions as follows: ```python import string import secrets import os from cryptography.fernet import Fernet

PASSWORDS_DIR = 'passwords'

def load_key():
    key = b'abcdefghijklmnop'  # Replace with your own key
    return key

def encrypt_password(password, key):
    cipher_suite = Fernet(key)
    encrypted_password = cipher_suite.encrypt(password.encode())
    return encrypted_password

def decrypt_password(encrypted_password, key):
    cipher_suite = Fernet(key)
    decrypted_password = cipher_suite.decrypt(encrypted_password)
    return decrypted_password.decode()

def create_password():
    length = 12
    characters = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(secrets.choice(characters) for _ in range(length))
    key = load_key()
    encrypted_password = encrypt_password(password, key)
    print(f"Generated Password: {password}")
    print(f"Encrypted Password: {encrypted_password}")

    password_id = secrets.token_hex(8)
    password_file = os.path.join(PASSWORDS_DIR, f"{password_id}.txt")

    os.makedirs(PASSWORDS_DIR, exist_ok=True)
    with open(password_file, 'wb') as file:
        file.write(encrypted_password)

    print(f"Password saved in {password_file}")

def retrieve_password():
    password_id = '...'  # Replace with an actual password ID
    password_file = os.path.join(PASSWORDS_DIR, f"{password_id}.txt")

    key = load_key()

    with open(password_file, 'rb') as file:
        encrypted_password = file.read()

    decrypted_password = decrypt_password(encrypted_password, key)
    print(f"Decrypted Password: {decrypted_password}")
``` In the updated code, we define a constant variable `PASSWORDS_DIR` which specifies the directory where the password files will be stored.

In the create_password() function, after encrypting the password, we generate a unique password_id using the secrets.token_hex() function. We then create a password file path by joining the PASSWORDS_DIR and the password_id with the .txt extension.

Next, we create the PASSWORDS_DIR if it doesn’t already exist using the os.makedirs() function. Finally, we open the password file in binary write mode and write the encrypted password to the file.

In the retrieve_password() function, we specify a placeholder password_id which should be replaced with the actual password ID you want to retrieve. We then open the password file in binary read mode and read the encrypted password. The password is then decrypted and printed on the console.

Save the file and try running the following commands: python python password_manager.py create python password_manager.py retrieve You should now see the password being saved in a file and retrieved successfully.

Summary

In this tutorial, we learned how to build a password manager using Python. We explored the cryptographic techniques provided by the cryptography library to generate secure passwords, encrypt and decrypt sensitive information, and store passwords safely.

We started by creating a user-friendly command-line interface using the argparse module. We then implemented the functionality to generate a secure password using the secrets module.

Next, we added encryption and decryption functionalities using the Fernet symmetric key encryption algorithm from the cryptography library.

Finally, to store the passwords safely, we used a file-based approach where each password entry was stored in a separate file with a unique identifier.

By following this tutorial, you have built a basic password manager that can generate strong passwords, encrypt and decrypt password data, and securely store the passwords. You can further enhance this password manager by adding features like password hashing, user authentication, and a graphical user interface.

Remember to use this password manager responsibly and keep your encryption key secure to ensure the protection of your passwords.