做运维这些年,你一定遇到过这种尴尬局面:一台服务器半年没登录,密码早就忘到九霄云外;或者同事离职前交接文档里写的密码,试了十次都进不去。最要命的是,这台机器上还跑着别人家的业务,不能随便重启,也没有现成的安装光盘。折腾过几次之后,我慢慢摸出一套“老运维私藏”的思路——把Linux的用户账户体系当成一个数据库来操作,用最底层的方式找回密码。
这个系列的第二篇,我会重点讲清楚密码到底存在哪、为什么改个文件就能重置密码、以及各种常见和进阶的实操手段。文章不会只丢命令,还会把背后的原理、适用场景、坑点一次性讲透。如果你手头负责几台Linux服务器,或者正在备考相关认证,这篇文章可以当操作手册来用。
1. 为什么“密码找回”会和数据库操作扯上关系
很多人一听“数据库操作”,第一反应就是MySQL、PostgreSQL这类业务库。但在Linux系统里,“用户密码”这件事本身就是一套标准的数据管理流程。系统把用户信息、密码哈希、密码有效期这些数据,分别存放在若干文本文件里,再用一组专用命令去修改和查询。这本质上就是一张“用户表”,只不过这张表的存储引擎是文本文件,查询语言是命令行的各种工具。
理解这个思路之后,密码找回就不再是玄学:你只需要拥有修改这些文件的权限,或者绕过系统正常登录流程进入一个能操作文件系统的环境,就能把密码“更新”掉。换句话说,重置密码等于在操作系统的账号数据库里执行了一次UPDATE语句,只不过这条语句的SQL方言是passwd、chpasswd这些工具。
这套方案相比“重装系统”“找厂商远程援助”最大的优势是快和可控。熟练的情况下,从开机到重置完成通常控制在三到五分钟内,业务分区和数据分区完全不动,只改认证信息相关文件,影响面极小。只要不碰/home、/var等业务数据目录,数据结构就是完整的。对只跑着内部测试服务、或者迫切需要救急的机器来说,这是性价比最高的处理方式。
2. 密码到底藏在哪里:Linux用户账户数据库解析
要把“密码找回”讲明白,必须先搞清楚Linux到底把密码放了哪些地方。早年几乎所有信息都堆在/etc/passwd里,任何人可读。后来为了安全,把密码哈希挪到了/etc/shadow,只有root和shadow组能读取。这就是最核心的两张“表”。
2.1 /etc/shadow 是怎么工作的
/etc/shadow的每一行对应一个用户,字段用冒号分隔,典型内容长这样:
user1:$6$salt$hashvalue:18000:0:99999:7:::- 第1字段是用户名
- 第2字段是密码哈希,如果为!或*,表示该账号被锁定或不能直接登录
- 第3字段是最后一次修改密码的日期,用1970年1月1日以来的天数表示
- 第4字段是密码最少使用天数,0表示随时可改
- 第5字段是密码最长使用天数,99999表示几乎不强制过期
- 第6字段是过期前多少天提醒
- 第7字段是过期后多少天禁用账号
- 第8字段是账号失效日期
- 第9字段保留
只要把第2字段替换成一个新生成的哈希,再满足权限要求,就能实现密码重置。这和你直接执行passwd命令的效果完全一致,区别只是绕过了一大堆交互和检查逻辑。生产环境里执行passwd最稳,但故障场景下直接编辑文件可能更快。
2.2 密码哈希算法的演进与识别
shadow文件里的哈希串开头就能看出算法类型:$1$是MD5,$2y$或$2a$是Blowfish,$5$是SHA-256,$6$是SHA-512,$y$是yescrypt,$argon2id$是Argon2。不同发行版默认算法不完全一样,CentOS 7之后基本是SHA-512,Debian 12、Ubuntu 22.04之后慢慢过渡到yescrypt。
这个识别点很重要。你如果在一台老系统上用新工具生成哈希,算法可能不兼容,导致写进去之后无法登录。反过来,老工具生成的弱哈希放到新版系统上虽然能认,但可能触发PAM模块的安全策略拒绝登录。稳妥的姿势是:在目标系统上用目标系统自带的工具生成哈希,别跨系统搬哈希。
2.3 /etc/passwd 和 /etc/group 的角色
/etc/passwd保存用户名、UID、GID、家目录、默认Shell等基础信息。密码重置一般不需要改动这张表,但有一种例外:如果你想临时绕过shadow里的密码限制,可以把passwd里对应用户的密码字段从x改成其他值,这样系统就会忽略shadow认证。这是早期系统遗留下来的行为,现在几乎没人这么干,因为太不安全了。遇到老文档这么写,要留个心眼。
/etc/group负责用户组信息。重置密码时不涉及,但如果你通过“找个不用密码的登录入口”来进入系统,得确认自己所在的附加组是否有sudo权限。否则进入系统后提不了权,还是改不了密码,这就尴尬了。
3. 常规实操:不依赖安装盘的密码重置方案
如果服务器就在手边,能重启,那么下面几套方案基本通用。根据发行版和引导环境的不同,选择合适的一条路就行。
3.1 单用户模式重置密码
单用户模式是Linux提供的最小运行环境,只挂载根文件系统,运行最少服务,默认以root身份进入。这是最经典的密码找回方案。
操作流程大致如下:
- 重启服务器,在GRUB菜单出现时按方向键暂停自动启动
- 找到默认的内核启动行,按e进入编辑模式
- 在以linux或linux16开头的行末尾,添加
s single或single(部分新版系统用1作为运行级别参数) - 按Ctrl+X或F10启动,系统会直接进入单用户Shell
- 执行
passwd root重置root密码 - 执行
exec /sbin/init或直接reboot
注意,RHEL/CentOS 7之后的systemd时代,传统单用户模式有些变化。有的发行版进入后网络、本地化服务不加载,文件系统可能是只读的。如果执行passwd时报“Permission denied”或者提示文件系统只读,先执行mount -o remount,rw /把根分区重新挂载为可写。
我用这个方法在一次救急中成功把一台CentOS 6的服务器从“谁也登不进”的状态里捞了回来。那台机器是某个内部报表系统,密码存在某人脑子里,但那人离职了。整个过程不到四分钟,业务数据完全没受影响。
3.2 通过 systemd 内核参数直接进入救援环境
新版系统推荐的做法,是给内核传参rd.break,这个参数会中断initramfs流程,在真正切换根目录之前弹出一个shell。此时实际的系统根分区还没有挂载,你现在所在的文件系统是临时的内存盘。
典型步骤如下:
- 重启,GRUB菜单按e编辑启动项
- 在linux行末尾添加
rd.break - 启动后出现shell提示符
- 此时根目录被挂载在/sysroot下,执行
mount -o remount,rw /sysroot - 执行
chroot /sysroot进入真实系统环境 - 执行
passwd root重置密码 - 执行
touch /.autorelabel(针对启用SELinux的系统,下节细说) - exit退出chroot,再exit退出救援Shell,系统会继续引导
这套方法适用面很广,尤其适合新版RHEL、CentOS、Fedora系列。它比单用户模式多一道chroot动作,但只要理解了“当前Shell不等于真实系统Shell”这一点,就不会迷路。
3.3 从安装介质 chroot 重置密码
当服务器连GRUB都坏了,或者引导分区被误删,上面的方案都会失效。这时候需要一张原版安装光盘或U盘,进入救援模式,把硬盘上的系统挂载起来再改密码。
以常见的安装介质方式为例:
- 用安装盘启动,进入启动菜单
- 选择Troubleshooting或Rescue a system
- 选择语言和键盘布局
- 系统会自动检测已安装的Linux,并挂载到
/mnt/sysimage之类的目录 - 如果没自动挂载,手动执行
mount /dev/你的根分区 /mnt/sysimage - 执行
chroot /mnt/sysimage - 执行
passwd root - 退出并重启
这套思路的本质,是把一个“外部系统”借进来,操作坏了密码的那块磁盘。就像你进不了家门,但可以请锁匠从外面把锁芯换掉。胜率最高,只要磁盘没物理坏,基本都能救回来。
3.4 几个方案的适用场景对比
| 方案 | 适用场景 | 依赖条件 | 操作复杂度 | 风险等级 |
|---|---|---|---|---|
| 单用户模式 | GRUB完好、老系统 | 物理/远程控制台可操作重启 | 低 | 低 |
| rd.break | systemd版本系统 | 同上 | 低 | 低 |
| 安装介质救援 | GRUB损坏、系统无法引导 | 有安装盘或镜像 | 中 | 中 |
| 直接编辑shadow文件 | 已有root壳或应急Shell | 能读写根分区 | 中 | 高,需谨慎 |
优先级建议:能进GRUB就优先单用户或rd.break,进不去再用安装盘。别一上来就拆机器硬盘挂到别的机器上,那是最后手段。
4. 深入:直接操作账号“数据库”的进阶场景
救急场景下能进单用户模式就够用了。但有些时候,你以为重置的是root密码,实际要解决的是“某个普通用户密码失效但root也被锁了”“root不能用SSH登录”“密码过期但记住的是旧密码”这类复合问题。这些情况就需要直接操作shadow文件,把“数据库语言”用起来。
4.1 用 chpasswd 批量更新用户密码
chpasswd是批量修改密码的好工具,它从标准输入读取用户名:新密码格式的内容,逐行更新。比逐个执行passwd更高效,适合帮多个账号统一改初始密码的场景。
在救援环境下可以这样写:
echo "root:YourNewPass123" | chpasswd也可以用-e参数直接传入已加密的哈希,适合你想把某个已知哈希直接写入账户的场景:
echo "user1:$6$xxxxxxx" | chpasswd -e这里有个很容易踩的坑:chpasswd默认会把传入的字符串当作明文密码来处理。如果你想写入的是哈希串,没加-e的情况下,系统会把那一整串当成新密码重新哈希,那shadow里就会变成一个双重哈希的值,用户永远无法登录。不少新手在复制网上的命令时,就栽在这一步。
4.2 用 Python 脚本直接解析 shadow 文件
用脚本的好处是自动化。比如你要给几十台服务器批量重置密码,手动一台一台操作太费时间,脚本可以统一处理。
import hashlib import secrets # 生成随机盐 salt = secrets.token_hex(16) # 使用 SHA-512 哈希(与常见 Linux 系统兼容) password_hash = hashlib.sha512( (salt + "your_plain_password").encode() ).hexdigest() print(f"$6${salt}${password_hash}")但注意,这不等于你能直接把这个字符串塞进shadow。生产系统中shadow里的$6$哈希使用的是crypt格式,不只是简单的sha512(salt+password),还涉及多轮迭代。Python标准库里的crypt模块在Python 3.13后已被移除,如果你用新版本Python又不想引入额外依赖,直接用操作系统的openssl passwd -6或者mkpasswd更省心:
openssl passwd -6 -salt mysalt YourNewPass123生成的那一长串,就是可以写入shadow第2字段的合法哈希。这比手搓Python靠谱得多。
4.3 强制用户在下次登录时改密码
有些场景下,管理员重置完密码后不希望长期使用这个密码,而是让用户首次登录就改成自己的密码。这可以通过shadow文件的有效期字段实现。
操作上,先用passwd把密码重置成临时值,然后执行:
chage -d 0 username这条命令会把shadow第三个字段(最后一次修改日期)设为0,PAM模块检测到后就会强制用户在下一次登录时修改密码。这在批量开账号、员工入职场景里非常实用。
4.4 锁定与解锁的“状态位”
shadow第2字段开头的!符号表示锁状态。有时候用户反馈“密码正确但登录失败”,查看shadow发现字段开头有个感叹号,那就是账号被锁了。可以用usermod -U username解锁,或者直接编辑shadow去掉感叹号。
但反过来说,当你想临时“禁用一个账号但不想删掉它”,可以在哈希前加!实现快速锁定。这比删除用户安全,因为UID、属主信息都保留着,哪天要恢复也容易。对离职员工账号的处理,我一向建议先锁再观察,不要急着删,否则文件属主会变成一堆数字,后续清理成本极高。
5. 常见问题与排查技巧实录
操作多了就会遇到各种情况,我把这些年踩过的坑整理一下,基本上是“翻车高发区”。
5.1 SELinux 上下文问题
在启用SELinux的系统中,如果你通过chroot进入系统改了shadow文件,文件的安全上下文可能不会被正确设置,导致系统重启后SSH还能认证,但登录成功后进不了家目录;更有甚者,直接无法认证。
解决办法是在chroot环境里执行:
touch /.autorelabel这会触发下次启动时的自动重打标签流程。虽然会延长启动时间,但比登录后再排查上下文问题要省事得多。如果你觉得整个过程太久,可以只针对相关文件恢复上下文:
restorecon -v /etc/shadow /etc/passwd不过,在救援环境里restorecon不一定可用,所以/.autorelabel更保险。还有一点:某些拥有大量数据的服务器,autorelabel可能让重启变慢,遇到大型生产库要评估好维护窗口。
5.2 文件系统只读导致的失败
进入单用户模式或rd.break环境后,根文件系统经常以只读方式挂载。直接执行passwd会出现类似passwd: Authentication token manipulation error的提示,或者permission denied。这不是权限问题,而是文件系统层面就不能写。
处理方法很简单:
mount -o remount,rw /别急着去改shadow,先确认能写文件再操作。我习惯先touch /tmp/test探一下,能创建文件再继续,省得白忙活。
5.3 密码重置成功但登录失败
密码重置成功,重启后却登录失败,可能的原因有:
- 哈希算法不兼容,上面说过了,用目标系统自己的工具生成哈希
- PAM模块有额外策略,比如
pam_pwhistory强制不能使用历史密码,你重置的新密码太简单或和旧密码相似,直接被拒 - 账号本身被锁了,shadow第2字段以
!开头 - 用户被限制只能从某些终端登录,比如
/etc/security/access.conf里设置了黑白名单
排查路径是:先看shadow状态,再看PAM配置,最后看SSH配置。别一上来就怀疑密码没改对。
5.4 远程救援的变通方案
有些服务器在机房,没有物理控制台,只有带外管理卡(比如IPMI、iDRAC之类)。这类设备支持远程挂载ISO、远程KVM,操作方式和本地接显示器几乎一样。只要厂商文档里把远程控制台打开,你就能像坐在面前一样进入GRUB菜单,用前面几套方案操作。
这里有个容易被忽略的细节:远程KVM下,GRUB菜单的键盘输入可能存在时序问题。按e键要趁菜单刚出现时立刻操作,否则系统直接进默认启动项了。我习惯在开机自检阶段就开始连按方向键,确保GRUB菜单停住。
5.5 密码找回后的安全操作
密码重置完成并不是终点。如果这台机器是因为密码泄露或离职员工交接不清导致的锁定,建议按顺序做四件事:
- 重置密码后立即设置新密码复杂度,别用临时密码长期裸奔
- 检查SSH登录日志,确认有没有未知来源的登录记录
- 检查最近有没有新增的ssh authorized_keys文件
- 如果怀疑账户被入侵,还要排查计划任务和系统服务
这个步骤很少见诸文档,但实际工作中比密码重置本身更重要。密码丢了可以改,但系统被种了后门就得花更多时间清理。
6. 扩展思考:业务系统数据库里的“密码找回”
既然标题里有“数据库操作”,除了系统账户数据,我想把手伸到业务库的场景也聊聊。很多管理后台的用户密码也存在数据库里,比如MySQL或PostgreSQL的user表。如果管理员密码丢了,或者某个账号异常,怎么处理?
6.1 数据库中的密码哈希和系统库逻辑的异同
业务系统的做法通常是:应用层把密码用MD5、SHA-256加盐后再存入数据库。比如某常见框架的user表结构里,password字段存的就是哈希串。你只要执行:
UPDATE user SET password = MD5('newpass') WHERE username = 'admin';就能重置密码。前提是你知道业务系统用的是哪种哈希算法。
这里就要多说一句:搞清楚算法比会写SQL重要。有的系统存的是password_hash(PHP的函数),字符串格式类似$2y$10$...,那就不应该用MD5函数去生成,而要用系统支持的password_hash逻辑来生成。用对字符串格式,UPDATE后才能登录。
6.2 避免在生产库上直接UPDATE的教训
别直接在业务库上UPDATE。万一这个表里有触发器,或者应用端有额外的缓存,你需要考虑影响面。我见过一个案例:某团队直接在库里把超级管理员密码UPDATE成MD5值,结果应用登录模块做的是“先查出一行再在内存里写MD5”,和数据库里存储的格式对不上,导致超级管理员把所有人都锁在门外,最后只能回滚数据。
如果实在要操作,先备份:
mysqldump -uxxx -p db_name user > /tmp/user_table_bak.sql再有,改完密码后应该清一下应用的缓存配置。很多管理系统把用户信息扔在Redis、Memcached里,缓存不失效,改库也白改。这个细节在你救急的时候能少走很多弯路。
6.3 找回思路的“抽象层”
系统用户密码和业务库密码,表面上是两套技术栈,但底层逻辑高度一致:找到存放认证数据的地方,确认算法格式,用正确工具写入新值,最后验证。掌握了这套抽象,遇到任何“密码丢了的系统”都能快速定位。
对我个人来说,这套思维帮过大忙。有一回某内部系统用的是很老的论坛程序,数据库里用户密码是明文加盐保存,接口文档全丢了。我通过翻数据库表结构、看应用代码,把最后一个有管理员权限的账号重置了出来。整个过程没重装软件,也没找厂商,靠的就是这个“认证数据操作”的思路。
7. 一点实操心得
这些年处理过的密码找回次数不下几十次,最想提醒后来者的一句话是:先确认操作环境,再动手改文件。很多人一上来就急着编辑shadow,结果要么文件系统只读,要么没进chroot就在救援盘的文件系统里瞎改,改了个寂寞。
我的标准流程固定为:确认能否进GRUB、确认根分区挂载状态、进入真实或chroot环境、生成兼容哈希、执行重置、顺手检查SELinux标签、重启并验证登录。每一步都有明确的检查点,不会在一个环节上反复试探。
最后再分享一个细节:在执行passwd或写shadow之前,先把原shadow备份一份到/tmp,命令是cp /etc/shadow /tmp/shadow.bak。如果操作后出了幺蛾子,还能一键还原。这个习惯救过我一次,那次我在一台老系统上用了新版chpasswd,哈希算法不兼容差点把管理员密码彻底弄坏,靠备份直接恢复了原状。备份三秒钟,换来的是操作时心里有底。