Storing the salt together with the hash is fine. The salt is essentially public information. Many systems store it in a format like $hashAlgorithm$parameters$salt.hash
.
But isn't this insecure?
No, because the point of a salt is not to be secret. The point for the salt is to be random and unique. It should increase the work factor for the attacker, not prevent an attack completely. Let me demonstrate this:
Scenario 1
Your database contains 1,000,000 users, all just having their hashes stored in plain SHA-1. An attacker compromises it. Their possibilities are now:
- Perform a lookup-attack, meaning that the attacker just looks up the hash in a database of pre-computed hashes.
- Perform an attack with a rainbow-table, which is a variant of the above-mentioned attack.
- Hash a well-known list of passwords themselves (e.g. rockyou.txt), exactly once.
- Brute-force weak passwords, or password in a common scheme, like an uppercase character, followed by 8 lowercase characters and one digit.
Scenario 2
Your database contains 1,000,000 users, all just having their hashes stored in Argon2 with a unique, random salt. An attacker compromises it. Their possibilities are now:
- Hash a well-known list of passwords themselves (e.g. rockyou.txt), 1,000,000 times.
- Brute-force weak passwords, or password in a common scheme, like an uppercase character, followed by 8 lowercase characters and one digit - 1,000,000 times.
It's not possible to do a lookup-attack, because nobody would store the hashes to millions of password, with millions of possible salt combinations. It's also not possible to do a rainbow-table attack, because building the rainbow table for every user with their salt would take longer than to just computer each hash.
Furthermore, just by adding a salt, you changed the amount of work required from "Hash once, check everything" to "Hash every time". In our scenario with 1,000,000 users, you made the task of the attacker 1,000,000 times more difficult.