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 差在哪
很多刚接触数据库安全的人会有一个误区:以为“密码存储是加密的,
