Table of Contents
When you create an account on a website, what happens to your password behind the scenes determines how safe it really is. If the service stores it in plain text, a single data breach exposes every credential instantly. If it hashes passwords but skips a critical step called salting, attackers can still crack millions of hashes in minutes using precomputed tables. Salting is the technique that makes the difference – and understanding it will change how you think about password security as a whole.
This article covers password hashing, why plain-text storage is dangerous, what a salt is, how salting works, and why modern key derivation functions like Argon2 and bcrypt have made password storage dramatically more resilient.
How Passwords Are Stored: The Basics
Plain-Text Storage
In the earliest days of computing, passwords were stored exactly as the user typed them. If your password was hunter2, the database held the literal string hunter2. Anyone who gains access to the database – through a breach, an insider threat, or a misconfiguration – immediately has every password in readable form.
Despite being universally condemned, some services have been caught storing passwords this way even in recent years. When breaches happen, plain-text databases give attackers instant access to every account.
Hashing: A One-Way Function
The solution is hashing. A hash function takes an input of any length and produces a fixed-length output called a hash (or digest). The process is designed to be one-way: given the hash, there is no mathematical way to reverse it back to the original input.
Here is an example using the SHA-256 hash function:
Input: "hunter2"
SHA-256: f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
Instead of storing hunter2, the service stores only the hash. When you log in, the system hashes whatever you type and compares it to the stored hash. If they match, you are authenticated. The actual password is never stored.
This is a major improvement over plain text. If an attacker steals the database, they get hashes, not passwords. But hashing alone has a critical weakness that salting was invented to address.
The Problem with Unsalted Hashes
Hashing without salting has two vulnerabilities that attackers exploit ruthlessly: rainbow table attacks and identical hash collisions across users.
Rainbow Table Attacks
A rainbow table is a massive precomputed lookup table that maps hash outputs back to their inputs. An attacker generates this table once by hashing millions (or billions) of common passwords, dictionary words, and character combinations. Then, for any stolen hash, they simply look it up in the table.
Here is how it works in practice:
Precomputed Rainbow Table (partial):
"password" -> 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
"123456" -> 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
"hunter2" -> f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
"qwerty" -> 65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5
... millions more entries ...
Stolen hash from database:
f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
Rainbow table lookup -> "hunter2"
The attacker never needs to “crack” anything. They just look up the hash and find the password instantly. Rainbow tables for common algorithms are freely available online. Once you have the table, cracking one hash or one million hashes takes the same trivial amount of time per hash.
Identical Passwords Produce Identical Hashes
Without salting, two users who choose the same password have identical hashes in the database:
User: alice Password: "sunshine" Hash: 8b2c86dba8e...
User: bob Password: "sunshine" Hash: 8b2c86dba8e...
User: carol Password: "dragon" Hash: 37b4e2d82a1...
An attacker scanning the database sees that Alice and Bob share the same password – even without knowing what it is. If they crack one hash, they crack every account using that password simultaneously. Given that the most common passwords are shared by millions of people, this vulnerability is enormous.
What Is a Salt?
A salt is a random string of data that is generated uniquely for each user and combined with their password before hashing. The salt itself is not secret – it is stored alongside the hash in the database. Its purpose is not to be hidden but to ensure that every hash is unique, even when two users choose the same password.
Here is the concept in simple terms:
Without salt:
hash("sunshine") -> 8b2c86dba8e... (same for every user)
With salt:
hash("a1f09c3e" + "sunshine") -> 7d3f28ba91c... (unique to alice)
hash("b7e42d1a" + "sunshine") -> e5a917c403f... (unique to bob)
Alice and Bob both chose “sunshine,” but because each was assigned a different random salt, their hashes are completely different. An attacker looking at the database sees two unrelated hashes with no indication that the underlying passwords are identical.
How Salting Works Step by Step
Account Creation
- The user chooses a password (e.g.,
correcthorse) - The system generates a cryptographically random salt (e.g.,
x9Kp2mNq) - The system concatenates the salt and password:
x9Kp2mNqcorrecthorse - The combined string is passed through the hash function
- Both the salt and the resulting hash are stored in the database
Database record:
username: alice
salt: x9Kp2mNq
hash: a84f2c1e8b3d7f09e6a5c2b1d4f8e7a3...
Login Verification
- The user enters their password:
correcthorse - The system retrieves Alice’s salt from the database:
x9Kp2mNq - The system concatenates the salt and the entered password:
x9Kp2mNqcorrecthorse - The combined string is hashed using the same algorithm
- The resulting hash is compared to the stored hash
- If they match, authentication succeeds
A Visual Example: Salting in Action
To make this concrete, here is a side-by-side comparison showing how salting transforms identical passwords into completely different hashes:
+------------------+------------------+------------------+
| User | Without | With |
| | Salting | Salting |
+------------------+------------------+------------------+
| alice | | Salt: a1f09c3e |
| password: | Hash: 5e884898.. | Hash: 7d3f28ba.. |
| "password" | | |
+------------------+------------------+------------------+
| bob | | Salt: b7e42d1a |
| password: | Hash: 5e884898.. | Hash: e5a917c4.. |
| "password" | (IDENTICAL!) | (DIFFERENT!) |
+------------------+------------------+------------------+
| carol | | Salt: c3d85f2e |
| password: | Hash: 5e884898.. | Hash: 2b8c64df.. |
| "password" | (IDENTICAL!) | (DIFFERENT!) |
+------------------+------------------+------------------+
Without salting, all three users produce the exact same hash. An attacker who cracks one has cracked all three. With salting, each hash is unique. Cracking Alice’s hash tells the attacker nothing about Bob’s or Carol’s passwords, even though they are identical.
Why Salts Defeat Rainbow Tables
Rainbow tables are precomputed for a specific hash function applied directly to passwords. When you add a salt, the input becomes the salt plus the password. Since each user has a unique salt, the attacker would need a separate rainbow table for every possible salt value.
If salts are 128 bits long, there are 2^128 possible values – roughly 3.4 x 10^38. An attacker would need that many separate rainbow tables. This is computationally impossible. The entire storage capacity of every hard drive ever manufactured would not hold a fraction of those tables.
Salting does not make individual passwords harder to guess. An attacker can still brute-force a specific user’s password by trying candidates with the known salt. But salting eliminates the scalability that makes precomputed attacks dangerous. Each hash must be attacked individually, transforming a mass compromise into a per-user effort.
This is why salting and password entropy work together. Salting forces individual attacks, and high entropy ensures those attacks take an impractically long time.
Beyond Basic Hashing: Key Derivation Functions
Simple hash functions like SHA-256 were designed to be fast – an advantage for file integrity checks but a liability for password storage. A modern GPU can compute billions of SHA-256 hashes per second, making brute-force attacks feasible even against salted passwords if the password is weak.
Key derivation functions (KDFs) solve this. A KDF is designed specifically for password hashing: it incorporates salting automatically and adds deliberate computational cost to make each attempt slow and resource-intensive.
bcrypt
Introduced in 1999, bcrypt was one of the first widely adopted password hashing functions. Its key feature is a configurable cost factor that controls how many iterations the algorithm performs. As hardware gets faster, you increase the cost factor to keep pace.
bcrypt output format:
$2b$12$LJ3m5R4yPQtSjXkzGh8C9OuK7pVzA4fEqWdN1xYbRnmT6sGvH3Fy
$2b = algorithm identifier
$12 = cost factor (2^12 = 4,096 iterations)
$LJ3m... = 22-character salt + 31-character hash
At a cost factor of 12, bcrypt takes roughly 250 milliseconds per hash on modern hardware. That is imperceptible to a user logging in, but it limits an attacker to about four guesses per second per CPU core – compared to billions with raw SHA-256.
scrypt
Developed in 2009, scrypt adds memory hardness. It requires a large amount of RAM for each hash computation, which is significant because GPUs and custom ASICs have enormous computational power but limited memory per core. scrypt neutralizes this advantage by demanding resources that parallel hardware cannot easily provide.
Argon2
Argon2 won the Password Hashing Competition in 2015 and is the current state of the art. It comes in three variants:
- Argon2d – Optimized for resistance against GPU cracking attacks
- Argon2i – Optimized for resistance against side-channel attacks
- Argon2id – A hybrid that provides both protections (recommended for most use cases)
Argon2 allows you to configure three independent parameters:
- Time cost – Number of iterations
- Memory cost – Amount of RAM required
- Parallelism – Number of threads used
A typical configuration uses 64 MB of memory, 3 iterations, and 4 threads, producing a hash that takes about 500 milliseconds on a modern server but would take an attacker days or weeks to brute-force for a single reasonably strong password.
PBKDF2
PBKDF2 is the oldest commonly used KDF, standardized in RFC 2898. It repeatedly applies HMAC-SHA256 for a configurable number of iterations (NIST recommends a minimum of 600,000). While well-understood, it lacks memory-hardness, making it more vulnerable to GPU-based attacks than scrypt or Argon2.
Comparing KDFs
| Feature | bcrypt | scrypt | Argon2id | PBKDF2 |
|---|---|---|---|---|
| Year introduced | 1999 | 2009 | 2015 | 2000 |
| Memory-hard | No | Yes | Yes | No |
| GPU resistance | Moderate | High | High | Low |
| Configurable parameters | 1 (cost) | 3 (N, r, p) | 3 (time, memory, parallelism) | 1 (iterations) |
| Current recommendation | Good | Good | Best | Acceptable |
How KeePass and PanicVault Use These Techniques
The KeePass database format – used by PanicVault – implements a layered defense combining salting, key derivation, and strong encryption:
Master passphrase entry – You enter your master passphrase to unlock the vault.
Key derivation with Argon2d – The KeePass 4.x format (KDBX4) uses Argon2d by default. Your passphrase is combined with a randomly generated salt (stored in the database header) and processed through Argon2d with configurable memory and iteration parameters. This produces a derived key that is computationally expensive to brute-force.
Additional transform seed – KeePass adds a second random seed as an extra input to key derivation, conceptually similar to a second salt, regenerated each time you save the database.
AES-256 encryption – The derived key is used to encrypt the entire database contents with AES-256 in CBC mode (or ChaCha20 in newer configurations). Without the correct derived key, the database contents are indistinguishable from random noise.
Integrity verification – A HMAC-SHA256 authentication code verifies that the encrypted data has not been tampered with.
An attacker who obtains your vault file faces multiple reinforcing layers. They cannot use rainbow tables because of the salt. They cannot efficiently brute-force the passphrase because of Argon2’s deliberate slowness. And they still face AES-256 encryption even if they could derive the key.
This is zero-knowledge encryption in practice: only someone who knows the master passphrase can derive the key. Neither PanicVault nor any cloud service ever has access to it.
How Salting Relates to Password Cracking
Salting fundamentally changes the economics of password cracking. Without salting, an attacker who compromises 10 million password hashes can attack all of them simultaneously with a single rainbow table or brute-force pass. With proper salting, each hash must be attacked independently, and the cost scales linearly with users.
Combined with a slow KDF, the math becomes punishing:
Without salt + fast hash (SHA-256):
10,000,000 users x 1 lookup per user = seconds
With salt + Argon2 (500ms per attempt):
1 user, testing 1,000,000 common passwords = ~6 days
10,000,000 users x 1,000,000 attempts each = ~164,000 years
Salting and key stretching transform mass cracking into an individually targeted, computationally expensive operation. But salting cannot protect a truly weak password – “123456” will fall quickly regardless, because it is among the first candidates in any dictionary attack. Salting is one layer of defense; choosing a strong password is another. Both are essential.
What Happens When Salting Is Missing
When LinkedIn was breached in 2012, 6.5 million password hashes were leaked. The passwords were hashed with SHA-1 but not salted. Security researchers cracked over 90% of the hashes within days using rainbow tables. Had LinkedIn used per-user salts with a proper KDF, the vast majority would have remained uncracked.
The pattern has repeated across many services. Missing salts allow attackers to crack passwords at industrial scale, turning a database theft into a mass credential compromise. Those cracked passwords then fuel credential stuffing attacks, exploiting the fact that users tend to reuse passwords across accounts.
What You Should Take Away
You cannot control whether a service salts its hashes properly. But understanding salting helps you make informed decisions:
Use a password manager – If a service is breached and your password is exposed, the damage is limited to that one account when every password is unique. PanicVault generates and stores unique, high-entropy passwords for each account in a locally encrypted vault protected by salting and Argon2 key derivation.
Choose strong, unique passwords – Salting forces per-user attacks, but a weak password still falls quickly. Aim for at least 60 bits of entropy for regular accounts and 80+ bits for your master passphrase.
Enable two-factor authentication – Even if an attacker cracks your salted hash, 2FA provides an independent barrier.
Monitor for breaches – Services like Have I Been Pwned alert you when your email appears in a known breach. Change compromised passwords promptly.
Favor security-conscious services – Services using modern KDFs (Argon2, bcrypt, scrypt) with proper salting are investing in your protection.
Password salting is invisible to you as a user – you never see the salt and never interact with it. But it is one of the most important defenses standing between your credentials and the attackers who want them. Combined with strong password choices, a reliable password manager, and proper key derivation, salting ensures that even when breaches happen, your passwords remain protected.
