有一次线上服务半夜报警,登录认证大面积失败。我 SSH 进去试了一下,密码明明是对的,却被拒绝。后来一行一行看 /etc/shadow,才发现某个系统账号的密码哈希前面不知什么时候多出了一个感叹号。一个符号让服务中断了十几分钟,也让我重新意识到:这个平时不太有人关注的文件,其实是整个 Linux 认证体系里最关键的一环。
简单说,/etc/shadow 就是 Linux 专门用来保存用户密码摘要和密码策略的影子文件。它解决了早期 /etc/passwd 人人可读带来的安全问题。这篇内容我会从文件来历、9 个字段、哈希格式、实操修改密码策略,到常见认证故障排查,完整过一遍。不管你是刚接触 Linux 的新手,还是每天都要碰服务器的运维,读完都能直接上手使用。
1. /etc/shadow 到底是什么?——先弄懂它的定位
1.1 shadow 文件与 passwd 文件的“分家”逻辑
要理解 /etc/shadow,得先回头看 /etc/passwd。早期 UNIX 系统把所有用户信息都写在 /etc/passwd 一个文件里,用户名、UID、GID、家目录、登录 shell,以及密码的加密结果,全部并列存放。问题在于,/etc/passwd 必须对所有用户开放读取,因为很多命令要靠它把数字 UID 转换成用户名,比如 ls -l 显示文件属主时,普通用户执行也需要读这个文件。
一个所有用户都能读的文件,里面却保存着每个账号的密码哈希,这就像把保险柜钥匙挂在门口。虽然保存的不是明文,但早期加密算法相对较弱,攻击者把哈希拖回去跑字典,很容易撞出弱口令。于是后来系统做了拆分:把密码哈希和密码策略信息单独放到 /etc/shadow 里,/etc/passwd 对应的位置只留一个 x 占位,表示“真正的密码不在这里”。
我们可以用门锁来类比:/etc/passwd 是门牌号,任何人都能知道某某住在几零几;/etc/shadow 是保险柜钥匙的保管记录,只有锁匠能看。普通命令查用户基本信息走 /etc/passwd 就够了,完全没有必要触碰密码哈希这类敏感数据。这个拆分的核心原则一直沿用到现在:最小权限,能不看就不看,能不看就不暴露。
1.2 权限模型:为什么默认连普通用户都读不了
默认情况下,/etc/shadow 的所有者是 root,属组是 shadow,权限是 640(rw-r-----)。部分发行版也可能是 600 甚至 000,但无论哪一种,核心原则都一样:只有 root 能读,只有 root 能写。属组设置成 shadow,是为了让少数需要读取密码信息的系统程序通过 group 方式获得读取权限,而不是把整个文件开放给所有人。
这里我要特别提醒一句,很多安全问题不是因为系统设计不行,而是有人手动把 /etc/shadow 的权限改成了 644 甚至 777。恢复默认权限的命令很简单:
chown root:shadow /etc/shadow chmod 640 /etc/shadow不过在部分系统上,shadow 组的名称可能是 root 或其他,执行前先看看 ls -l /etc/shadow 的输出,别凭记忆强行对号入座。除了 shadow 本身,同类的还有一个 /etc/gshadow,它是群组密码的影子文件,逻辑和 shadow 完全一样,稍后排查群组问题时也会用上。
2. 逐字段拆解 shadow 文件——9 列字段排序详解
2.1 字段总览:一行的 9 个位置分别放什么
打开 /etc/shadow,每一行代表一个用户账号,各个字段用冒号分隔。以 root 用户为例,常见的一行长这样:
root:$6$Abc123def...:19000:0:99999:7:::这一行里一共有 9 个字段,顺序和含义可以整理成一张速查表:
| 字段位置 | 含义 | 常见值示例 |
|---|---|---|
| 1 | 用户名 | root |
| 2 | 密码哈希或锁定标记 | $6$... 或 ! 或 * |
| 3 | 最后一次修改密码的日期 | 19000(自 1970-01-01 起的天数) |
| 4 | 两次修改密码的最短间隔天数 | 0 |
| 5 | 密码有效期上限天数 | 99999 |
| 6 | 密码过期前多少天开始警告 | 7 |
| 7 | 密码过期后多少天禁用账号 | 空或数字 |
| 8 | 账号失效日期 | 空或数字 |
| 9 | 保留字段 | 空 |
我个人习惯是先记住一句话:9 个字段、冒号分隔、密码在第 2 列。后面处理密码修改、账号锁定、登录失败等绝大多数问题,都围绕这一列展开。第 3 到第 8 列则控制账号的生命周期状态,平时用 chage 命令修改,不需要手动去编辑数字。
2.2 密码哈希串拆解:算法标识、盐值与哈希值
第 2 列是整个文件里最核心的内容。很多人第一次看到 $6$xxxxxxxx$yyyyy 这种字符串会发懵,其实拆开看非常清晰:第一个 $ 和第二个 $ 之间的数字表示加密算法,第二个 $ 和第三个 $ 之间的部分是盐值(salt),最后一段是哈希值。
常见的算法标识有这些:
| 标识 | 算法 | 建议 |
|---|---|---|
| $1$ | MD5 | 不建议,强度偏低 |
| $2a$ / $2y$ | Blowfish | 视场景选用 |
| $5$ | SHA-256 | 一般 |
| $6$ | SHA-512 | 很多系统的默认值 |
| $y$ | yescrypt | 新一代方案,部分发行版已默认 |
| $argon2id$ | Argon2id | 安全性更优,逐步普及 |
盐值的作用是防止相同密码产生相同哈希。假设两个用户都把密码设成 Hello123,不加盐的话哈希结果完全一样,攻击者破解一次等于同时破两个账号。加了随机盐值之后,即使密码一样,最终哈希也完全不同,攻击成本显著提高。这就像两个人设置了同样的行李箱密码,但每把锁里都加了不同的内衬,外部看起来就是两把完全不同的锁。
实际操作中,检查系统里有哪些账号还在用弱算法,可以用一条命令扫出来:
awk -F: '($2 ~ /^\$1\$/) {print $1 ": MD5 hash"}' /etc/shadow如果输出里出现了用户,说明这些账号还在使用 MD5,建议尽快安排改密,让系统重新生成更安全的哈希。
2.3 生命周期字段:过期时间与状态联动
第 3 到第 8 列都是和时间相关的字段,但它们的值不是常见日期格式,而是“从 1970-01-01 00:00:00 UTC 起算的天数”。系统内核用整数天数做比较更简单,不需要解析各种日期格式。比如第 3 列为 19000,大约对应 2022 年年初。
要在 Linux 里把这个数字换算成可读日期,可以这样:
date -d "1970-01-01 UTC + 19000 days" +%F反推今天是第几天,用这个:
echo $(( $(date -u +%s) / 86400 ))第 3 列是密码最后修改时间,passwd 修改密码后会自动更新。第 4 列是最短修改间隔,0 表示随时能改密码,7 就代表改完密码后 7 天内不能再改。第 5 列是密码有效期上限,99999 表示近似永不过期。第 6 列是过期前警告天数,第 7 列是密码过期后的宽限天数,超过后账号被禁用。第 8 列是账号失效日期,一旦到达,账号直接无法登录,和密码是否过期无关。
理解这些字段后,再看 chage 命令的输出就会非常通透。比如用户登录时提示 “password will expire”,大概率就是第 5、6 列在起作用。
3. 实操:新建用户、改策略,亲手读懂 shadow
3.1 用 useradd 新建用户观察字段变化
光看理论容易忘,我建议你在测试虚拟机里跟着操作一遍。先创建一个测试用户:
useradd -m testuser grep testuser /etc/shadow执行 useradd 后,testuser 会在 shadow 文件里出现一行,但第 2 列通常是 ! 或 *,表示密码尚未设置,或者账号当前不允许直接登录。如果这时候尝试 ssh 登录 testuser,会直接提示认证失败。
接着设置密码:
passwd testuser输入两遍密码后,再查看 shadow:
grep testuser /etc/shadow第 2 列就会变成一长串以 $ 开头的哈希字符串,第 3 列也会自动变成今天对应的天数。这个过程能很清楚地看到:useradd 只负责创建账号框架,真正“写入密码”的动作发生在 passwd 这一步,两者对 shadow 文件的更新内容不一样。
3.2 用 chage 调整密码策略,验证字段联动
chage 是专门管理密码生命周期字段的命令。比如想让 testuser 的密码 30 天内必须修改,修改后最短 5 天才能再次修改,提前 3 天开始警告,执行:
chage -M 30 -m 5 -W 3 testuser再查看 shadow,第 4、5、6 列就会分别变成 5、30、3。想查看完整策略,用:
chage -l testuserchage -l 会把数字日期转换成人类可读格式,方便确认。每次修改完策略,我都会习惯性去 shadow 里核对一眼,确认字段确实发生了变化,这样排查问题时心里有底。chage 命令还有一个常用操作:直接把账号失效日期设为指定日期:
chage -E 2026-12-31 testuser执行后,第 8 列会出现一个大数字日期。这个机制很适合给临时外包账号设置访问期限,到期自动失效,不需要人工去删。
3.3 手动编辑 shadow 的正确打开方式
虽然 chage 和 passwd 能解决绝大多数需求,但有时候确实需要手动改 shadow。比如想快速清除某个账号的密码哈希、手动加上锁定标记,或者复制其他机器上的用户配置。Linux 提供了专门的编辑命令 vipw 和 vipw -s,前者编辑 /etc/passwd,后者编辑 /etc/shadow。vipw 会先锁定文件,避免多方同时编辑冲突,保存后还会提示你检查格式。
如果你执意直接 vi /etc/shadow,我强烈建议先备份,再动手:
cp /etc/shadow /etc/shadow.bak vipw -s pwckpwck 会遍历所有账号,检查字段数量、用户名是否存在、家目录是否有效等。这里最容易踩的坑有两个:一是手动编辑时把冒号删掉或者多打了一个冒号,导致整行解析失败;二是复制粘贴哈希值时把末尾字符漏掉,用户密码当场失效。这些坑我自己都踩过,所以“改之前备份、改之后 pwck”这条底线从来没有破过。
提示:无论是在测试环境还是生产环境,修改 shadow 之前都要先备份。编辑完千万别直接退出终端,先开一个新窗口验证 root 用户还能不能登录,再关旧窗口。这样可以避免“改完文件把自己锁在门外”的尴尬。
4. 常见问题与排查技巧实录
4.1 密码明明没错,却登录失败?先看第 2 列是不是有 !
有一次线上服务一直报认证失败,我排查之后发现,/etc/shadow 里对应账号第 2 列的开头不知道什么时候多了一个感叹号。! 开头的哈希代表这个账号被锁定,即使密码正确,系统也会拒绝认证。产生感叹号的原因很多,可能是被安全策略自动禁用,也可能是管理员执行过 passwd -l。
快速找出所有被锁定的账号:
awk -F: '$2 ~ /^!/ {print $1, "locked"}' /etc/shadow如果要解锁:
passwd -u testuser解锁前最好先确认这个账号是正常业务账号,还是已经废弃的僵尸账号。很多时候这类小问题反而是系统里存在异常账号的信号,比如某个离职员工的账号还留在服务器上,应该直接删除而不是解锁。
4.2 root 密码忘记怎么办
这是运维群里最常被问的问题之一。如果你有物理机或虚拟机控制台权限,通常可以在引导界面进入急救模式,不同发行版叫法不同,比如单用户模式、rescue 模式、emergency mode。进入后,把根分区以可读写方式挂载,再执行 passwd root 重置密码。
关键细节在于,急救模式下根文件系统通常默认只读挂载,直接 passwd 会提示无法更新 shadow。需要先重新挂载为可读写:
mount -o remount,rw / passwd root有些发行版还需要先 chroot 到实际系统目录再执行 passwd。操作前确认系统有快照或备份,万一过程中弄坏文件还能回滚。这个问题本质上是应急流程,最好提前准备好操作手册,别等到半夜再说。
4.3 权限被改坏导致的异常
有时候为了临时排查问题,有人会把 /etc/shadow 的权限改成 644 甚至 777,以为只是“看一下”。这样一来,任何普通用户都能读取所有密码哈希,前面所有安全设计全部白费。系统在安全检查时也可能报错。
恢复权限的标准操作:
chown root:shadow /etc/shadow chmod 640 /etc/shadow不过正如前面说的,不同发行版的属组名可能有差异,先看 ls -l /etc/shadow 再定。权限问题本身不复杂,难的是发现它:建议把对 shadow 权限的检查加入到日常巡检脚本里。
4.4 空密码和异常账号排查
安全审计时,空密码账号是重点关注对象。第 2 列为空的账号,意味着可能不需要密码就能登录。现代发行版默认禁止空密码登录,但如果有人改过 PAM 配置,风险就会重新出现。检查命令:
awk -F: '($2 == "") {print $1, "no password"}' /etc/shadow另一个需要重点关注的是 UID 为 0 的账号。正常情况下 UID 0 只有 root,如果 /etc/passwd 里出现其他账号也是 0,说明系统里可能存在提权后门。检查命令:
awk -F: '$3 == 0 {print $1, $6}' /etc/passwd我见过有些攻击者会新增一个 UID 为 0 的隐藏账号来留后门,这种账号往往不依赖 shadow 文件本身,而是通过系统文件一致性问题绕过去,所以日常审计绝不能只盯着影子文件看,还要结合 passwd 一起排查。
5. 安全加固建议:从影子文件开始守住认证体系
5.1 密码算法与强度检查
/etc/shadow 的设计已经告诉我们:密码哈希是敏感数据。第一道防线是权限,第二道防线是密码哈希算法。如果发现系统里大量存在 $1$ 开头的 MD5 哈希,那说明密码强度体系已经落后于当前安全要求,需要安排用户改密。
在 Debian 系系统里,可以通过 /etc/pam.d/common-password 里的密码模块配置来指定哈希算法,常见做法是设置 sha512 或 yescrypt。RHEL 系系统通过 authconfig 或相关的系统安全配置来调整。现代系统里,我通常建议优先使用 yescrypt 或 Argon2id,它们对 GPU 暴力破解的抵抗能力更强。
判断某个用户的哈希算法:
grep '^testuser:' /etc/shadow | cut -d: -f2 | cut -d$ -f2输出的数字就是算法标识,可以和前面的速查表对照。 ### 5.2 建立密码生命周期策略 除了算法本身,密码生命周期管理同样重要。/etc/login.defs 里有几个核心参数:PASS_MAX_DAYS、PASS_MIN_DAYS、PASS_WARN_AGE,分别控制密码最大有效天数、最短修改间隔、过期前警告天数。默认值往往是 99999,等于不限制,这对生产环境并不合适。 更合理的方式是结合 login.defs 和 chage 命令双层设置。login.defs 负责创建新用户时的默认策略,chage 负责对已有账号逐个调整。比如把全局密码周期设置为 180 天: ```bash sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 180/' /etc/login.defs然后对重要账号做单独收紧。密码策略的本质是平衡安全性和易用性,过短容易引发用户反感,过长又会留下长期不变的弱口令风险,180 天是很多企业环境里比较常见的折中值。
5.3 定期审计与备份
最后分享我自己的一个巡检习惯:每隔两周做一次账号审计,重点检查三件事。第一,/etc/shadow 的权限是否是 640 或更严格;第二,有没有 UID 为 0 的异常账号;第三,第 2 列以 ! 或 * 开头的账号是否明确属于历史遗留,该清理的坚决清理。
备份方面,不要把 /etc/shadow 单独备份到本机同一个磁盘分区,否则磁盘损毁时备份也没有意义。我会在夜间任务里把 shadow、passwd、group 三个文件打包加密,传到独立的存储位置,保留最近 30 天版本。恢复的时候也要注意文件权限,别从备份解压出来之后把权限弄丢了。
我在实际工作中发现,很多所谓的安全事故并不是攻击手段多高明,而是最基础的文件权限、密码策略长期没人检查,日积月累变成了漏洞。/etc/shadow 是很小的一个文件,但它身后是整个系统的认证边界。抽出一下午时间,把账号和密码策略完整梳理一遍,比装十个安全软件更能让人睡得安稳。