14.2 /etc/shadow: Hashed Passwords and Account Aging
Right, let’s talk about the one file on your system that’s actually supposed to be a secret: /etc/shadow. If /etc/passwd is the public directory—listing everyone’s names and user IDs—then /etc/shadow is the high-security vault where the actual credentials are kept. Its existence is a direct lesson from the early days of UNIX when everyone’s hashed password just sat in /etc/passwd, world-readable. Yes, you read that correctly. It was a disaster. shadow was invented to fix that monumental oopsie.
The core idea is simple but vital: separate the publicly needed user information from the privately sensitive authentication data. So now, in /etc/passwd, you’ll see a placeholder x where the password hash used to be, telling the system “look, the real deal is next door in /etc/shadow, and you’d better have the right permissions to see it.”
The Anatomy of a Shadow Entry
Let’s break down a typical line. You can peek with sudo (because, of course, you can’t just read it—that’s the point).
sudo head -1 /etc/shadow
You’ll get something delightfully cryptic:
charlie:$y$j9T$F8J82fK1TUSF4JQ6...$:19642:0:99999:7:::
It’s a colon-separated list of fields, each with a specific job:
- Username:
charlie- Straightforward, links this entry back to/etc/passwd. - Password Hash:
$y$j9T$F8J82fK1TUSF4JQ6...$- This is the crown jewels. It’s not the password; it’s a one-way cryptographic hash of it. The$y$prefix here indicates it’s using the modern, robustyescrypthashing algorithm. You might also see$6$for SHA-512 or$1$for the ancient, broken MD5—please tell me you don’t. - Date of Last Password Change:
19642- This is the number of days since the “epoch” (January 1, 1970, because of course it is). You can make this human-readable with:echo $((19642 / 365))(spoiler: it’s roughly 2023). - Minimum Password Age:
0- The number of days a user must wait before they can change their password again.0means they can change it whenever they like. Setting this to5prevents a user from immediately changing their password back to the old one after a forced change. - Maximum Password Age:
99999- The number of days after which the password must be changed.99999is the default “practically never” setting. If you set this to90, the user will start getting warnings to change their password as it nears expiration. - Password Warning Period:
7- How many days before expiration the system will start politely nagging the user with “your password will expire soon” messages. - Password Inactivity Period: (empty here) - The number of days after expiration that the account will be disabled if the password hasn’t been changed. An empty field means “just disable it immediately on expiration, no grace period.”
- Account Expiration Date: (empty here) - Another date field, like field 3. This is for setting an absolute date when the entire account will be disabled, regardless of password status. Useful for temporary accounts.
- Reserved Field: (empty) - A placeholder. Just ignore it.
Managing Aging with chage
You are not, under any circumstances, going to edit /etc/shadow by hand. That’s a one-way ticket to locking yourself out of your own system. Instead, we use the chage (change age) command. It’s your friendly, sanity-checking interface for all these date fields.
Want to see the current status for a user? Use chage -l:
sudo chage -l charlie
Output:
Last password change : Jan 01, 2023
Password expires : never
Password inactive : never
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7
Now, let’s say we need to enforce some actual policy. We want Charlie to change his password every 90 days, with a 2-week warning, and we want his account to expire entirely on December 31, 2024. The -M, -W, and -E flags are your friends.
sudo chage -M 90 -W 14 -E 2024-12-31 charlie
The -E flag is brilliant—it accepts either a YYYY-MM-DD date or the number of days since the epoch. But who has time for that? Use the readable date. You can also set a password to be immediately expired, forcing the user to change it on next login, with -d 0 (sets the last change date to epoch).
sudo chage -d 0 charlie
The next time Charlie logs in, he’ll be forced to set a new password before he can do anything else. This is the number one way to handle a new user account or a password compromise.
The One Big Pitfall (Besides Permissions)
The most common administrative headache with shadow is the dreaded exclamation mark (!) or asterisk (*) in the password field.
sudo grep 'charlie' /etc/shadow
# charlie:!:19642:0:99999:7:::
A ! at the front of the hash (or a lone *) means password authentication is disabled. The account is still active, but you can’t log in with a password. This is how you lock an account:
sudo usermod -L charlie # Locks account, adds a '!'
sudo usermod -U charlie # Unlocks account, removes the '!'
The pitfall? You might see this and think “corrupted password entry!” when it’s actually a deliberate lock. It’s also how most system accounts (like bin or daemon) are configured—they never need a password to begin with. So before you start trying to reset a “broken” password, check if the account is just intentionally locked. It’ll save you an hour of confusion. Trust me, I’ve been there.