Direct Answers
What is the mechanism that creates the salt, juxtaposing it to the hash, and make[s] sure the password will comply to the hash+salt combination?
What creates the salt? - A random number generator, used when a user's password is being generated or changed.
What juxtaposes that salt beside the hash? - How exactly to store the results of that random number generation is an implementation decision; it makes no difference to the algorithm. All that's important is that the salt is concatenated to both the value that's stored, and (again, separately) to the value that's hashed.
What makes sure the password will comply with the hash+salt combination? - There are two stages to consider here: Password generation, and password checking.
During password generation, a new, random salt is created. The hash to be stored is then generated from this salt and the user's password, and both the newly-generated salt (as plaintext) and the hash (generated from both pieces) are stored in the password database.
When checking a password, both these fields are retrieved from the password database. The retrieved salt is combined with the newly-entered password, and the result of that combination is hashed. If the combined hash matches the hash which was previously stored, the operation is a success. Thus, the user needs to remember only their password; the plaintext salt was retrieved from the password database. Because the hash was generated from the combination of these two things in the first place, combining them again generates that same value again.
The fact that the same salt value is used when a password is generated and when that password is later checked ensures that the password+salt combination will have the same hash when generated, and when later checked.
Longer-Form Explanation
Initial State: No Salt
Let's say you have a password:
123
...and a cryptographic hash function which hashes that password to a string:
GBIQ
such that checkPassword 123 GBIQ
(as implemented below) emits GOOD
.
It's critical to understand (for the rest of this to make sense) that cryptographic hash functions are designed to be impossible reverse with any means short of a brute-force search (that is, just trying all possible inputs and comparing their outputs against whatever you're trying to find).
What's Wrong With This
...now, someone who hasn't stolen your password database can work out ahead-of-time that GBIQ
is the hash of password 123
, so they can just make a big database with millions of common passwords with their hashes and compare that to any password database they steal. (Also, they can look at which users have the same hash, and immediately know that those users have the same password).
Consequently, if and when they do steal your password database, they can just check the hashes it contains against their big list of hashes whose plaintext passwords they've already figured out -- any matches they find is an account they can break into!
Adding Salt
Let's say you want to stop those attacks -- so, what you do is you generate a random value to use as a "salt" whenever a user changes their password. So, when the user is setting their password to 123
, let's say that you randomly generate the salt 111
. Then, you take the hash of 111|123
, and get a result of P2C/
. You would then store the salt and the hash (of the salt and the password together) as something like:
111|P2C/
(P2C/
is different from 5a3H
because the salt is part of the hash value, even though the password didn't change). When the user enters their password of 123
, you retrieve that 111|
prefix from storage, prepend it to the front, and hash 111|123
. If the result is P2C/
, then you know that the password entered by the user was correct.
Thus, when you generate a new hash for the password 123
without specifying a salt, each one is different:
$ hashPassword 123
3sm|BVve
$ hashPassword 123
gwu|00Eq
$ hashPassword 123
84c|akWi
...but any one of these passes the check:
$ checkPassword 123 '3sm|BVve'
GOOD
$ checkPassword 123 'gwu|00Eq'
GOOD
$ checkPassword 123 '84c|akWi'
GOOD
...and changing any part (the salt or the actual password) will break it:
$ checkPassword 123 'ABC|BVve'
BAD
$ checkPassword 123 '3sm|NOOP'
BAD
Why does this work? The salt isn't just a database prefix, but is also a prefix to the input to the hash function itself. Consequently, every hash is different depending on the salt.
Advantages/Effects
Rainbow Table Attacks Defeated
Now, someone can't just figure out ahead of time that GBIQ
is the hash to 123
-- instead, if they want to work out possible hashes ahead-of-time, they need to calculate and store that hash with every possible salt. Even with small amounts of salt, this gets hard very quickly (because salt is much more random than human-generated passwords): 8 bits of salt multiplies the effort (and storage required) by 256; 16 bits by 65,536; 32 bits by 4,294,967,296.
So instead, someone who steals the password database and sees an entry of ABC|BVve
needs to figure out what strings can be appended to ABC|
and fed into the hash function to get BVve
as output. Because 123
is a really simple password (and four bytes is not remotely a long enough hash), they'll probably find something quickly -- but they had to attack just that one password, rather than just doing a big database merge between the database they stole and the one they precalculated; and with strong passwords and suitably long hashes, such attacks can take a long time.
Even Identical Passwords Have Different Hashes
Moreover, if you have a second user set their password to 123
, they'll (if your salts are long enough and random enough) almost certainly have a different salt. So let's say this second user gets the salt 222
. Since 222|123
has a completely different hash from 111|123
(whereas 111|123
hashes to 5a3H
, perhaps 222|123
hashes to CJq3
, and so this entry is stored in the database as 222|CJq3
), there's no way for someone looking at the password database to know that these two users' passwords are identical (and thus that if they want to find out the CEO's password all they need to do is bribe the janitor, or whomever else is using the same one).
Sample Implementations
Unsalted
The above actually performs the 123
-> GBIQ
transform used in earlier examples:
hashPassword() {
local password=$1
openssl dgst -sha256 -binary <<<"$password" \
| openssl enc -base64 \
| head -c 4 \
&& printf '\n'
}
Consequently, checking a password against a database entry is just a direct comparison:
checkPassword() {
local userPassword=$1 databaseEntry=$2
if [[ $(hashPassword "$userPassword") = "$databaseEntry" ]]; then
echo "GOOD"
else
echo "BAD"
fi
}
Salted
With salt, the process of hashing a password changes: It now accepts a salt to use in the check, and -- to generate the same hash used in a prior run -- must be given the same salt again.
It also has the ability to generate a new random salt (though we could potentially put this responsibility on the caller instead):
hashPassword() {
local password=$1 salt=$2
if ! [[ $salt ]]; then
# generate new random bytes for salt
salt=$(openssl rand -base64 4 | head -c 3)
fi
# put salt at the front of our output
printf '%s|' "$salt"
# then also generate the hash WITH THE SALT AS PART OF THE HASHED VALUE
openssl dgst -sha256 -binary <<<"${salt}|${password}" \
| openssl enc -base64 \
| head -c 4 \
&& printf '\n'
}
checkPassword() {
local userPassword=$1 databaseEntry=$2 salt hash
# split the database entry into the salt and the hash
IFS='|' read -r salt hash <<<"$databaseEntry"
# use that salt with the user's plaintext password to generate a hash
if [[ $(hashPassword "$userPassword" "$salt") = "$databaseEntry" ]]; then
echo GOOD
else
echo BAD
fi
}
P
, hashing functionH
and randomly-generated saltS
, you're storing{S, H({S,P})}
-- so becauseS
is stored plaintext, all you need is a possibleP
to try to rerunH({S,P})
and see if the results match; there aren't any restrictions on the possible passwords needed, and thus nothing with whichP
needs to "comply"). $\endgroup$H(P)
to be considered a correct validation, but is instead expected to matchH({S,P})
. That is to say, when comparing a password against a stored value, you feed into the hash function first the stored salt (which was randomly generated once, but then is reused after that at verification time), and then the password. $\endgroup$