Shadow Passwords and the Group File

 

Shadow Passwords and the Group File
Chapter 8 Part 2 — /etc/shadow Security Model and /etc/group Explained
Part 2
of 4
Beginner
Level
~20 min
Read Time
What You Will Learn

In Part 1, we learned that the password field in /etc/passwd just contains an “x” on modern systems. Where did the real password go? To /etc/shadow. This part explains why the shadow password file was introduced, what extra security fields it contains, and how Linux groups work through /etc/group.

Understanding both files is essential for any Linux system programmer or administrator, because they directly control who can access what on the machine.

Key Concepts in This Tutorial
/etc/shadow Shadow Password Security Password Aging /etc/group Group Membership Multiple Groups newgrp command /etc/gshadow
The Security Problem with /etc/passwd

In the early days of UNIX, encrypted passwords were stored directly in /etc/passwd. The file had to be world-readable because many programs (like ls) needed to read it to convert UIDs to usernames. Anyone on the system could read the encrypted passwords.

Here is why that was dangerous. Suppose the encrypted password for user “alice” looks like this:

/* Old-style /etc/passwd with encrypted password visible to all */
alice:6q8VtWL3gcFRc:1001:1001:Alice:/home/alice:/bin/bash
      ^^^^^^^^^^^^^^
      This 13-character DES hash is readable by EVERYONE on the system

An attacker could write a program that takes a dictionary of common passwords, encrypts each one using the same algorithm, and compares the result against the stolen hash. With modern hardware, billions of guesses per second are possible. This is called a dictionary attack or offline brute-force attack.

The solution was to split the file. Keep the non-sensitive parts (UID, GID, home, shell) in the world-readable /etc/passwd, and move the encrypted password to a new file — /etc/shadow — that only root can read.

The Shadow Password File: /etc/shadow

The file /etc/shadow is readable only by privileged programs (those running as root, or with special capabilities). Each line corresponds to one user account and contains the encrypted password plus several password-aging fields.

/* Check permissions on /etc/shadow — it is NOT world-readable */
$ ls -l /etc/shadow
----------  1 root shadow 1234 May 10 09:00 /etc/shadow

/* Regular users cannot read it */
$ cat /etc/shadow
cat: /etc/shadow: Permission denied

/* Root can read it */
$ sudo cat /etc/shadow | grep alice
alice:$6$Kx8r2mYp$LongHashString...:19500:0:99999:7:::

The shadow file uses colons as delimiters just like /etc/passwd, but it has 9 fields. Let us go through each one.

The 9 Fields of /etc/shadow

Here is the full structure of the spwd (shadow password) record:

/* The struct spwd that represents one shadow file entry */
struct spwd {
    char *sp_namp;      /* Field 1: Login name — matches /etc/passwd */
    char *sp_pwdp;      /* Field 2: Encrypted password (the actual hash) */
    long  sp_lstchg;    /* Field 3: Last password change (days since epoch) */
    long  sp_min;       /* Field 4: Minimum days before password can change */
    long  sp_max;       /* Field 5: Maximum days before password MUST change */
    long  sp_warn;      /* Field 6: Days before expiry to warn user */
    long  sp_inact;     /* Field 7: Days after expiry account is locked */
    long  sp_expire;    /* Field 8: Account expiry date (days since epoch) */
    unsigned long sp_flag; /* Field 9: Reserved for future use */
};

A real /etc/shadow line looks like this:

alice:$6$salt$hashedpassword:19500:0:90:7:30:19700:
  |   |                      |     | |  | |  |
  |   |                      |     | |  | |  +-- Field 8: Account expires (empty = never)
  |   |                      |     | |  | +----- Field 7: 30 days inactivity before lock
  |   |                      |     | |  +------- Field 6: Warn 7 days before expiry
  |   |                      |     | +---------- Field 5: Must change every 90 days
  |   |                      |     +------------ Field 4: Cannot change before 0 days
  |   |                      +------------------ Field 3: Last changed on day 19500
  |   +----------------------------------------- Field 2: The hashed password
  +--------------------------------------------- Field 1: Login name
Password Aging — Why It Matters

Password aging forces users to change their passwords periodically. Even if an attacker eventually cracks a stolen password hash, by the time they succeed, the password may already be different.

Think of it like changing the lock on your house every few months. Even if someone copied your old key, it becomes useless after the lock changes.

/* Real-world example: enforce 90-day password policy */

/* Use the chage command to configure password aging */
$ sudo chage -l alice
Last password change                           : May 01, 2026
Password expires                               : Jul 30, 2026  (90 days)
Password inactive                              : Aug 29, 2026  (30 days after expiry)
Account expires                                : never
Minimum number of days between password change : 0
Maximum number of days between password change : 90
Number of days of warning before password expires: 7

/* Set policy for a new user */
$ sudo chage -M 90 -m 0 -W 7 -I 30 newuser
/*         -M=max days  -m=min days  -W=warn days  -I=inactive days */
The Encrypted Password Field in Detail

Modern Linux systems use a format like $id$salt$hash where the prefix tells you which algorithm was used:

/* Password hash format: $algorithm_id$salt$hash */

$1$...  = MD5 (old, considered weak today)
$5$...  = SHA-256
$6$...  = SHA-512 (most common on modern Linux)
$y$...  = yescrypt (newest, used on recent Ubuntu/Fedora)

/* Example SHA-512 hash in /etc/shadow */
alice:$6$rounds=5000$saltstring$verylonghash...:19500:...

/* Special values for locked/disabled accounts */
!password   = Account locked (! prefix)
!!          = No password set, account cannot log in
*           = Account disabled (used for system accounts)

The salt is critical. Two users with the same password will have completely different hashes because their salts are different. This prevents rainbow table attacks, where an attacker precomputes hashes for all common passwords.

The Group File: /etc/group

The group file defines named collections of users. Think of it like departments in a company. The “engineering” group might contain alice, bob, and carol. Any file owned by the engineering group can be configured to grant access to all three of them at once.

$ cat /etc/group

/* Format: groupname:password:GID:member,member,member */
root:x:0:
sudo:x:27:alice,bob
developers:x:200:alice,carol,dave
docker:x:999:alice,bob,carol,dave
testers:x:201:carol,emma,frank

Each line has four fields, also colon-separated.

The 4 Fields of /etc/group

The group structure in C mirrors the four fields:

/* The struct group representing one /etc/group entry */
struct group {
    char  *gr_name;     /* Field 1: Group name (e.g., "developers") */
    char  *gr_passwd;   /* Field 2: Encrypted group password (rarely used) */
    gid_t  gr_gid;      /* Field 3: Numeric Group ID */
    char **gr_mem;      /* Field 4: NULL-terminated array of member usernames */
};

Field 2 (group password) is almost never used today. It was historically used with the newgrp command to let non-members temporarily switch to a group by entering the group password. Modern systems leave this as “x” and store any group passwords in /etc/gshadow.

/* Example: The developers group with GID 200 and 3 members */
developers:x:200:alice,carol,dave

/* Breakdown */
gr_name   = "developers"
gr_passwd = "x"           (real password in /etc/gshadow if any)
gr_gid    = 200
gr_mem    = {"alice", "carol", "dave", NULL}
Primary Group vs Supplementary Groups

This is one of the most confusing parts for beginners, so let us be very clear about it.

A user’s primary group is defined in /etc/passwd (field 4). A user’s supplementary groups are defined by the group file — specifically, by which group entries list that username in their member field.

/* In /etc/passwd */
alice:x:1001:1001:Alice:/home/alice:/bin/bash
                  ^^^^
                  Primary GID = 1001 (group "alice")

/* In /etc/group */
alice:x:1001:          -- primary group, alice is the only member
developers:x:200:alice,carol,dave  -- supplementary group
docker:x:999:alice,bob             -- supplementary group
sudo:x:27:alice                    -- supplementary group

/* Alice belongs to 4 groups total */
$ id alice
uid=1001(alice) gid=1001(alice) groups=1001(alice),27(sudo),200(developers),999(docker)

The kernel checks all of a process’s groups (primary + supplementary) when deciding if access to a group-owned file should be granted. If any one of them matches, access is allowed (subject to the permission bits).

A Complete Real-World Example

Imagine a small software company with these roles:

alice — Senior Developer bob — Junior Developer carol — QA Tester dave — DevOps / Sysadmin
/* /etc/passwd entries */
alice:x:1001:1001:Alice Kumar:/home/alice:/bin/bash
bob:x:1002:1002:Bob Sharma:/home/bob:/bin/bash
carol:x:1003:1003:Carol Patel:/home/carol:/bin/zsh
dave:x:1004:1004:Dave Reddy:/home/dave:/bin/bash

/* /etc/group entries */
developers:x:200:alice,bob
testers:x:201:carol
devops:x:202:dave,alice
sudo:x:27:dave
docker:x:999:dave,alice,bob

With this setup:

/* Source code directory owned by developers group */
$ ls -ld /opt/app/src/
drwxrwx--- 5 root developers 4096 May 10 src/
/* alice and bob can read and write; carol and dave cannot */

/* Deployment scripts owned by devops group */
$ ls -ld /opt/app/deploy/
drwxrwx--- 2 root devops 4096 May 10 deploy/
/* dave and alice can deploy; bob and carol cannot */

/* Only dave can run sudo */
$ sudo visudo
dave ALL=(ALL:ALL) ALL

This is the Linux permission model in action — clean, flexible, and requiring no code, just configuration files.

The newgrp Command and Switching Groups

By default your primary group is set at login. But you can start a new shell session with a different group as the active primary group using newgrp. Any files you create in that shell will inherit the new group.

/* alice is logged in, her primary group is "alice" (GID 1001) */
$ id
uid=1001(alice) gid=1001(alice) groups=1001(alice),200(developers)

/* Switch active group to developers */
$ newgrp developers
$ id
uid=1001(alice) gid=200(developers) groups=1001(alice),200(developers)

/* Any file created now gets group "developers" as owner */
$ touch feature_branch.c
$ ls -l feature_branch.c
-rw-r--r-- 1 alice developers 0 May 10 feature_branch.c

If a group has a password set and you are not a member, newgrp will prompt you for that group password before switching. In practice this is rarely used today.

Practical Commands to Explore Users and Groups
/* View all groups you belong to */
$ groups
alice sudo developers docker

/* View all groups with their members */
$ cat /etc/group

/* Add user bob to the developers group */
$ sudo usermod -aG developers bob
/* -a = append (do not remove existing groups), -G = supplementary group */

/* Create a new group */
$ sudo groupadd testers

/* Check effective group of current shell */
$ id -g
1001

/* List all supplementary groups of current shell */
$ id -G
1001 27 200 999

/* Change the group of an existing file */
$ sudo chown :developers myproject.c
$ ls -l myproject.c
-rw-r--r-- 1 alice developers 0 May 10 myproject.c
Summary: Three Files, One Security Model
/etc/passwd — public user info (UID, home, shell) /etc/shadow — private password hashes (root only) /etc/group — group names, GIDs, membership lists /etc/gshadow — private group passwords (rare use)

The split between passwd and shadow was a security improvement. The split between passwd and group was a historical evolution that added multi-group membership. Together, these three files define the complete user and group identity model on a standard Linux system.

/* Quick permissions check on all three files */
$ ls -l /etc/passwd /etc/shadow /etc/group
-rw-r--r-- 1 root root   2345 May 10 /etc/passwd   (world-readable)
---------- 1 root shadow  987 May 10 /etc/shadow   (only root readable)
-rw-r--r-- 1 root root    654 May 10 /etc/group    (world-readable)
Continue to Part 3

Next, we look at the C library functions that let your programs read user and group information — getpwnam, getpwuid, getgrnam, getpwent, and more.

Part 3: Library Functions Back to Part 1

Leave a Reply

Your email address will not be published. Required fields are marked *