1. 一张认证失败截图背后,口令到底存在哪
前阵子帮一个客户处理生产环境连接问题,现象很典型:应用从某一刻开始批量报 password authentication failed for user "app_user",业务方非常笃定地说“密码没人改过”。我第一反应不是去看应用连接串里的字符串,而是直接查了数据库里的用户密码存储状态。很多人习惯性地认为数据库用户口令就是个字符串,存在某个配置表里,改密码就是把字符串换掉。但如果真这么理解,遇到这类认证失败会非常被动,因为问题往往不在“密码对不对”,而在“密码以什么格式存着、客户端和服务器按什么协议对上号”。
围绕 KingbaseES(人大金仓关系型数据库,下文按业内的习惯简称 KES)做口令认证相关工作时,需要先建立起一个认知:数据库侧保存的从来就不是明文口令,而是经过特定算法处理后的口令摘要。而客户端发起的登录请求,也不是把密码直接扔给服务器比对字符串,而是走一套挑战-响应式握手流程。这套机制设计得比较巧妙,理解了它,排障效率会高很多。
1.1 为什么我先查 pg_shadow 而不是应用配置
排查认证失败的顺序,我一般坚持“先看库、再看配置、最后看网络链路”。因为应用配置里的连接串只能告诉你“客户端以为的密码”,而数据库侧真正生效的是“角色对应的口令摘要”。两者一旦不一致,表现就是密码明明没改,却死活连不上。
在 PostgreSQL 生态体系里,角色信息和口令摘要分别通过几个不同的视图和系统表暴露。KingbaseES 大体延续了这套体系,只是底层系统表的命名在不同版本、不同兼容模式下有差异(V8、V9 里有些表叫 sys_authid,同时也保留了 PG 风格的兼容视图)。所以我平时最常用的查询入口是两个视图:
sql复制-- 先确认角色本身是否存在、是否允许登录
SELECT rolname, rolcanlogin, rolvaliduntil
FROM pg_roles
WHERE rolname = 'app_user';
-- 再看口令摘要和过期时间
SELECT usename, passwd, valuntil
FROM pg_shadow
WHERE usename = 'app_user';
这里小小提醒一下:pg_roles 是所有人可见的视图,但出于安全考虑,不会把 passwd 字段直接暴露给普通用户;pg_shadow 则相当于带敏感字段的只读视图,只有具备相应权限的账号(通常是超级用户)才能看到口令摘要。实际在生产环境里,如果用普通运维账号查询时报权限不足,别觉得奇怪,这是刻意设计的。
拿到查询结果后,重点看 passwd 列的前缀和格式。一个常见的情况是:口令摘要以 md5 开头,说明这个角色当前的密码摘要还是老算法;如果以 SCRAM-SHA-256$ 开头,说明已经切换到了新机制;如果 passwd 是空的或者 ******** 这种脱敏占位,那要警惕账号可能处于异常状态。
1.2 pg_shadow、pg_authid、pg_roles 三张表的区别
很多 DBA 初看这几个视图时容易混淆,我把它们的定位整理成一张表:
| 视图/表 | 可见范围 | 主要字段 | 典型用途 |
|---|---|---|---|
| pg_roles | 所有用户可见 | rolname、rolcanlogin、rolvaliduntil、rolsuper 等 | 查看角色属性、权限、是否允许登录 |
| pg_shadow | 仅超级用户/特权用户 | usename、passwd、valuntil 等 | 查看真实口令摘要与过期时间 |
| 底层系统表(如 sys_authid / pg_authid) | 超级用户 | 角色属性+口令摘要 | 数据字典底层,普通排查不需要动 |
简单来说,pg_roles 是“对外能看的角色清单”,pg_shadow 是“带口令摘要的管理视图”,底层表则存储了物理数据。日常运维中查询口令状态,绝大多数场景停在 pg_shadow 这一层就够了,不要轻易去改底层表,尤其是口令字段,绕过正常语法直接 UPDATE 系统表很容易造成认证元数据不一致,甚至导致所有客户端都无法登录。
1.3 三种密文格式:md5 前缀、SCRAM-SHA-256 和空口令
在 KES 的认证存储中,口令摘要的格式不是只有一种。我实际见过的无非下面几类:
| passwd 字段形态 | 含义 | 安全强度 |
|---|---|---|
以 md5 开头,后面跟 32 位十六进制字符 |
基于 MD5 的口令摘要 | 较弱,建议升级 |
以 SCRAM-SHA-256$ 开头,后面包含迭代次数、盐、签名 |
基于 SCRAM-SHA-256 的存储 | 强,推荐 |
| 空字符串或者明显异常的短值 | 无口令或状态异常 | 不可用 |
这里有个细节值得展开:MD5 摘要并不是简单地对密码做一次哈希,而是把用户名也参与了计算。公式大致是:
code复制存储内容 = md5 + md5(明文密码 + 用户名)
为什么要拼上用户名?因为如果不加用户名,同一个密码在不同用户身上的摘要完全一样,一旦某个用户的摘要值泄露,攻击者就能在多个账号之间做交叉比对。另外,在挑战-响应过程中,服务器发送随机盐(salt)后,客户端计算响应值时也要基于“口令摘要”再做一次加盐摘要。也就是说,数据库存储时用用户名做了一次绑定,认证握手时又叠加了一次随机盐,双重防护避免哈希值被直接重放。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 口令握手流程拆解:MD5 和 SCRAM 差在哪
很多刚接触数据库安全的人会有一个误区:以为“密码存储是加密的,所以登录时就是把加密后的密码发给服务器比对”。真实情况完全不是这样。口令摘要存储在服务器上,但客户端在认证握手过程中不会把存储的摘要直接传给服务器,那样等于把“保险箱钥匙的复制件”送了出去,网络监听者一旦截获,就能永久冒充登录。
KES 支持的认证方式很多,但从口令认证角度看,常见的无非四种:trust(完全信任,不需要密码)、password(明文传输)、md5(老式挑战-响应)、scram-sha-256(现代挑战-响应)。后两者才是真正需要深度理解的。
2.1 MD5 challenge-response 协议的两次交互
MD5 认证方式在 PostgreSQL 8.x 时代被广泛使用,KingbaseES 的早期版本和很多沿用 PG 习惯的迁移系统里也保留了这套机制。它的认证流程大致是这样的:
- 客户端发起连接请求,携带用户名和数据库名。
- 服务器根据
pg_hba.conf中该条规则的认证方法,决定采用md5方式,于是向客户端发送一个AuthenticationMD5Password消息,同时附带一个随机生成的 4 字节盐值。 - 客户端拿到盐值后,本地计算:
code复制阶段一 = md5(明文密码 + 用户名)
阶段二 = md5(阶段一 + 随机盐)
把阶段二的结果作为响应值发回服务器。
- 服务器根据自己存储的口令摘要(也就是阶段一的内容),结合刚才下发的盐值做同样的计算,如果结果一致,认证通过。
这套机制比明文传输进步的地方在于:网络上不会直接出现密码明文,也不会出现数据库里存储的那个完整摘要。即使有人抓包拿到了响应值,由于盐值是随机的,不能直接拿这段响应去另一台服务器上重放。
但 MD5 的问题也很突出。一方面,数据库存储的口令摘要本身没有“真正意义上的随机盐”,攻击者如果拿到了数据库备份或者系统表内容,可以做离线的字典爆破,因为 MD5 计算速度太快,弱口令几乎瞬间被破解;另一方面,MD5 本身已经被学术界和工业界判定为不安全,继续使用它属于历史包袱。
2.2 SCRAM-SHA-256 的挑战与证明流程
SCRAM-SHA-256(Salted Challenge Response Authentication Mechanism)是更现代的替代方案,也是目前 KES 新版本和 PostgreSQL 高版本主推的口令认证机制。它和 MD5 挑战-响应最大的区别在于:整个过程中服务器不会保存任何可以直接用于验证的“明文等价物”,而是保存一对经过 PBKDF2 派生的密钥材料。
先从存储格式说起。当 KES 使用 SCRAM-SHA-256 保存用户口令时,系统表里的摘要结构形如:
code复制SCRAM-SHA-256$4096:随机盐$StoredKey:ServerKey
大概拆解如下:
| 组成 | 作用 |
|---|---|
| 迭代次数(如 4096) | PBKDF2 的迭代轮数,加大离线破解成本 |
| 随机盐 | 每个用户每次设置密码时生成的随机值,避免相同密码得到相同摘要 |
| StoredKey | 服务器用于验证客户端证明的关键材料 |
| ServerKey | 服务器用于向客户端证明自己身份的关键材料 |
认证时流程比 MD5 略复杂,可以粗分为几步:
- 客户端发起认证,双方协商机制为 SCRAM-SHA-256。
- 服务器发送“服务器优先消息”,包含盐值和迭代次数。
- 客户端用用户输入的密码,结合盐值和迭代次数,通过 PBKDF2 派生出 SaltedPassword,再进一步生成 ClientKey、StoredKey、ServerKey 等派生密钥。
- 客户端构造一个“证明”(ClientProof),发送给服务器。
- 服务器用自己存储的 StoredKey 验证这个证明;同时服务器也要构造一个“服务器签名”(ServerSignature)返回给客户端,客户端用 ServerKey 验证服务器确实持有正确密钥材料。
换句话说,SCRAM 是双向认证,不仅服务器要验证客户端知道密码,客户端也要验证服务器没有拿假的密码库来钓鱼。
2.3 不要把“加密存储”和“认证协议”混为一谈
在日常沟通里,我发现一个特别容易绊倒人的概念混淆:把“口令摘要”叫成“密码加密后的密文”。严格来说,MD5 和 SCRAM 用的都是哈希/密钥派生算法,不是可逆加密。明文密码一旦经过这些算法处理,理论上就无法还原出原始字符串。服务器保存的是验证材料,而不是能解密回明文的密文。
这个区别在安全评估时很重要。比如客户等保测评要求“密码不得明文存储”,很多厂商汇报时只说“我们库里的密码不是明文”,但这远远不够。更严谨的做法是看存储的材料是否具备抗离线爆破能力。如果还是 MD5 摘要,弱口令被拖库后基本等于裸奔;如果升级到 SCRAM-SHA-256,即便系统表泄露,攻击者要破解一个强度尚可的密码,成本也会高好几个数量级。
所以我的建议很直白:新环境一律使用 scram-sha-256,老环境把存量用户的 md5 摘要想办法刷新成 SCRAM 格式。具体怎么做,下面一节详细说。
3. 维护口令的常见操作与安全策略落地
理解了理论,接下来落到实际操作。这部分我挑几个平时被问得最多、也最踩坑的场景展开。
3.1 ALTER USER 修改口令时发生了什么
DBA 最常用的口令维护语句无非是:
sql复制ALTER USER app_user WITH PASSWORD 'NewStrongPass_2026';
这条语句看起来简单,但背后有一个容易被忽略的环节:新口令被保存成什么格式,取决于当前会话的 password_encryption 参数。
sql复制SHOW password_encryption;
在 KES 的新版本里,这个参数默认值一般是 scram-sha-256,所以执行上面的 ALTER USER 后,pg_shadow.passwd 里会写入一条以 SCRAM-SHA-256$ 开头的摘要。如果某个老实例的参数值是 md5,那同样的语句生成的还是 MD5 格式。
这带来一个工程问题:当你准备把整个实例从 MD5 迁移到 SCRAM 时,光改 pg_hba.conf 是不够的。hba 里的 method 只决定“客户端和服务器之间用什么协议握手”,而每个用户口令摘要的格式,必须通过重新设置密码才能刷新。也就是说:
- 先确认
password_encryption已经是scram-sha-256。 - 再对存量用户执行重新设置密码(哪怕是设置成原密码,也会按新算法重新生成摘要)。
- 最后调整
pg_hba.conf的认证方法并 reload。
顺序不能反。如果先把 hba 全改成 scram-sha-256,而用户口令摘要还都是老的 md5 格式,所有存量账号都会立刻无法登录。
3.2 切换加密算法:password_encryption 参数与重启陷阱
我遇到过不止一次这样的情况:客户想把认证方式从 MD5 升级到 SCRAM,结果只改了 postgresql.conf 里的参数或 hba 文件,忘记重新设置用户密码。登录时日志反复报:
code复制FATAL: password authentication failed for user "xxx"
但业务方坚持密码没有输错。一查 pg_shadow,发现用户的 passwd 字段仍然以 md5 开头——服务器手里根本没有 SCRAM 格式的验证材料,自然无法与客户端完成 SCRAM 握手。
还有另一个隐蔽的坑:password_encryption 参数和 pg_hba.conf 里的 method 是两层概念。
| 层面 | 作用 | 生效时机 |
|---|---|---|
| password_encryption 参数 | 决定 ALTER USER/CREATE USER 时新口令的存储格式 | 执行 SQL 时 |
| pg_hba.conf 的 method | 决定 客户端登录时 使用哪种认证协议 | 连接建立时 |
如果你在会话里执行了:
sql复制SET password_encryption TO 'scram-sha-256';
这个设置只对当前会话有效,退出后参数就恢复成全局值。如果是在事务里执行了 ALTER USER,还要注意提交时机。而在集群多节点或主备架构下,更要确认修改是否同步到了所有节点。这里给一个实用的检查套路:
- 用
SHOW password_encryption;查看当前会话参数。 - 用
SELECT * FROM pg_hba_file_rules;查看 hba 规则解析结果(这个视图能直接看到每条规则的 method 是否生效)。 - 修改后执行
SELECT pg_reload_conf();让配置生效,不需要重启数据库。如果改完发现规则没变化,再检查是不是改错了实例或改错了文件路径。
3.3 有效期、复杂度、登录失败锁定这类外围能力
口令认证不只是“密码对不对”的问题,还有一批外围策略直接影响登录行为。
先说过期时间。KES 的角色有一个 valid until 属性,控制口令的有效截止时间。我常用它来约束临时账号或外包人员账号:
sql复制ALTER USER temp_auditor VALID UNTIL '2026-12-31 23:59:59';
查看哪些用户即将过期,可以用这条 SQL:
sql复制SELECT usename, valuntil
FROM pg_shadow
WHERE valuntil IS NOT NULL
AND valuntil < now() + interval '7 days'
ORDER BY valuntil;
再说口令复杂度。KES 在兼容 PG 的生态下可以通过扩展来检查密码强度,扩展名在不同版本里可能见到 passwordcheck 或 sys_passwordcheck 之类的差异,装之前先用 \dx 看看已经装了哪些扩展。启用后,创建或修改用户时如果设置的密码过于简单,数据库会直接拒绝。
最后是登录失败锁定。很多从 Oracle 迁过来的 DBA 习惯用 profile 管理尝试次数和锁定时间。KES 也提供了类似能力,但不同版本、不同兼容模式下 SQL 语法会有差异,而且默认未必开启。如果你发现某账号被锁,登录时提示账户状态异常或 locked,可以通过管理员账号解锁:
sql复制ALTER USER app_user ACCOUNT UNLOCK;
这种场景在暴力破解尝试后特别常见。建议在安全策略中明确:密码复杂度检查必须开,连续失败次数阈值根据业务容忍度设置,并且定期巡检被锁定的账号清单。
4. 生产环境认证失败的排查链路与迁移兼容
第二、三节讲的是正常工作的逻辑,这一节专门讲故障。生产环境遇到口令认证失败,不要一上来就怀疑密码被改,按照下面的链路排查会更快。
4.1 “password authentication failed” 的完整定位链路
我把标准排查过程总结为五步:
第一步:确认角色存在且允许登录。
sql复制SELECT rolname, rolcanlogin
FROM pg_roles
WHERE rolname = 'app_user';
如果输出为空,说明应用连接的账号根本不存在,常见于从测试环境同步脚本没执行成功。如果 rolcanlogin 为 false,说明该角色被设置成“不可登录”,也会报认证失败。
第二步:检查口令摘要的状态。
sql复制SELECT usename, passwd, valuntil
FROM pg_shadow
WHERE usename = 'app_user';
看三点:passwd 是否为空、valuntil 是否早于当前时间、前缀是 md5 还是 SCRAM。如果口令过期,客户端会得到类似口令过期的提示,或者干脆表现为认证失败。如果摘要前缀与 hba 方式不匹配,则大概率是机制协商问题。
第三步:确认 hba 规则和实际命中的认证方法。
sql复制SHOW hba_file;
SELECT * FROM pg_hba_file_rules;
pg_hba_file_rules 是排查 hba 问题最直观的视图,每一行对应一条规则,能看到数据库、用户、地址段和认证方法。注意 hba 规则是按顺序从上到下匹配的,第一条命中的规则生效,后面的规则即使更严格也不会执行。所以如果文件里有 host all all 0.0.0.0/0 md5 写在前面,后面再写 scram-sha-256 也不会对远程连接生效。
第四步:看数据库日志里的实际错误信息。
日志中如果出现类似:
code复制LOG: provided SASL mechanism(s) for user "app_user" do not match
这说明服务器和客户端之间可用的认证机制不匹配。假如服务器只允许 scram-sha-256,而客户端驱动只支持 md5(老版本 JDBC、某些中间件自带的连接池都可能这样),连接就会失败。
第五步:排除密码确实错误的情况。
在能够短时停业务或通过维护通道连接的前提下,可以用 psql 单独测试一次密码是否正确:
bash复制PGPASSWORD='NewStrongPass_2026' psql -h 127.0.0.1 -U app_user -d appdb -c "select 1;"
如果维护通道能通、业务通道失败,问题基本就锁定在 hba 规则、客户端驱动兼容性或者网络链路的 SSL 策略上了。
4.2 日志里的 clue:auth method 和 encryption 字段
数据库日志是排障时最该依赖的线索,而不是玄学式地猜密码。KES 的日志里通常会把连接认证过程中协商的机制打出来。比如:
code复制FATAL: password authentication failed for user "app_user"
DETAIL: Connection matched pg_hba.conf line 97: "host all app_user 10.0.0.0/8 scram-sha-256"
这条 DETAIL 极有价值,它直接告诉了你命中的是 hba 文件里的哪一行、用的什么认证方式。看到它,排查范围就缩小了。
还有一类错误,日志提示客户端发送的 SASL 机制列表里没有服务器需要的。这种情况常见于:驱动版本太老,只支持 MD5;或者 JDBC 连接串里显式指定了 authenticationPluginClassName,把机制限定死了。解决方案通常是升级驱动,或者在连接串中允许机制协商。
为了能获取到这些信息,生产环境建议打开连接日志相关参数。我一般会配置 log_connections = on,这样每次认证成功与否都会留下记录,万一出问题时有据可查。日志量大一点没关系,毕竟认证日志的量级比业务查询少得多。
4.3 从 Oracle/PostgreSQL 迁移时密码带来的特殊坑
数据库迁移是 KingbaseES 最常见的落地场景。很多人关注表结构、存储过程、SQL 语法兼容,却容易忽略用户口令体系,等业务系统一启动,第一波连接请求就全部失败。
从 Oracle 迁移时,Oracle 的用户口令散列算法和 KES 完全不是一套体系,直接搬运密码哈希没有任何意义。最稳妥的做法是:在割接窗口内让业务系统统一走“重置密码”流程,先把目标库的应用账号设置成统一初始密码,登录后要求修改,或者由配置中心统一下发新密码。Oracle 的 profile 策略、口令复杂度规则也需要在 KES 侧重新创建,不能指望迁移工具自动映射。
从 PostgreSQL 迁移到 KES 时,情况稍微好一点,因为两边都延续了 PG 的认证框架。如果源库的口令摘要本身就是 SCRAM-SHA-256,并且目标 KES 版本的算法兼容,密码摘要理论上可以直接带过去。但我个人的建议仍然是:割接后统一轮换一次口令。理由很简单:
- 你无法确认源库系统中是否已经有被记录下来的老旧口令摘要。
- 直接复用摘要意味着团队持有源库备份的人,理论上仍然掌握着登录新环境的口令验证材料。
- 安全合规审计时,很难解释为什么迁移后的密码和在旧系统里完全一致。
批量重置密码时,用 DO 匿名块在开发、测试环境里非常方便。下面给一个示例,把一批指定用户重置成同一个初始密码:
sql复制DO $$
DECLARE
v_user text;
BEGIN
FOR v_user IN SELECT unnest(ARRAY['app_user', 'report_user', 'etl_user'])
LOOP
EXECUTE format('ALTER USER %I WITH PASSWORD ''InitPass_2026''', v_user);
END LOOP;
END
$$;
生产环境不要用这种统一密码,建议通过密钥管理系统或配置中心生成随机密码,下发给应用,同时保留一次人工审计。
5. 我建议的安全基线配置
如果你负责的 KES 实例还没做口令认证方面的加固,可以参考下面的基线配置,它是我反复打磨过的一套比较稳的组合。
5.1 一套可以直接落地的 pg_hba 策略
先明确一个前提:完全不建议在远程连接上使用 trust 和 password(明文)。trust 意味着只要网络能到达,不需要密码就能登录,这在生产环境等同于裸奔;password 虽然会进行口令比对,但密码以明文形式在链路上传输,一旦被网络抓包就全完了。
下面是一个生产实例的基础配置范例:
code复制# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
host all postgres 127.0.0.1/32 scram-sha-256
host all postgres ::1/128 scram-sha-256
host all app_user 10.10.0.0/16 scram-sha-256
host all all 0.0.0.0/0 reject
逐行解释一下:
- 本地通过 Unix Socket 连接时,系统维护账号走
peer认证,也就是以操作系统用户身份识别。 - 本机回环地址允许超级用户用口令方式登录,但强制使用 scram-sha-256。
- 应用账号只允许来自内网网段,并且同样强制 scram-sha-256。
- 最后一行把其余来源全部拒掉,杜绝遗漏。
实际落地时,还要配合几件事:在 postgresql.conf 里确认 password_encryption = 'scram-sha-256';把 log_connections 打开;定期巡检账号过期情况。如果你的业务确实需要跨公网访问,不要只依赖口令认证,要把 SSL 强制打开,并且连接串里配置证书校验。
5.2 检查脚本与验证清单
配置完成后,不是看一眼 hba 文件就结束了,我建议按下面的清单做一轮验证:
bash复制# 1. 确认参数
ksql -c "SHOW password_encryption;"
# 2. 确认 hba 规则解析正常
ksql -c "SELECT line_number, type, database, user_name, address, auth_method FROM pg_hba_file_rules;"
# 3. 确认存量用户口令摘要格式
ksql -c "SELECT usename, left(passwd, 20) AS passwd_prefix FROM pg_shadow;"
# 4. 模拟一次远程连接认证
PGPASSWORD='CheckPass_2026' ksql -h <ip> -U app_user -d appdb -c "select 'auth ok';"
# 5. 故意输错密码,确认日志有记录且不会被误判为正常
PGPASSWORD='WrongPass' ksql -h <ip> -U app_user -d appdb -c "select 1;" || true
如果检查发现还有老用户的 passwd 前缀是 md5,说明他们还没有被重新设置过口令。此时不要简单地把 hba 改成 scram-sha-256 了事,而是要在变更窗口内对这些用户 ALTER USER 重新设置口令(哪怕暂设原密码),随后再切换 hba 认证方法。新版本 KES 也支持在 hba 里同时写多个认证方法吗?严格说不是这样:一条规则只能写一个 method。若想让存量 md5 用户和新 SCRAM 用户都能在过渡期登录,正确的做法是配置两条规则,一条限定 md5 用户来源、一条限定 scram 用户来源,或者把同网段的规则拆成两条分别指定 method。不过这类过渡配置建议尽快下线,长期维持多种认证方式只会让排查变得更加困难。
最后分享一个我个人的实践细节:在做 KES 版本升级或迁移前,先对全部业务账号做一次口令摘要格式盘点。用下面这条 SQL 把结果导出来,提前判断新版本的认证默认值是否会带来兼容性冲击:
sql复制SELECT usename
FROM pg_shadow
WHERE passwd LIKE 'md5%'
OR passwd IS NULL;
如果在待升级的实例上跑出来还有大量 MD5 摘要的账号,那升级方案里一定要包含“口令刷新”步骤,否则割接后大概率会重现前面说的“密码没改但全连不上”的惨案。
另外再补一句:给应用账号设置密码时,尽量绕开“在命令行直接输入明文密码”的操作习惯,尤其是在多人共用的跳板机上,历史命令一翻就全暴露了。可以用交互式 ksql 里的 \password 命令,或者通过标准输入传递密码,让口令尽量避免留在 shell history 里。口令安全的链路很长,数据库侧的认证机制只是其中一环,但对生产系统来说,这一环往往是最后一道防线。
