In Linux, user account definitions reside in /etc/passwd (world-readable), while encrypted password hashes and credential aging rules reside in /etc/shadow (restricted to root with 0640 or 0600 permissions).
Understanding /etc/shadow field definitions is essential for system auditing, incident response, and credential analysis.
Anatomy of an /etc/shadow Entry
Each line consists of 9 colon-separated fields:
n1x:$6$UoDmVdoW$tYQQm5uHgOpe...:16631:7:15:2:14:16819:
| Field # | Attribute Name | Value in Example | Description |
|---|---|---|---|
| 1 | Username | n1x |
Account login name matching /etc/passwd. |
| 2 | Encrypted Password | $6$UoDmVdoW$... |
Cryptographic hash prefixed by algorithm identifier. |
| 3 | Last Changed Date | 16631 |
Days since the Unix Epoch (Jan 1, 1970) when password was last updated. |
| 4 | Minimum Age | 7 |
Days user must wait before they can change password again. |
| 5 | Maximum Age | 15 |
Days before the password expires and must be changed. |
| 6 | Warning Period | 2 |
Days prior to expiration that warning prompts begin displaying. |
| 7 | Inactivity Period | 14 |
Days after expiration during which user can still change password; past this, account is disabled. |
| 8 | Expiration Date | 16819 |
Absolute date (in days since epoch) when account is permanently disabled. |
| 9 | Reserved | (empty) | Flag reserved for future operating system use. |
Identifying Password Hash Algorithms
The second field begins with $id$salt$hash. The ID reveals the cryptographic hashing algorithm used:
$1$: Legacy MD5$2a$/$2y$: Blowfish (bcrypt)$5$: SHA-256$6$: SHA-512 (standard on modern Linux distributions)$y$: Yescrypt (modern default on Debian 12+ and Fedora)!or*: Account locked or password disabled (cannot log in via password).
Calculating Human-Readable Dates from Epoch Days
To convert the integer day counts (such as 16631 or 16819) into standard dates using Python:
from datetime import datetime, timedelta
epoch = datetime(1970, 1, 1)
last_changed = epoch + timedelta(days=16631)
print(last_changed.strftime("%Y-%m-%d"))
# Output: 2015-07-15
Managing Shadow Policies with chage
Rather than manually editing /etc/shadow, use the chage (change age) command:
# Set aging policy: min 7 days, max 90 days, warn 7 days, inactive 14 days, expire 2026-12-31
sudo chage -m 7 -M 90 -W 7 -I 14 -E 2026-12-31 n1x
# Display active account aging details
sudo chage -l n1x
System-Wide Default Settings (/etc/login.defs)
To set default aging rules for all newly created accounts, configure /etc/login.defs:
PASS_MAX_DAYS 90
PASS_MIN_DAYS 7
PASS_WARN_AGE 7
PASS_MIN_LEN 12