Password hashing

Password hashing and password based key derivation mechanisms in actual use are all based on the idea of iterating a hash function many times on a combination of the password and a random salt, which is stored along with the hash, and allows verifying a proposed password while avoiding clear-text storage.

The latest developments in password hashing have been memory-hard mechanisms, pioneered by the scrypt mechanism [SD2012], which is implemented by functions exposed in nacl.pwhash.

Scrypt usage

Password storage and verification

The scryptsalsa208sha256_str() internally generates a random salt, and returns a scrypt hash already encoded in ascii modular crypt format, which can be stored in a shadow-like file:

>>> import nacl.pwhash
>>> password = b'my password'
>>> for i in range(4):
...     print(nacl.pwhash.scryptsalsa208sha256_str(password))
...
b'$7$C6..../....p9h...'
b'$7$C6..../....pVs...'
b'$7$C6..../....qW2...'
b'$7$C6..../....bxH...'

To verify a user-proposed password, the scryptsalsa208sha256_verify() function extracts the used salt and scrypt memory and operation count parameters from the modular format string and checks the compliance of the proposed password with the stored hash:

>>> import nacl.pwhash
>>> hashed = (b'$7$C6..../....qv5tF9KG2WbuMeUOa0TCoqwLHQ8s0TjQdSagne'
...           b'9NvU0$3d218uChMvdvN6EwSvKHMASkZIG51XPIsZQDcktKyN7'
...           )
>>> correct = b'my password'
>>> wrong = b'My password'
>>> # the result will be True on password match
... # on mismatch
... res = nacl.pwhash.verify_scryptsalsa208sha256(hashed, correct)
>>> print(res)
True
>>>
>>> res2 = nacl.pwhash.verify_scryptsalsa208sha256(hashed, wrong)
Traceback (most recent call last):
    ...
nacl.exceptions.InvalidkeyError: Wrong password
>>>

Key derivation

Alice needs to send a secret message to Bob, using a shared password to protect the content. She generates a random salt, combines it with the password using kdf_scryptsalsa208sha256() and sends the message along with the salt and key derivation parameters.

from nacl import pwhash, secret, utils

ops = pwhash.SCRYPT_OPSLIMIT_SENSITIVE
mem = pwhash.SCRYPT_MEMLIMIT_SENSITIVE

salt = utils.random(pwhash.SCRYPT_SALTBYTES)

password = b'password shared between Alice and Bob'
message = b"This is a message for Bob's eyes only"

Alices_key = pwhash.kdf_scryptsalsa208sha256(secret.SecretBox.KEY_SIZE,
                                             password, salt,
                                             opslimit=ops, memlimit=mem)
Alices_box = secret.SecretBox(Alices_key)
nonce = utils.random(secret.SecretBox.NONCE_SIZE)

encrypted = Alices_box.encrypt(message, nonce)

# now Alice must send to Bob both the encrypted message
# and the KDF parameters: salt, opslimit and memlimit;
# using the same parameters **and password**
# Bob is able to derive the correct key to decrypt the message


Bobs_key = pwhash.kdf_scryptsalsa208sha256(secret.SecretBox.KEY_SIZE,
                                           password, salt,
                                           opslimit=ops, memlimit=mem)
Bobs_box = secret.SecretBox(Bobs_key)
received = Bobs_box.decrypt(encrypted)
print(received)

if Eve manages to get the encrypted message, and tries to decrypt it with a incorrect password, even if she does know all of the key derivation parameters, she would derive a different key. Therefore the decryption would fail and an exception would be raised:

>>> from nacl import pwhash, secret, utils
>>>
>>> ops = pwhash.SCRYPT_OPSLIMIT_SENSITIVE
>>> mem = pwhash.SCRYPT_MEMLIMIT_SENSITIVE
>>>
>>> salt = utils.random(pwhash.SCRYPT_SALTBYTES)
>>>
>>> guessed_pw = b'I think Alice shared this password with Bob'
>>>
>>> Eves_key = pwhash.kdf_scryptsalsa208sha256(secret.SecretBox.KEY_SIZE,
...                                            guessed_pw, salt,
...                                            opslimit=ops, memlimit=mem)
>>> Eves_box = secret.SecretBox(Eves_key)
>>> intercepted = Eves_box.decrypt(encrypted)
Traceback (most recent call last):
    ...
nacl.exceptions.CryptoError: Decryption failed. Ciphertext failed ...
[SD2012]A nice overview of password hashing history is available in Solar Designer’s presentation Password security: past, present, future