I'm currently in the process of building a password manager as a project to get a better understanding of PostgreSQL, alongside learning how to handle data between Python and a database.
Here's a little about the logic of my password manager.
- If you're a new user, the program will greet you with a prompt awaiting a master password to be set, to access your password vault. Once set, the master password is encrypted using the hashlib Python module as you can see below in the code snippet. A key and a salt is used to encrypt the password, and both are stored as binary values in the database. These values are sent to the databse using the psycopg2 Python module.
- If you're an existing user, the program will greet you with a promt awaiting the already-set master password so you can access with your password vault. The program will fetch the key and salt from the database, use the salt to create a new key and see if the key and new key values match. If they do, access granted, otherwise access denied to the vault.
However, I have encountered the following problem: the binary data (key and salt) that is stored in the database is not the same as the binary data (key and salt) in Python. Below you will find my code:
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
import sys
import os
import hashlib
# This connects to PSQL
connection = psycopg2.connect(user="postgres",
password="abc",
host="localhost",
port="5432",
dbname="passwordmanager")
connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
cur = connection.cursor()
salt = b""
key = b""
new_key = b""
stored_salt = b""
def welcome_n(): # This welcomes a new user without a master password set
user_input = input("Welcome! You need to set a master password"
" before you can start storing passwords - continue?\n y/n: ")
valid = False
while not valid:
if user_input == "y":
password_input = input("Please choose your password:\n")
global salt
global key
salt = os.urandom(32)
key = hashlib.pbkdf2_hmac(
"sha256",
password_input.encode("utf-8"),
salt,
100000
)
# PSQL commands to store salt and key
psycopg2.Binary(key)
psycopg2.Binary(salt)
cur.execute("INSERT INTO master (key, salt) VALUES (%s, %s)",
(key, salt))
valid = True
print(key) # b'\xfe\xfdO{\xd6?\xa1\x8d(\xbb\xb3r\x8a\xbc\xd6&t\x11[\x06\x110`\xb3\xfa\x91\xee\xc7x\x14\xddR'
key = b""
elif user_input == "n":
print("Quiting program.")
sys.exit()
else:
user_input = input("Invalid input. Please choose either 'y' or 'n'.\n y/n: ")
def welcome_o(): # This welcomes an existing user with a master password set already
password_to_check = input("Welcome! Enter your password to access the password safe:\n")
# Encrypting the password given
global salt
global key
global new_key
global stored_salt
cur.execute("SELECT key FROM master;")
key = cur.fetchone() # The key variable now has the fetched binary key from database
cur.execute("SELECT salt FROM master;")
stored_salt = cur.fetchone() # The stored_salt variable now has the fetched binary salt from database
new_key = hashlib.pbkdf2_hmac("sha256", password_to_check.encode("utf-8"), b"stored_salt", 10000)
# A new key has been generated using the fetched salt
if new_key == key:
print("Password is correct")
else:
print("Incorrect Password")
# Checking password similarities
print(key) # (<memory at 0x03B96388>,)
print(new_key) # b"k\r\xd8\xcfU\x05x\xfc'9\xaaC\x1fp~*9av6k^\xeeec\xef\xc5\xe3\xf1^\x883"
welcome_n()
welcome_o()
As I aforementioned, the binary data that is stored in the database is not the same as the binary data in Python. We can see that in the key;
Python binary = b'\xfe\xfdO{\xd6?\xa1\x8d(\xbb\xb3r\x8a\xbc\xd6&t\x11[\x06\x110`\xb3\xfa\x91\xee\xc7x\x14\xddR'
vs
PSQL binary = \xfefd4f7bd63fa18d28bbb3728abcd62674115b06113060b3fa91eec77814dd52
Another problem I'm having is that, when I try printing on screen the key, I get (<memory at 0x03B96388>,) shown above, while the new key displays as b"k\r\xd8\xcfU\x05x\xfc'9\xaaC\x1fp~*9av6k^\xeeec\xef\xc5\xe3\xf1^\x883" . So far, my guesses are that I'm not inputting the data correctly into the database and not extracting it correctly, meaning that the binary data is deformed. I would also like to mention that the table holding the master password binary data has the key column and salt column data types set to BYTEA. You can find the table schema below:
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
-------+--------+-----------+----------+------------------------------------+---------+--------------+-------------
id | bigint | | not null | nextval('master_id_seq'::regclass) | plain | |
key | bytea | | not null | | extended| |
salt | bytea | | not null | | extended| |
Any feedback in any form is much appreciated, I'm new to programming and I'm looking to improve and learn new things!
pbkdf2_hmacwith different iterations. They should share a single constant.bytea. If you could edit that into your question please. Avoid screenshots, paste the text in.