
这个问题被问得非常多而且几乎所有新手都猜错过觉得密码就存在/etc/passwd里。这个理解说对了一半但恰恰是那一半最容易出问题。我当年刚接触Linux时也这么想直到有一次想直接改密码文件提权结果把系统搞得无法登录才老老实实把整套机制啃了一遍。先说结论Linux用户的密码信息并不是集中存放在一个文件里而是由/etc/passwd和/etc/shadow两个文件配合存储。前者负责记录“用户是谁”后者才是密码哈希真正安家的地方。这篇文章把这两个文件从头到尾拆开讲包括每个字段的含义、密码哈希的前世今生、忘记密码后的恢复手段以及我在生产环境里踩过的那些坑。无论是刚入门的小白还是需要批量管理服务器的运维都能找到可以直接上手的东西。1. /etc/passwd并不存密码它负责的是用户身份档案很多人第一次打开/etc/passwd会特别失望因为它看起来实在太简单了。每行一个用户用冒号分成七个字段中间那个密码位清一色是一个x。如果你以为把x换成一段哈希就能改密码那就踩进大坑了。1.1 七个字段逐项拆解随便执行cat /etc/passwd你会看到类似这样的内容root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin tom:x:1000:1000:Tom Zhang:/home/tom:/bin/bash每一行都对应一个用户七个字段从左到右分别是用户名登录时使用的名称在整个系统中必须唯一。密码占位符几乎总是x它表示真正的密码哈希存放在/etc/shadow中。个别老系统上会直接显示哈希但那种配置在今天已经不推荐了。UID用户ID。内核识别用户靠的是UID而不是用户名0代表root。GID用户所属主组的ID。用户还可以加入其他附加组那些附加关系记录在/etc/group里。GECOS一段注释信息常见内容是用户全名、电话、办公室等很多系统把它当作用户的“备注栏”。finger命令会读取这个字段。家目录用户登录后的初始目录root通常是/root普通用户一般是/home/用户名。登录Shell用户登录后执行的Shell程序。如果这里是/usr/sbin/nologin或者/bin/false说明该账户被禁止交互登录这种账户通常用于跑服务。1.2 为什么passwd文件必须所有人可读/etc/passwd的权限是644也就是说所有用户都能读。为什么要把用户信息暴露给所有人看因为在Linux里很多操作都需要把用户名转换成UID或者反过来把UID显示成用户名。比如你执行ls -l系统要把文件的属主数字UID对应成名字这个过程就要读取/etc/passwd。如果这个文件只有root能读那普通用户执行ls都看不到文件属于谁整个系统会变得非常难用。所以/etc/passwd本质是一份“公开的用户目录”里面可以放名字、ID、家目录这些非敏感信息但密码哈希这种高度敏感的数据必须挪走。1.3 从历史看设计动机早期密码真的存在passwd里Unix刚诞生那会儿密码哈希确实直接写在/etc/passwd的第二个字段里。但问题来了既然所有用户都能读这个文件那哈希就等于公开了。虽然哈希算法有不可逆性但七几年的机器性能虽然差字典攻击却已经可行。尤其是用户喜欢用简单密码的时候攻击者把常见密码的哈希算好一比对就能还原出明文这种“预计算哈希表”的攻击方式后来演变成了彩虹表。于是后来的系统引入/etc/shadow把密码哈希从passwd中剥离出去存入一个仅root可读的文件。passwd里原来的哈希位统一换成x用来告诉系统“去shadow里找”。这样即使普通用户能看到passwd也拿不到密码哈希。这个改动算是Unix安全史上一个里程碑今天几乎所有Linux发行版都在用这套方案。2. /etc/shadow密码真正安家的地方/etc/shadow权限是600属主是root。普通用户即使能cat/etc/passwd也没权限读/etc/shadow。这个文件里每一行对应一个用户同样用冒号分隔一共九个字段。tom:$6$7KxnGEDx$Vf3hK...Qm0:18935:0:99999:7:30:19001:2.1 九个字段的作用第一个字段是用户名和/etc/passwd对应。第二个字段是密码哈希也是最核心的字段。第三个到第九个字段则是一套完整的密码生命周期管理规则密码哈希形如$6$salt$hash$6$表示使用SHA-512算法。如果是!或*开头代表这个账户没有可用密码或已被锁定。最后一次修改密码的时间从1970年1月1日到最近一次改密码日期的天数。注意这里的单位是“天”系统计算时会换算成当前天数做比较。如果你手动改成0那用户下次登录时系统会强制要求立即修改密码。密码最短使用期限改密码后至少经过多少天才允许再次修改0表示不限制。这个字段防止用户被要求改密时又立刻改回旧密码。密码最长使用期限多少天后密码过期必须修改。常见默认值是99999约等于273年相当于永不过期。密码过期警告期在密码即将过期前多少天开始提醒用户。如果你设置的过期天数是90警告期是7那用户在最后7天登录时会看到“密码即将过期”的提示。密码宽限期密码过期后仍允许登录的天数。宽限期内登录会强制要求改密码超过宽限期还没改账户就会被锁定为“密码过期”状态只能找管理员重置。账户过期时间也是从1970年起的天数到了这一天账户整体失效和密码是否过期无关管理员可以用它来给临时员工或测试账户设置“截止日期”。如果为空则表示永不过期。保留字段目前未使用留空即可。2.2 密码哈希的算法识别与转换/etc/shadow里的哈希字符串并不是一团乱码开头几位的特殊标记直接告诉系统用的是哪种算法。常见的识别方式如下前缀算法哈希长度常见场景$1$MD522字符老系统已不推荐$2a$$2b$$2y$bcrypt53字符BSD系、部分应用$5$SHA-25643字符中规中矩的选择$6$SHA-51286字符大多数现代Linux发行版默认$y$yescrypt较高新版Debian/Ubuntu等不同算法对CPU的消耗差异很大。MD5算得飞快GPU一秒能跑几亿次暴力破解的成本很低SHA-512比MD5慢一些但依然不够“扛揍”bcrypt和yescrypt这类算法刻意设计成计算慢、占用内存高专门用于对抗暴力破解。如果你的系统还停留在$1$这种老哈希上建议尽快升级密码算法。最简单的办法是让用户执行passwd重新设置密码新哈希就会按照/etc/login.defs里配置的算法生成。2.3 盐值的作用为什么同一密码哈希完全不同很多人问过我给两个用户设置同样的密码为什么他们在/etc/shadow里的哈希完全不一样答案就是“盐值”。在哈希前系统会随机生成一段字符串把它和密码拼在一起再计算哈希这段随机字符串就是盐值。哈希串里$分隔的第二部分就是盐值。这样做的好处是即使两个用户密码相同最终哈希也不一样攻击者不能直接复用已知密码的哈希来批量破解。一个精心随机生成的盐值同时还能防止彩虹表攻击——攻击者想预先把所有可能的“密码盐”组合算好几乎是不可能的。2.4 普通用户到底能不能看到shadow直接执行cat /etc/shadow普通用户会得到Permission denied。有些发行版还会开启/etc/shadow的强制访问控制或者audit审计规则一旦有非root进程尝试读取日志立即记录。但需要注意如果你给某个用户分配了sudo权限他完全可以sudo cat /etc/shadow。所以不要只看文件权限sudo规则本身也是密码安全的一部分。3. 密码从设置到校验的完整链路了解了文件本身下一步需要理解系统在背后做了什么。很多安全问题其实出在“知道文件在哪但不清楚调用链路上谁在起作用”。3.1 passwd命令改密码时系统做了什么当你执行passwd并输入两次新密码后系统并不是直接写一行哈希到/etc/shadow。它做的是这么一串事情读取/etc/passwd确认用户存在并确认UID、家目录等信息。调用PAM模块通常是pam_pwquality或pam_cracklib检查新密码是否满足强度要求比如最小长度、是否包含用户名、连续重复字符等。在密码哈希计算时生成随机盐值再用/etc/login.defs里指定的算法计算出新哈希。锁定/etc/shadow文件防止并发修改造成数据错乱。把新哈希、修改时间更新到对应行并写入密码老化策略的初始值。解锁文件。你手动往/etc/shadow里塞一段哈希绕过这些步骤就等于跳过了策略检查和文件锁保护。短时间改一个用户可能看不出问题但在批量修改或脚本自动化时并发写坏文件的概率会指数级上升。3.2 登录时系统如何验证密码登录时输入的密码并不是直接拿去和/etc/shadow里的哈希对比。系统先把输入密码和shadow里的盐值拼接起来用同样的算法重新计算哈希然后和存储的哈希做字符串比对。如果一致认证通过不一致返回认证失败。这也就是为什么改密码时盐值会变因为生成新哈希时重新随机生成了盐值而不是复用旧的。所以每次改完密码你在/etc/shadow里看到的哈希串都会变这是正常的不必担心。3.3 PAM与NSS在这里的角色两个经常被提起但容易被新手忽略的组件PAM插拔式认证模块和NSS名称服务开关。PAM负责“认证策略”。比如设置密码强度、限制root通过SSH登录、登录失败N次后锁账户这些都是通过PAM模块实现的。密码验证本身也走PAM的pam_unix模块它会读取/etc/shadow完成校验。NSS负责“用户信息查询”。系统通过NSS去决定“找用户信息从哪个数据库读”默认除了本地文件外还可以查询LDAP、NIS等外部目录服务。如果你在/etc/nsswitch.conf里配置了passwd: files ldap那系统会先查本地/etc/passwd查不到再去LDAP里找。同理如果配置了shadow: files ldap密码验证也可能去LDAP完成。这也是为什么有些环境下改了本地shadow却完全不生效——因为系统根本就没在用它。这几个环节少任何一个密码体系都会出问题。我遇到过一台机器所有用户都能登录但passwd命令报错最后排查是PAM配置里pam_unix.so被错误移除了认证还能走其他模块但改密功能彻底瘫痪。4. 实战忘记密码后的三种恢复路径讲完原理聊点实际用得上的。忘记密码这件事几乎每个运维都遇到过尤其是root密码。下面这三条路径都是我在不同环境下验证过的。4.1 单用户模式重置这是最传统的方式重启机器在GRUB启动界面按e编辑启动项。找到以linux或linux16开头的那一行在行尾加上single或数字1有的新系统也可以加init/bin/bash。按Ctrlx或F10启动进入单用户模式。执行passwd root输入新密码。如果系统启用了SELinux还需要执行touch /.autorelabel否则重启后SELinux上下文错乱可能导致 root 都无法登录。执行exec /sbin/init或直接重启。这个方法在CentOS 6以及之前的时代特别好用。但到了CentOS 7之后systemd接管了init单用户模式可能需要root密码或者直接drop到紧急模式操作方式有所变化下面要说的方法更通用。4.2 用rd.break和chroot重置root密码对于CentOS 7、RHEL 7以上以及新版Ubuntu Server我推荐用rd.break方法重启进入GRUB按e编辑启动项。找到linux开头的那行末尾追加rd.break。按Ctrlx启动系统会进入initramfs的紧急shell根文件系统此时是只读的。执行mount -o remount,rw /sysroot把真正的根重新挂载为可写。执行chroot /sysroot切换到真实系统环境。执行passwd root修改密码。如果是SELinux系统同样执行touch /.autorelabel或者改用以下命令更新SELinux策略chroot /sysroot touch /.autorelabel exit reboot这里最容易翻车的点是忘记重新挂载/sysroot为可写。我见过不少人在chroot后执行passwd半天没反应退出才发现根本就是只读状态白忙活一场。4.3 虚拟机场景下的等效操作结合很多人在虚拟机上练习的情况VMware或VirtualBox里跑Linux时上面两条路径完全适用。因为虚拟机能够通过控制台访问GRUB界面操作比物理机更顺手。如果你是普通用户忘记了自己的密码但知道root密码那直接sudo passwd 用户名就能重置如果root密码也忘了那就只能借助上述方法进入单用户模式或者紧急模式处理。有一类特殊场景是普通用户被锁定了root密码还在。这时passwd -u 用户名解锁即可。但如果是root被锁定那要进入系统后用passwd -u root解锁。这里要提醒一句千万不要轻易锁定root账户尤其是在没有配置sudo用户的情况下锁定root等于把自己关在门外。4.4 直接改文件的正确姿势有人会说既然密码在shadow里那我不如备份后直接改文件把root那一行的哈希删掉或者替换成已知哈希是不是也能达到重置目的思路没错但在生产环境我强烈不建议这么做。首先空密码字段两个冒号之间没有内容在大多数系统上意味着“无密码登录”这本身是一个极其危险的状态。其次直接编辑/etc/shadow会绕过PAM的复杂度和锁定策略容易留下安全隐患。最后如果你编辑器保存时改变了文件行尾或权限轻则登录失败重则其他依赖shadow的服务一起报错。如果必须用文件方式重置至少应该这样做先备份cp /etc/shadow /etc/shadow.bak。用vipw -s而不是vi /etc/shadow。vipw会对你做文件锁校验避免多人同时编辑造成数据错乱。修改后用pwck检查文件格式是否正确。改完立即登录测试确认没问题后再删除备份。5. 运维中常见的误区和坑这一节是我在多年实际运维中反复遇到的真实问题每一条都付过学费写出来帮大家少走弯路。5.1 用vi直接编辑shadow引发的文件锁问题有一次我在处理一批账号时图省事直接vi /etc/shadow编辑。改完保存后系统开始间歇性出现“认证失败”的报错。查了很久才发现我用vi保存文件时改变了inode而nscd或某些NSS模块还在引用旧的缓存导致部分服务读到的是修改前的文件句柄。正确的做法是使用vipw -s。它本质上还是调用编辑器但会做文件锁处理和语法检查保存后还能触发权限修正。同理修改/etc/passwd用vipw修改/etc/group用vigr。这几个命令会确保你修改时系统其他进程不会并发写入改完权限不变避免inode变化带来的各种奇怪问题。5.2 哈希算法不兼容导致的登录失败某些老系统或者特殊发行版登录程序的PAM模块支持算法范围有限。如果你把一台机器的shadow哈希直接复制到另一台机器而两台机器的认证模块配置差异很大就可能出现“用户存在但密码永远错误”的情况。我当年就遇到过从Debian 8复制用户数据到CentOS 7结果用户全部无法登录。排查之后发现Debian那边用的是$6$没错但系统里某个服务被限制只能用特定算法库。后来是让用户重新设置密码解决的不是改文件能解决的。所以跨系统迁移用户时最好采用标准方式useradd创建用户后用passwd设置密码或者用pwconv等工具规范同步不要直接粘贴shadow行。5.3 密码老化字段填错引发的“神秘锁定”shadow文件第3到第9个字段虽然不复杂但设置错误会带来很隐蔽的问题。比如你把“密码最短使用期限”设成了30结果用户改密后没到30天又想换密码系统会直接拒绝提示“密码未过最短期限”。再比如你把“账户过期时间”误填成昨天的日期第二天这个用户就再也登不上去了表现跟密码错误很像但报错信息又不一致。遇到这类问题第一步看/var/log/secure或/var/log/auth.log里面有具体的认证失败原因。第二步用chage -l 用户名查看当前的密码老化状态这个命令会清晰列出最短天数、最长天数、过期时间等所有字段比手敲awk解析shadow文件直观得多。5.4 passwd与shadow不一致导致账户异常正常情况下/etc/passwd和/etc/shadow中的用户列表应该一一对应。但如果出现手工删除或编辑不当可能造成passwd里有用户但shadow里没有该用户行。这时系统会认为该用户没有密码或密码存储缺失登录时直接认证失败。同步两个文件的命令是pwck它会扫描并报告不一致项出现无法理解的错误时先跑一遍准没错。反过来如果shadow里有行但在passwd里找不到对应用户那就是所谓的“孤立shadow条目”这类条目也应该清理掉。5.5 批量修改密码脚本里的通配符陷阱批量创建用户或批量重置密码时很多人会写个循环脚本echo password | passwd --stdin username这条命令在CentOS/RHEL上没问题但Ubuntu的passwd不支持--stdin。跨发行版时应该用chpasswdecho username:password | chpasswd还有一个容易被忽略的点脚本里密码包含$符号时如果没有转义或者用单引号包裹Shell会把它当成变量前缀最终写入的哈希是错的。我写脚本时都会用单引号包密码并且避免密码里带空格和单引号。另外批量创建用户时别只顾着设置密码记得同步设置老化策略chage -M 90 username否则新账户默认可能永不过期等安全审计时全部暴露出来。6. 安全加固给密码存储加几道锁文件路径和字段搞清楚了最后聊聊怎么把这套机制用得更安全。毕竟知道密码存在哪只是第一步别让它成为系统最薄弱的环节。6.1 shadow文件权限与检查首先确认/etc/shadow的权限是600属主是root。正常情况下发行版默认就是如此但有些运维为了方便会用chmod 644 /etc/shadow这是非常危险的操作。一旦普通用户能读shadow离线暴力破解的门槛就大幅降低。建议把以下检查加入日常巡检脚本stat -c %a %U %G /etc/shadow预期输出应该是600 root shadow。同时检查一下是否有可疑的sudo规则sudo -l和visudo -c是基础中的基础。6.2 密码复杂度和老化策略密码策略可以通过/etc/login.defs和PAM配置来约束。/etc/login.defs里常用的参数有PASS_MAX_DAYS密码最长使用天数。PASS_MIN_DAYS两次修改密码的最小间隔。PASS_WARN_AGE过期前多少天提醒。ENCRYPT_METHOD新密码使用的加密算法。PAM侧的强度控制更加细致。以pam_pwquality为例常见配置是设置最小长度和至少包含几类字符password requisite pam_pwquality.so try_first_pass local_users_only retry3 minlen12 dcredit-1 ucredit-1 lcredit-1 ocredit-1这条规则的意思是最小密码长度12并且至少有一个数字、一个大写字母、一个小写字母和一个特殊字符。参数前面的负号和数字代表“最少需要多少个”如果是正数则代表“最多允许几个”。6.3 锁定账户与禁止登录临时锁定的账户不必删除用passwd -l 用户名可以在shadow对应行的密码哈希前加上!前缀让该账户无法认证。解锁时用passwd -u 用户名去掉前缀即可。对于需要完全禁止登录的系统账户比如daemon、bin这类它们本来就没有登录Shell。还可以通过usermod --expiredate 1 用户名将账户过期时间设置为1970-01-02使账户立即失效。管理员要恢复时再设成空值。还有一个细节容易被忽略即使某账户的登录Shell是/usr/sbin/nologin依然可以通过sudo或su切换到该账户执行命令所以“禁止登录”和“禁止提权”是两件事别混为一谈。6.4 从日志发现异常认证行为密码安全的最终防线是日志与审计。推荐维护一个简单的检查习惯定期查看/var/log/secureCentOS/RHEL系或/var/log/auth.logDebian/Ubuntu系重点关注被拒认证来源IP、错误密码重复尝试次数、非工作时间段的登录记录。一条典型的失败登录日志长这样May 12 03:14:16 host sshd[1234]: Failed password for root from 192.168.1.100 port 51234 ssh2如果短时间内出现大量类似记录基本可以确定有人在扫你的SSH端口。配合fail2ban或云厂商的安全组策略做IP封禁能大幅提高暴力破解的成本。另外别忘了开启audit守护进程对/etc/shadow的访问记录auditctl -w /etc/shadow -p wa -k shadow_watch这样谁试图读或改这个文件都会留下审计痕迹。回到最开始那个问题Linux用户密码到底存在哪答案可以浓缩成一句话——/etc/passwd提供用户身份/etc/shadow保管密码哈希和老化策略PAM负责认证流程NSS决定数据来源。但知道这些文件的名字只是入门真正值钱的是理解每个字段的作用、每条命令背后改了什么、每个权限位保护的是什么。我在实际运维中最大的体会是不要直接动手改shadow不要图省事跳过校验命令不要跨系统盲目复制用户数据。这三条坑我都踩过每一次都花了远高于“老老实实操作”的时间去收拾残局。希望这篇文章能让你少走这些弯路。