Python's safest method to store and retrieve passwords from a database
Asked Answered
I

6

35

Looking to store usernames and passwords in a database, and am wondering what the safest way to do so is. I know I have to use a salt somewhere, but am not sure how to generate it securely or how to apply it to encrypt the password. Some sample Python code would be greatly appreciated. Thanks.

Interosculate answered 3/4, 2010 at 17:44 Comment(1)
Lots of info here: #1183661Noblenobleman
R
42

Store the password+salt as a hash and the salt. Take a look at how Django does it: basic docs and source. In the db they store <type of hash>$<salt>$<hash> in a single char field. You can also store the three parts in separate fields.

The function to set the password:

def set_password(self, raw_password):
    import random
    algo = 'sha1'
    salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
    hsh = get_hexdigest(algo, salt, raw_password)
    self.password = '%s$%s$%s' % (algo, salt, hsh)

The get_hexdigest is just a thin wrapper around some hashing algorithms. You can use hashlib for that. Something like hashlib.sha1('%s%s' % (salt, hash)).hexdigest()

And the function to check the password:

def check_password(raw_password, enc_password):
    """
    Returns a boolean of whether the raw_password was correct. Handles
    encryption formats behind the scenes.
    """
    algo, salt, hsh = enc_password.split('$')
    return hsh == get_hexdigest(algo, salt, raw_password)
Rani answered 3/4, 2010 at 17:50 Comment(3)
Could be better - you don't use a constant time string compare (not so important here, but if the user does the hashing, for example secure cookies, then you must use constant time string compare), and sha1 can be brute forced for predictable (i.e. most people's) passwords.Enumerate
These functions are class functions, but the OP did not ask for class functions.Mayolamayon
Note that the code in this answer uses random.random as source for the salts which is considered insecure for security related purposes according to the documentation docs.python.org/3/library/random.html . Additionally it uses a single round of sha1 which is insecure for password hashing, and according to the Django documentation Django doesn't do that anymore by default. As mentioned in my answer it is better to use the PBKDF2 functionality from docs.python.org/3/library/hashlib.html#key-derivation . PBKDF2 is also the default algorithm used by Django at the moment.Outport
O
16

I think it is best to use a function dedicated to hashing passwords for this. I explain some reasons for this here: https://mcmap.net/q/167044/-salt-and-hash-a-password-in-python Nowadays the standard library has a dedicated section in the documentation for hashing password. It even mentions that you should get your salt from a cryptographically secure random source like os.urandom().

Outport answered 8/6, 2012 at 12:22 Comment(0)
I
15

I answered this here: https://mcmap.net/q/167044/-salt-and-hash-a-password-in-python, and so did @Koffie.

I don't know how to emphasize enough that the accepted answer is NOT secure. It is better than plain text, and better than an unsalted hash, but it is still extremely vulnerable to dictionary and even brute-force attacks. Instead, please use a SLOW KDF like bcrypt (or at least PBKDF2 with 10,000 iterations)

Ionia answered 28/8, 2013 at 17:42 Comment(3)
Are you suggesting that storing correctly salted, hashed passwords is an extremely vulnerable practice? You can't use a dictionary attack on salted hashes.Eddie
No. I am suggesting that a single iteration of SHA-1, as the code above suggests, is not "correctly salted and hashed". You can use a dictionary attack on salted hashes, and the faster the hash, the faster each pw can be tried. Would you rather it took one day or 64,000 days for the attack to succeed? SHA-x hashing is very fast on custom hardware (ASICs). owasp.org/index.php/… #13546177Ionia
@rz says "take a look at how Django does it" and then shows code that is completely different. Django does do it correctly: github.com/django/django/blob/master/django/contrib/auth/…Ionia
F
3

If you have enough control over both endpoints of the application, the absolute best way is using PAK-Z+.

(Edited: the original version recommended SRP but PAK-Z+ has a proof of security.)

Fallingout answered 7/4, 2010 at 10:46 Comment(0)
W
3

For flask application or any python application you can use the werkzeug WSGI web application library, which provides you the option to generate and decode the password with salt and different types of algorithms in a format like this : method$salt$hash

The default salt length is 16 and algo used is sha256

Eg.

from werkzeug.security import generate_password_hash, check_password_hash

raw_pwd = 'mySecurePassword'

# genreates the encrypted password
hashed_pwd = generate_password_hash(raw_pwd)

# to verify the password
print(check_password_hash(hashed_pwd, raw_pwd)) # return boolean value after validating password

you can read more about werkzeug security here : https://werkzeug.palletsprojects.com/en/2.0.x/utils/#module-werkzeug.security

Wold answered 1/9, 2021 at 5:32 Comment(0)
T
0

Here is a simpler way (taken from effbot), provided passwords with a length greater than 8 will not be a problem*:

import crypt

import random, string

def getsalt(chars = string.letters + string.digits):
    # generate a random 2-character 'salt'
    return random.choice(chars) + random.choice(chars)

for generate the password :

crypt.crypt("password", getsalt())

*: A password with a length greater than 8 is stripped from the right down to 8 chars long

Tagalog answered 5/1, 2013 at 2:49 Comment(1)
crypt have problems to encrypt password of more than 8 chars I thinkTagalog

© 2022 - 2025 — McMap. All rights reserved.