def __init__(self, login, email, password): """Takes a validated username, email, and password, and initializes a User""" # Simply save the username and email self.login = login self.email = email # Generate a random salt and compute the password hash using the defaults salt = secure_random(SALT_BYTES) digest = PASSWORD_ALG(password, salt, PASSWORD_ROUNDS)[:PASSWORD_BYTES] # Save the password hash, along with the salt, algorithm, and work factor # used to compute it self.password = to64(digest) self.salt = to64(salt) self.algorithm = PASSWORD_ALG.name self.work_factor = PASSWORD_ROUNDS
def verify(cls, login, password): """Take a username and password and return the corresponding user, if it exists and the provided password is correct""" # FIXME: minor timing attack to check if user exists # Grab data about the user from the database using their login. Deny # the login attempt if the specified user doesn't exist. user = cls.get(login) if user is None: return None # Retrieve the algorithm and parameters from the database to determine # which hashing function to use. These may not be the same as the current # standard, so be sure the correct paramaters are being used to produce # the same hash as the one stored in the database. stored_pass = from64(user.password) stored_dig_len = len(stored_pass) stored_salt = from64(user.salt) stored_salt_len = len(stored_salt) stored_alg = get_algorithm(user.algorithm) stored_work = user.work_factor presented = stored_alg(password, stored_salt, stored_work)[:stored_dig_len] # Make sure the comparison is done using this special operation, to avoid # timing attacks. The execution time of the comparison MUST NOT DEPEND ON # THE LOCATIONS OF ANY DIFFERENCES. If the credentials presented don't match # the database, deny the login attempt. if not slow_equals(presented, stored_pass): return None # If the stored hashing parameters don't match the current standard, # update them. Note that THIS MUST ONLY BE DONE ONCE THE PASSWORD HAS # BEEN VERIFIED using the stored values, or the account could be updated # to use invalid credentials. Which would be very bad. if (stored_alg is not PASSWORD_ALG or stored_dig_len != PASSWORD_BYTES or stored_salt_len != SALT_BYTES or stored_work != PASSWORD_ROUNDS): salt = secure_random(SALT_BYTES) stream = PASSWORD_ALG(password, salt, PASSWORD_ROUNDS) user.password = to64(stream[:PASSWORD_BYTES]) user.salt = to64(salt) user.algorithm = PASSWORD_ALG.name user.work_factor = PASSWORD_ROUNDS return user
def pres(cap): h2 = h.copy() # If passed the null requesting capability, hash the nonce instead h2.update(nonce if cap is None else cap.key) return to64(h2.digest())