Since I only just learnt about this, I wanted some advice on better practices I can use when it comes to cryptography. Still learning python and hoping I can get this 'hashing' and 'salting' thing in the bag. I plan on using this for a personal password manager I am making.
So basically what I did was, hash a password with a salt included using the pypasswords
library. Using the key made from this, I derive another key. I then to make the hacker go through more steps, encrypt the derived key and salt using the cryptography.fernet
library. I then save the encrypted derived key, encrypted salt, and the key for decryption in a .json file. Reason being I plan on saving other things to said file to.
from pypasswords import *
from cryptography.fernet import Fernet
import json
def base_generator(password: str):
"""
Generates key and salt with password and then rehashes to make a derived key.
This derived key is then saved in a dictionary with the salt for further security
This one must get the password, if they want to get in.
dictionary = {'main': {'derived_key': derived_key, 'salt': salt}}
"""
key, salt = hash_it(password, salting=True)
derived_key = hash_it(key)
json_save = {
'main': {
'derived_key': derived_key,
'salt': salt
}
}
return json_save
def re_hash(json_save: dict):
"""
Re-encrypts the values with the key for further security if any at all as the key is still saved
in the dictionary.
dictionary = {'main': {'derived_key': derived_key, 'salt': salt, 'key': key}}
"""
key = Fernet.generate_key()
json_save['main']['derived_key'] = Fernet(key).encrypt(json_save['main']['derived_key'].encode()).decode()
json_save['main']['salt'] = Fernet(key).encrypt(json_save['main']['salt'].encode()).decode()
json_save['main']['key'] = key.decode()
return json_save
def password_hasher(password: str):
"""
Combination of 'base_generator' and 're_hash'
"""
json_save = base_generator(password)
json_save = re_hash(json_save)
return json_save
def password_checker(json_save, password):
"""
Basically undoes all the hashing and checks if password and salt equal to derived key
"""
# Converting rehashed values back to originals
key = json_save['main']['key'].encode()
salt = Fernet(key).decrypt(json_save['main']['salt'].encode()).decode()
derived_key = Fernet(key).decrypt(json_save['main']['derived_key'].encode()).decode()
# Preparing for password confirmation
password_key, salt = hash_it(password, salting=True, static_salt=salt)
# Checking for password_confirmation with derived_key
return match_it(password_key, derived_key)
def json_file(command, json_data):
if command in ['dump', 'load']:
if command == 'dump':
with open('data.json', 'w') as data_file:
json.dump(json_data, data_file, indent=4)
if command == 'load':
with open('data.json', 'r') as data_file:
json_save = json.load(data_file)
return json_save
else:
print('Invalid Command. Try "load", "dump"')
def main():
password1 = input('Password: ')
password2 = input('What was password? ')
json_save = password_hasher(password1)
json_file('dump', json_save)
json_save = json_file('load', json_save)
print(password_checker(json_save, password2))
print(password1, password2, json_save)
if __name__ == '__main__':
main()
Please bear in mind that I'm still just beginning and don't fully understand cryptography. With that in mind, I look forward to the advice and criticism.
1 Answer 1
I wanted some advice on better practices I can use when it comes to cryptography
Cryptographic advice items number 0 through 999 are: don't do this yourself. Don't write the crypto yourself, and unless you have a very strange, specific and inflexible requirement, don't write the utilities and wrappers yourself, either. It's especially true for beginners, but it's equally true for the general population of programmers who haven't been in the security & crypto trenches for years. There are entire categories of vulnerability that most people don't even think about, like timing attacks. It takes years of things going wrong and being fixed ("high exposure") for a package or program to be considered secure. As a starting point, it helps to read the source code of established programs.
Some of the absolute minimum security best-practices are not present here. You display the user's password in plain-text; you should use getpass
instead. After that, there is a long litany of other things to be considered: where specifically is your data.json
stored? With what file permissions and what user ownership? Do you need to add padding to prevent size attacks? Are there minimum length and entropy requirements (not character class requirements) for the password? And so on.
I plan on saving other things to said file to.
is almost certainly a bad idea; but if you persist with it, your current code will not work because you unconditionally steamroll anything that had been in the file beforehand.
As to the code itself:
Avoid importing *
. You only need two symbols from pypasswords
.
Don't pass around json_save
, as that's a serialization detail. Only pass around the variables you care about.
Writing json_file
to accept one of two command strings is less helpful than just writing two separate functions.
A first pass is:
from getpass import getpass
from pathlib import Path
from typing import Any
from pypasswords import hash_it, match_it
from cryptography.fernet import Fernet
import json
DATA_PATH = Path('data.json')
def base_generator(password: str) -> tuple[
str, # derived key
str, # salt
]:
"""
Generates key and salt with password and then rehashes to make a derived key.
This derived key is then saved with the salt for further security
"""
key, salt = hash_it(password, salting=True)
derived_key = hash_it(key)
return derived_key, salt
def re_hash(derived_key: str, salt: str) -> tuple[
str, # derived key
str, # salt
str, # key
]:
"""
Re-encrypts the values with the key for further security if any at all as the key is still saved
in the dictionary.
"""
key = Fernet.generate_key()
derived_key = Fernet(key).encrypt(derived_key.encode()).decode()
salt = Fernet(key).encrypt(salt.encode()).decode()
key = key.decode()
return derived_key, salt, key
def password_hasher(password: str) -> tuple[
str, # derived key
str, # salt
str, # key
]:
"""
Combination of 'base_generator' and 're_hash'
"""
derived_key, salt = base_generator(password)
derived_key, salt, key = re_hash(derived_key, salt)
return derived_key, salt, key
def password_checker(derived_key: str, salt: str, key: str, password: str) -> bool:
"""
Basically undoes all the hashing and checks if password and salt equal to derived key
"""
# Converting rehashed values back to originals
key = key.encode()
salt = Fernet(key).decrypt(salt.encode()).decode()
derived_key = Fernet(key).decrypt(derived_key.encode()).decode()
# Preparing for password confirmation
password_key, _ = hash_it(password, salting=True, static_salt=salt)
# Checking for password_confirmation with derived_key
return match_it(password_key, derived_key)
def dump_json(derived_key: str, salt: str, key: str) -> None:
if DATA_PATH.exists():
with DATA_PATH.open() as f:
data: dict[str, Any] = json.load(f)
else:
data = {}
main = data.setdefault('main', {})
main.update(derived_key=derived_key, salt=salt, key=key)
with DATA_PATH.open('w') as f:
json.dump(data, f)
def load_json() -> tuple[
str, # derived key
str, # salt
str, # key
]:
with DATA_PATH.open() as f:
main = json.load(f)['main']
return main['derived_key'], main['salt'], main['key']
def main() -> None:
print('! Do not use this for real credentials. You have been warned. !')
password_1 = getpass('Password: ')
derived_key, salt, key = password_hasher(password_1)
dump_json(derived_key, salt, key)
derived_key, salt, key = load_json()
password_2 = getpass('What was password? ')
if password_checker(derived_key, salt, key, password_2):
print('Password check succeeded')
else:
print('Password check failed')
if __name__ == '__main__':
main()
-
2\$\begingroup\$ Thank you so much. I didn't know I was wrong by so much. I will NOT be using my code to store my passwords lol. I think I will be staying away from this for a while. \$\endgroup\$Beginner– Beginner2023年07月11日 01:08:27 +00:00Commented Jul 11, 2023 at 1:08
-
\$\begingroup\$ Well. It's good to have interest, and I hope that I haven't dissuaded you from learning security topics. It's important and interesting in part because it's so difficult. \$\endgroup\$Reinderien– Reinderien2023年07月11日 02:38:28 +00:00Commented Jul 11, 2023 at 2:38
-
1\$\begingroup\$ No not at all, I will try to pick this up again, further into my coding career \$\endgroup\$Beginner– Beginner2023年07月12日 05:10:38 +00:00Commented Jul 12, 2023 at 5:10
Explore related questions
See similar questions with these tags.