-
Notifications
You must be signed in to change notification settings - Fork 95
Password hashing
uint8_t master_key[hydro_pwhash_MASTERKEYBYTES];
hydro_pwhash_keygen(master_key);
#define CONTEXT "Examples"
#define OPSLIMIT 10000
#define MEMLIMIT 0
#define THREADS 1
#define PASSWORD "test"
#define PASSWORD_LEN 4
uint8_t derived_key[32];
hydro_pwhash_deterministic(derived_key, sizeof derived_key, PASSWORD, PASSWORD_LEN,
CONTEXT, master_key, OPSLIMIT, MEMLIMIT, THREADS);
uint8_t stored[hydro_pwhash_STOREDBYTES];
hydro_pwhash_create(stored, PASSWORD, PASSWORD_LEN, master_key,
OPSLIMIT, MEMLIMIT, THREADS);
#define OPSLIMIT_MAX 50000
#define MEMLIMIT_MAX 0
#define THREADS_MAX 1
if (hydro_pwhash_verify(stored, PASSWORD, PASSWORD_LEN, master_key,
OPSLIMIT_MAX, MEMLIMIT_MAX, THREADS_MAX) != 0) {
/* incorrect password */
}
uint8_t derived_key[32];
if (hydro_pwhash_derive_static_key(
derived_key, sizeof derived_key, stored, PASSWORD, PASSWORD_LEN,
master_key, OPSLIMIT_MAX, MEMLIMIT_MAX, THREADS_MAX) != 0) {
/* incorrect password */
} else {
/* password verification passed; derived_key contains a derived key */
}
uint8_t new_master_key[hydro_pwhash_MASTERKEYBYTES];
hydro_pwhash_keygen(new_master_key);
if (hydro_pwhash_reencrypt(stored, master_key, new_master_key) != 0) {
/* master key is incorrect */
} else {
/* stored has been reencrypted in-place using the new master key */
}
#define OPSLIMIT_NEW (OPSLIMIT * 2)
hydro_pwhash_upgrade(stored, master_key, OPSLIMIT_NEW, MEMLIMIT, THREADS);
Secret keys used to encrypt or sign confidential data have to be chosen from a very large keyspace.
However, passwords are usually short, human-generated strings, making dictionary attacks practical.
Password hashing functions derive a high-entropy secret key of any size from a password.
- The generated key will have the size defined by the application, no matter what the password length is.
- The same password hashed with same parameters will always produce the same output.
- The function deriving a key from a password is CPU intensive, to mitigate brute-force attacks by requiring a significant effort to verify each password.
Common use cases:
- Password storage, or rather: storing what it takes to verify a password without having to store the actual password.
- Deriving a secret key from a password, for example for disk encryption.
void hydro_pwhash_keygen(uint8_t master_key[hydro_pwhash_MASTERKEYBYTES]);
libhydrogen's password hashing API requires a master key. The main purpose of this key is to encrypt all hashed passwords, along with their parameters.
Hashed passwords and master keys should be stored in different places: hashed passwords are typically stored in a database, whereas the master key can be statically loaded or hardcoded in the application.
If the database ever gets breached, the list of hashed passwords will be completely useless without the master password.
The storage format supports reencryption and algorithm upgrades.
The hydro_pwhash_keygen()
function creates a new master key. This operation is usually performed offline.
For that feature to be valuable, always use that function to create the key; do not use a password instead.
int hydro_pwhash_deterministic(
uint8_t *h, size_t h_len,
const char *passwd, size_t passwd_len,
const char ctx[hydro_pwhash_CONTEXTBYTES],
const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES],
uint64_t opslimit, size_t memlimit, uint8_t threads);
The hydro_pwhash_deterministic()
function derives a deterministic high-entropy key of any length (h_len
bytes) from a password passwd
of length passwd_len
bytes, a context ctx
, a master key master_key
and a set of parameters for the hash function:
-
opslimit
is the number of iterations. The higher the number, the slower the function will be, and the more secure the end result will be against brute-force attacks. This should be adjusted according to the hardware, and to application constraints. -
memlimit
is the maximum amount of memory to use. The current function use a fixed amount of memory, and ignores this parameter. It can be unconditionally set to0
. -
threads
is the number of threads. The current function ignores this parameter. It can be unconditionally set to1
.
The resulting key is put into h
.
This function can be used to derive a key from a password if no other information has been stored. For example, it can be used to encrypt/decrypt a file using nothing but a password.
int hydro_pwhash_create(uint8_t stored[hydro_pwhash_STOREDBYTES],
const char *passwd, size_t passwd_len,
const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES],
uint64_t opslimit, size_t memlimit, uint8_t threads);
The hydro_pwhash_create()
function computes a fixed-length (hydro_pwhash_STOREDBYTES
bytes), hashed, encrypted, authenticated representative of the password passwd
of length ``passwd_len`, that can be safely stored in a database.
This representative can be used to later check if a user provided password is likely to be the original one, without ever storing the password in the database.
The function encrypts and authenticates the representative and the parameters using the master key master_key
. All passwords can safely be encrypted using the same, long-term master key. Applications can also choose to derive master_key
from a master-master key, and a unique user identifier.
The representative includes opslimit
, memlimit
and threads
: these do not have to be stored separately.
Note that the representative is not a string: this is binary data, that must be stored as a blob in a database, or encoded as a string (for example as a hex value or using a safe base64 variant).
int hydro_pwhash_verify(const uint8_t stored[hydro_pwhash_STOREDBYTES],
const char *passwd, size_t passwd_len,
const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES],
uint64_t opslimit_max, size_t memlimit_max, uint8_t threads_max);
The hydro_pwhash_verify()
function verifies that the password passwd
of length passwd_len
bytes is valid for the stored representative stored
, decrypted using master_key
.
opslimit_max
, memlimit_max
and threads_max
are maximum values, designed to prevent DoS attacks against applications if the input is untrusted. They should be set to the maximum values ever used in the hydro_pwhash_create()
function.
If the encoded parameters in the representative exceed these values, the function returns -1
.
If the representative cannot be decrypted, the function returns -1
without even trying to hash the password.
If the password doesn't appear to be valid for the stored representative, the function returns -1
. If the password passes all the checks, the function returns 0
.
int hydro_pwhash_derive_static_key(
uint8_t *static_key, size_t static_key_len,
const uint8_t stored[hydro_pwhash_STOREDBYTES],
const char *passwd, size_t passwd_len,
const char ctx[hydro_pwhash_CONTEXTBYTES],
const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES],
uint64_t opslimit_max, size_t memlimit_max, uint8_t threads_max);
The hydro_pwhash_derive_static_key()
function verifies that password
is valid for the representative. If this is the case, it fills static_key
with static_key_len
bytes derived from that representative, and returns 0
.
If the password doesn't appear to be valid for what was stored, the function returns -1
.
This function can be used to derive a deterministic, high-entropy key from a password and user-specific data stored in a database.
int hydro_pwhash_reencrypt(
uint8_t stored[hydro_pwhash_STOREDBYTES],
const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES],
const uint8_t new_master_key[hydro_pwhash_MASTERKEYBYTES]);
The hydro_pwhash_reencrypt()
function reencrypts a representative stored
using the current master key master_key
and a new master key new_master_key
.
It updates stored
in-place and returns 0
on success. If the representative couldn't be decrypted using master_key
, the function returns -1
.
If previously passwords become too fast to verify after a hardware upgrade, stored representatives can be upgraded with new parameters without requiring the original passwords.
int hydro_pwhash_upgrade(uint8_t stored[hydro_pwhash_STOREDBYTES],
const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES],
uint64_t opslimit, size_t memlimit, uint8_t threads);
The hydro_pwhash_upgrade()
function upgrades in-place a previously computed representative stored
encrypted using the master key master_key
, to the new parameters opslimit
, memlimit
and threads
.
Note that parameters can only be increased. Trying to reduce the value of an existing parameter will not change the original value.
The function returns 0
on success, or -1
if the data couldn't be decrypted using the provided master password.
#define hydro_pwhash_CONTEXTBYTES 8
#define hydro_pwhash_MASTERKEYBYTES 32
#define hydro_pwhash_STOREDBYTES 128
Constants:
enc_alg = 0x01
hash_alg = 0x01
context = "hydro_pw"
F_RATE = 16
Password stretching:
h := H(context, passwd_len || passwd || salt || hash_alg || threads || memlimit || h_len)
s := F(h ^ 1)
for i in 0..opslimit:
s[..F_RATE] := i || {0}
s := F(s)
h := s[F_RATE..]
Encrypted representative format:
params := hash_alg || threads || opslimit || memlimit || salt || h
enc_rep := E(k=master_key, msg_id=enc_alg, context, enc_alg || params)
If multiple clients can simultaneously log in on a shared server, the computation requirements can exhaust the server's resources. In order to mitigate this, passwords can be pre-hashed on the client. A lower number of iterations on the server will then be acceptable.