hynek / argon2-cffi

Secure Password Hashes for Python
https://argon2-cffi.readthedocs.io/
MIT License
549 stars 47 forks source link

Support password verification without sending the password from client to server #149

Closed aviv-barel-pp closed 1 year ago

aviv-barel-pp commented 1 year ago

Assume both server and clients are written in python

to verify argon2 hash, the client need to send the password to the server, and then the server can verify it using the verify method.

If the client wants to prove it knows the password without sending it to the server the verify method isn't enough. We can use sha256 of the password in the client and use this sha256 as the password this way the server wont get from the client the actual password and can still verify the client knows the real password. but sending sha256 of a password is less secure than sending argon2 hash. the problem is that because argon2 hash function is always generating a new random salt, the verify methos must use the actual password.

If the hash function had the ability to get pre-generated salt known only to the client, the client could send the same hash to the server every time and the server will be validating the hash instead of validating the secret password

hynek commented 1 year ago

I'm not sure I 100% understood, so just to double-check:

You'd like that the server sends the client a salt (presumably the salt that it added to its own password) and the client returns an argon2 hash with said salt and the server can just compare its own hash with the one the client sent? 🤔

aviv-barel-pp commented 1 year ago

No, I would like that the client will have a salt that is never shared with the server. so the client won't have to send the actual password to the server, but will use a constant argon2 hash instead.

This is the flow I have in my mind Client code:

def get_temporary_token_from_server(user_name, user_password):
    ph = PasswordHasher(salt=os.environ.get("CLIENT_SALT")
    argon2_hash = ph.hash(user_password)
    token_from_server = server.get_token(user_name=user_name, password_hash=argon2_hash)

Server code:

def verify_password_hash(user_name, hash_from_client):
    user = get_user(user_name) # In the DB we store for each user argon2_hash and a unique random salt)
    user_hash_to_verify = concat(hash_from_client + user.user_salt)

    ph = PasswordHasher()
    # here we verify that the user.password_hash is a valid argon2 hash for user_hash_to_verify which is a constant string we can create by concating the constant argon2_hash sent via the client and the salt kept for this user in the server DB
    ph.verify(user.password_hash, user_hash_to_verify)
hynek commented 1 year ago

I presume #153 would fix your problem?

aviv-barel-pp commented 1 year ago

yes, thanks :+1:

hynek commented 1 year ago

23.1.0 is on PyPI