The /etc/shadow Hash Is a Recipe, Not Random Noise

daemon:*:1:1:The devil himself:/:

That string sitting in your /etc/shadow file, the one that starts with $6$, looks like keyboard mash. It isn't. It's a recipe: algorithm, salt, and the actual hash, all readable if you know how to look. I still find it strange that your password protection boils down to something you could almost type by accident.

Back in the early Unix days, your password hash lived in /etc/passwd, right next to your username, in a file the whole system could read. In 1980, Bill Joy decided the daemon account shouldn't ever let anyone log in, so he put a single * in the password field. That tiny asterisk became the template for locking accounts, and eventually for the whole shadow file.

So the next time you see $6$abc$k2P9..., you're looking at decades of security evolution packed into a few characters. But how did we get from world-readable password files to this, and what does each piece of that string actually do?

Where Linux Stores Password Hashes

The password hash lives in /etc/shadow, not /etc/passwd. Each line follows the format $6$salt$hash, where the $6$ prefix means SHA-512 crypt, the middle field is the random salt, and the final field is the actual hash. This is a change from older systems that stored hashes directly in /etc/passwd, which was world-readable.

The file permissions are what make this work: /etc/shadow is owned by root:shadow with mode 640, meaning only root and members of the shadow group can read it. /etc/passwd remains world-readable (644) but contains only placeholder x characters where the hash used to be. This separation matters because /etc/passwd is readable by every user and process on the system, so putting hashes there meant any local user could copy them and crack them offline.

ls -l /etc/shadow

My take is that this split is the right call, but it only protects against casual snooping. If an attacker gets root, or if your backups leak, /etc/shadow is sitting there ready for offline cracking. The $6$ SHA-512 method is also showing its age; it's faster to brute-force on modern GPUs than Argon2id or bcrypt, which is why newer systems default to $y$ (yescrypt) or $argon2id$.

Why Every Hash Includes a Salt

If two users both choose "hunter2" as their password, their entries in /etc/shadow look nothing alike. One might be $6$Xk9mP2qL$vN8... and the other $6$aB3dEf7J$wR4.... The $6$ prefix means SHA-512 crypt, the middle segment is the salt, and the tail is the actual hash. Because the salt is random per account, identical passwords produce completely different output strings. That's the entire point.

This defeats rainbow tables. Without a salt, an attacker precomputes hashes for the 10 million most common passwords once, then looks up leaked hashes against that list. One table cracks every account using "password123". With per-user salts, that precomputed table is useless. The attacker now has to brute-force each hash individually, which turns a mass attack into a per-account grind.

The salt comes from a cryptographically secure random source, typically 16 bytes for SHA-512 crypt, pulled from the operating system's entropy pool. It isn't derived from the password or username, and it isn't secret; it sits in plaintext right next to the hash so the system can verify logins later.

import crypt
hashed = crypt.crypt("hunter2", crypt.mksalt(crypt.METHOD_SHA512))
print(hashed)

Bottom line: salts are non-negotiable because they make precomputation attacks economically unfeasible. But there's a catch: a salt only stops lookup tables, not brute force. If your password is "123456", a salted SHA-512 hash still falls to offline cracking in seconds on modern GPUs. Salts buy you uniqueness; you still need a slow, memory-hard algorithm like Argon2 to buy you time.

Anatomy of the $6$ Hash

I think the $6$ finding is technically real but the exposure is narrower than the headline suggests. The community is right to push back on the premise—musl without PAM and with OpenSSH password auth enabled is a specific, non-default configuration, mostly seen in hand-rolled Alpine setups. Most musl systems either use PAM, disable password authentication entirely, or run dropbear instead of OpenSSH.

The real gotcha here isn't the SHA-512 crypt algorithm itself—$6$ is computationally expensive compared to older schemes like MD5. The problem is the plumbing around it. Without PAM, there's no account lockout, no rate limiting, and no delay injection after failed attempts; OpenSSH hands the password straight to crypt() with nothing in between. That raw exposure is the actual vulnerability, not the hash.

Compared to glibc systems where PAM modules can enforce progressive delays or lock accounts after repeated failures, this musl path offers none of that protection. Whether that matters depends entirely on whether internet-facing devices are actually running this exact stack. My guess is they're rarer than the discussion assumes. The question worth sitting with: are there enough misconfigured embedded or IoT devices running musl-without-PAM-and-SSH-passwords to make this a widespread threat, or is this really just a hardening note for a configuration that mostly doesn't exist in the wild?

What the Algorithm Choice Actually Means

The community is asking exactly the right question: where does this actually bite? The flaw requires a specific stack — musl libc, no PAM, and OpenSSH configured for password authentication — and that combination isn't as common as the disclosure might imply. Most musl-based systems are Alpine Linux containers or embedded devices, and while they often lack PAM, many admins disable password auth by default in those environments anyway. I think the real-world exposure is narrower than the headline suggests.

That said, "narrow" isn't "nonexistent." There are almost certainly some minimal Alpine containers or IoT devices running SSH with passwords enabled and no PAM layer, and for those systems this is a genuine remote code execution risk. The question is whether defenders can identify them at scale. If your infrastructure is mostly musl-based but you've already moved to key-only auth, this doesn't change your posture. If you're running password-based SSH on a stripped-down Alpine image exposed to the internet, you need to patch now.

I don't know if we'll see widespread exploitation, because the target set seems small. But the uncertainty here is worth sitting with: how many internet-facing systems actually run this exact configuration? If the answer is "thousands," that's a problem. If it's "dozens," it's a footnote. We probably won't have good numbers until someone scans for it.

Conclusion

The * Bill Joy dropped into the daemon entry in 1980 is still the kill switch today—no hash to match means no login, full stop. When you look at $6$abc$k2P9… in /etc/shadow, that's SHA-512 plus a salt doing exactly what that asterisk did, just with actual cryptography instead of a placeholder.

My verdict: this setup is solid, but only if your password isn't terrible. SHA-512 crypt is deliberately slow, which is the right call, but it won't protect you from "Password1!" getting hashed and cracked offline in minutes. The real limitation is that moving hashes to /etc/shadow stopped casual shoulder-surfing, but anyone with read access still gets a perfect offline cracking target.

I keep wondering if we've actually solved the problem or just moved it. The architecture is the same one Joy sketched in 1980—salt, hash, compare—just with longer strings. Next time you're staring at that $6$, ask yourself whether the file permissions are actually tighter than they were in /etc/passwd, or if you're just hoping nobody looks.

Related posts