0

I am making a password manager and generator. The user creates the login information, and then I hash their password. After that, I generate the password and encrypt the password with a key stored unprotected. My Question is how do I protect the key?

I need an easy way to do it since I am just a beginner and working on this password manager as a practice. However, I can't find what I need, I also intend to put this into a GUI which I'll work on when I know how to store the keys safely.

Here is the python file:

import random,string,os,sys
from cryptography.fernet import Fernet
import bcrypt

if os.path.isfile('salt.txt'):
    #Gets the salt
    with open('salt.txt','rb') as saltfile:
        salt = saltfile.read()

else:
    with open('salt.txt','wb')as saltfile:
        salt = bcrypt.gensalt()
        saltfile.write(salt)

#Hashes the item
def hashPass(item):
    global passwordOut
    hashed = bcrypt.hashpw(item,salt)
    passwordOut = hashed

# Random Password Generator 
def setPassword(length=30,char=string.ascii_letters+string.digits+string.punctuation):
    global generatedPassword
    generatedPassword= ''.join(random.choice(char) for x in range(length))
    return generatedPassword

if os.path.isfile('mykey.key') == True:
    #Opens the key
    with open('mykey.key', 'rb') as mykey:
        print('True')
        key = mykey.read()
        f = Fernet(key)

elif os.path.isfile('mykey.key')== False:
    print('False')
    # Generates a kay
    key = Fernet.generate_key()
    # Writes a new key
    with open('mykey.key', 'wb') as mykey:
        mykey.write(key)
        f = Fernet(key)

#Sets the key

#Stating initalization
print("Hello and welcome to your password manager!")

while True:
    #If there is a user file
    if os.path.isfile('user.txt'):
        #Read the user file
        with open('user.txt','rb') as user_file:
            file = user_file.read()
            print("File ", file)

        #Gets the inputs
        getUser = input("Enter your username ").encode('utf-8')
        getPass = input('Enter your password: ').encode('utf-8')

        #Hashes the inputs through the hashing funcion
        hashPass(item=getUser)
        usr = passwordOut
        hashPass(item=getPass)
        passw = passwordOut

        #If the users hashed input is the same in the users file it carries on with the procedure
        if usr in file and passw in file:
            while True:
                print("""Pick from the list of what you want to do:
                1. Generate a new password
                2. See passwords
                3. Quit""")

                usrinput = int(input('Enter a number from the menu: '))

                if usrinput == 1:
                    print("\nGenerating password...")
                    setPassword()
                    usrinput = input("What is the password for: ")
                    splitter = ': '
                    if os.path.isfile('passwordenc.txt'):
                        with open('passwordenc.txt','ab')as password_file:
                            var = usrinput + splitter + generatedPassword 
                            encrypted = f.encrypt(bytes(var.encode('utf-8')))
                            password_file.write(encrypted)
                            password_file.write(b"--END OF PASSWORD--")

                            print("Your new password for: "+usrinput)
                            print("And the password is: "+generatedPassword)
                            

                    else:
                        with open('passwordenc.txt','wb')as password_file:
                            var = usrinput + splitter + generatedPassword
                            encrypted = f.encrypt(bytes(var.encode('utf-8')))                    
                            password_file.write(encrypted)
                            password_file.write(b"--END OF PASSWORD--")

                            print("Your new password for: " + usrinput)
                            print("And the password is: " + generatedPassword)


                if usrinput == 2:
                    if os.path.isfile('passwordenc.txt'):                    
                        with open('passwordenc.txt','r') as password_file:
                            whole_file = password_file.read()
                            password_list = whole_file.split("--END OF PASSWORD--")                        
                            for password in password_list:
                                if password:
                                    decrypt = f.decrypt(bytes(password, encoding="utf-8"))
                                    print("Decrypted pass: ", decrypt)                

                    else:
                        print('File not found! Need to create a new file.')

                if usrinput == 3:
                    quit()


        #If not the same it loops back around
        else:
            print("\nUser not found!\n")


    #If there is no file:
    else:
        #Gets a user input
        username = input('Enter a username: ').encode('utf-8')
        password = input('Enter a password, cannot be changed! ').encode('utf-8')

        #Hashes the user input
        hashPass(item=username)
        usr = passwordOut
        hashPass(item=password)
        passw = passwordOut

        #Writes the user input
        with open('user.txt','wb') as user:
            user.write(usr)
            user.write(passw)
        print('\nUser has been created!\n')
1

You don't usually use the password to encrypt, you use it to derive a key. This way you can generate a key with high entropy even if the underlying password is low entropy.

It allows you to create a key with the exact size as needed, no padding or truncating needed. It's better to use PBKDF2 and generate a 64-byte password than asking the user to type a password with exact 64 chars.

And you don't use the derived key to encrypt, you create a random symmetric key, encrypt it with the derived key. This way you don't have to re-encrypt the entire database again when the user changes the password. You just derive the key again with the new password, and encrypt the symmetric key with the new generated key.

Confusing? I think so. So, bullets:

  • User types his password (PASS): SuperSecurePassword9000

  • You use PBKDF2 (or BCrypt) and generate a key (DKEY): 5c8fe66a...b8bfc6f8

  • You generate a random key (RKEY): f01903e3...d2e67a0f

  • You encrypt RKEY with DKEY and store it encrypted (EKEY): aabbccdd...11223344

This encrypted key (EKEY) is stored. It cannot be derived back to the RKEY, and there are no traces of DKEY nor PASS anywhere. You only store EKEY.

Now the user unlocks the database. He types PASS, you derive DKEY from it, and decrypt EKEY using DKEY, getting RKEY. You can now use symmetric encryption to decrypt the database.

If the user wants to change its password, you need to re-encrypt only EKEY:

  • User types the current password (PASS1)

  • You derive DKEY as always, and decrypt EKEY, giving back RKEY on plain text.

  • You ask for the new password (PASS2)

  • You derive the new DKEY2 from PASS2

  • You encrypt RKEY with DKEY2, giving you EKEY2

  • You store EKEY2 and it's done

Deriving the key from the password gives you 2 advantages: you can control the security of the key by changing the number of iterations on the key derivation function to be as much as you want, and you can change the password of a very large database by changing only the encryption key.

0

Look what the other password managers do. They encrypt the whole file where you store passwords with a single password. When user opens this file in your password manager, they ask user for a password. User provides password. They decrypt the file using this password. Now the content is decrypted and password manager can do whatever they want: Add new password entries, modify entries, delete entries. When user saves the password file, password manager encrypts it using the password.

You say you have just started with implementation. After such first version works, think of improvement. If you just encrypt the password file with some symmetric algorithm, it does not mean that it is resistant to brute-forcing. To make usage of rainbow tables impossible for an attacker, use a random salt. To make brute-forcing expansive for an attacker, don't use password directly, but derive it from the user password, using some algorithms that requires relatively much CPU and RAM. For instance, use Argon2. It provides all these features: it requires a salt, it has tunable parameters for CPU and RAM. Set these parameters to such values, that derivation takes relatively long time, but is still acceptable, e.g. so that it takes 1-2 seconds. This will make your encrypted file more resistant to brute-forcing.

4
  • I shall try this thank you, so what you're saying is the password that the user creates is the key not a separate key? Mar 28 '21 at 2:12
  • Yes. A single key is sufficient to encrypt the whole file (database) with all passwords.
    – mentallurg
    Mar 28 '21 at 3:12
  • @Mr_Westhead: See update.
    – mentallurg
    Mar 28 '21 at 3:42
  • 1
    @Mr_Westhead To be clear, you should never try to encrypt anything using a password directly (usually this won't even be possible, without padding or truncating the password). Instead, you encrypt using a key derived from a password, using a strong password hashing / key-derivation algorithm.
    – CBHacking
    Sep 8 '21 at 21:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.