数据库安全第一关:用户密码存储与认证机制的深度拆解
从一个让人困惑的报错说起
几个月前,团队里的一位运维同事遇到了一个让他百思不得其解的问题:
"新建了一个用户,密码明明输对了,为什么就是连不上?"
他在KingbaseES里执行了CREATE USER app_user WITH PASSWORD 'App@2026',然后在客户端用这个用户名和密码去连接,结果反复提示认证失败。他确认了三次密码没有输错,确认了用户没有被锁定,确认了网络是通的------一切都正常,但就是连不上。
我让他执行了下面这条查询:
sql
SELECT rolname, rolpassword FROM sys_authid WHERE rolname = 'app_user';
然后又看了s alt="sys_hba.conf里的配置。问题很快就定位了:用户密码存储用的是默认的scram-sha-256算法,但s alt="sys_hba.conf里配置的认证方式是md5。存储算法和认证方法不匹配,导致虽然密码正确,但认证流程走不通。
这个问题让我意识到,很多数据库使用者对"密码怎么存"和"密码怎么验"这两个概念是混淆的。这篇文章就来彻底拆解一下这两套机制。
第一章:两个容易混淆的核心概念
1.1 存储 vs 认证:各管一摊
先看一张对比表:
| 维度 | 密码存储加密 | 口令认证 |
|---|---|---|
| 解决什么问题 | 数据文件泄露后,密码原文是否安全 | 网络传输时,口令会不会被窃取 |
| 控制参数 | password_encryption |
sys_hba.conf中的METHOD |
| 默认值 | scram-sha-256 |
scram-sha-256 |
| 什么时候起作用 | 创建/修改用户密码时 | 客户端连接数据库时 |
| 打个比方 | 把密码写进保险箱的方式 | 门卫核对身份的方式 |
这两个概念之所以容易被混淆,是因为它们确实有关联------认证方法必须兼容存储算法,否则就会像开头那位同事一样,密码正确却登录失败。但它们的职责是分离的:一个管"存",一个管"验"。
1.2 密码到底存在哪里?
所有用户的密码信息都存储在sys_authid系统表中:
sql
-- 查看用户密码存储信息
SELECT rolname, rolpassword, rolvaliduntil
FROM sys_authid
WHERE rolname IN ('system', 'fin_app');
输出示例:
ruby
rolname | rolpassword
-------------+---------------------------------------------------------------------
system | SCRAM-SHA-256$4096:SBfzuqMqQjEWP/w+n8TlwQ==$WeOQ2yOLEGvYp9v0DS5F...
fin_app | SCRAM-SHA-256$4096:kOPAYRYx2elBrNaeCo18BQ==$4xV0YdFPq7Uq2UvGqJ2H...
rolpassword这个字段存储的不是密码原文,而是经过哈希算法处理后的值。哈希算法是单向的------从哈希值无法还原出原始密码,这正是密码存储安全的基础。
1.3 准备测试用户
为方便后续演示,先创建几个测试用户:
sql
-- 创建演示数据库
CREATE DATABASE demodb;
-- 创建四个测试用户
CREATE USER fin_app WITH PASSWORD 'SecurePass!2026'; -- 金融应用
CREATE USER teaching_assistant WITH PASSWORD 'Kingbase_123'; -- 教学应用
CREATE USER legacy_mercury WITH PASSWORD 'Mercury@2026'; -- 遗留系统
CREATE USER gov_finance WITH PASSWORD 'Gov@2026'; -- 政务系统
这四个用户会贯穿全文,分别对应不同的存储算法和认证方式组合。
第二章:密码存储------数据文件里的安全防线
2.1 支持的存储算法
KingbaseES支持多种密码存储算法,通过password_encryption参数控制。
通用算法:
| 参数值 | 算法类型 | 存储格式示例 | 安全等级 |
|---|---|---|---|
scram-sha-256 |
SCRAM + SHA-256 | SCRAM-SHA-256$4096:盐$密钥 |
高 |
md5 |
MD5哈希 | md5 + 32位十六进制哈希 |
中 |
国密算法(V9R1+版本支持):
| 参数值 | 算法类型 | 存储格式示例 | 安全等级 |
|---|---|---|---|
sm3 |
国密SM3 | sm3 + 64位十六进制哈希 |
高 |
scram-sm3 |
SCRAM + SM3 | SCRAM-SM3$... |
高 |
2.2 查看当前用户的存储格式
sql
SELECT
rolname AS 用户名,
CASE
WHEN rolpassword IS NULL THEN '未设置密码'
WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'SCRAM-SHA-256'
WHEN rolpassword LIKE 'SCRAM-SM3$%' THEN 'SCRAM-SM3'
WHEN rolpassword LIKE 'md5%' THEN 'MD5'
WHEN rolpassword LIKE 'sm3%' THEN 'SM3'
ELSE '未知格式'
END AS 存储算法
FROM sys_authid
WHERE rolname IN ('fin_app', 'teaching_assistant', 'legacy_mercury', 'gov_finance');
默认情况下,所有用户的存储算法都是scram-sha-256。
2.3 修改存储算法(两步法)
这里有一个常见误区 :很多人以为执行了ALTER ROLE ... SET password_encryption = 'md5'就算改完了。实际上,这只改了参数,已存在的密码并不会自动转换格式。
正确的做法是两步:
sql
-- 第一步:为用户设置默认存储算法
ALTER ROLE legacy_mercury SET password_encryption = 'md5';
-- 第二步:重置密码(这一步不能少!)
-- 注意:当前会话必须先设置参数,再重置密码
SET password_encryption = 'md5';
ALTER USER legacy_mercury PASSWORD 'Mercury@2026';
如果不做第二步,密码仍然是旧的存储格式。这一点在运维中经常被忽略,导致明明改了配置,密码格式却没变。
2.4 参数优先级
password_encryption可以在多个层级设置,优先级从高到低:
- 会话级:
SET password_encryption = 'sm3'; - 用户级:
ALTER ROLE fin_app SET password_encryption = 'sm3'; - 数据库级:
ALTER DATABASE demodb SET password_encryption = 'scram-sm3'; - 实例级:
ALTER SYSTEM SET password_encryption = 'scram-sm3';
查看参数是否需要重启:
sql
SELECT name, context, pending_restart
FROM sys_settings
WHERE name = 'password_encryption';
context = user表示可以在各层级动态设置,pending_restart = f表示修改后无需重启数据库。
第三章:口令认证------连接时的身份核验
配置好密码存储后,还需要在sys_hba.conf中指定认证方法,客户端才能成功连接。
3.1 认证方法一览
| 认证方法 | 安全等级 | 说明 | 适用版本 |
|---|---|---|---|
scram-sha-256 |
高 | SCRAM挑战-响应,抗嗅探、抗重放 | 所有版本 |
scram-sm3 |
高 | SCRAM + SM3国密 | V9R1+ |
sm3 |
高 | 国密SM3哈希认证 | V9R1+ |
md5 |
中 | 兼容老客户端 | 所有版本 |
password |
极低 | 明文密码传输 | 仅限测试 |
3.2 sys_hba.conf配置示例
根据前面设置的存储算法,配置对应的认证方法:
sql
# TYPE DATABASE USER ADDRESS METHOD
# -----------------------------------------------------------------------
local all system peer
host all fin_app 192.168.126.0/24 scram-sha-256
host all teaching_assistant 192.168.126.0/24 scram-sha-256
host all legacy_mercury 192.168.126.0/24 md5
host all gov_finance 192.168.126.0/24 sm3
配置完成后,需要重载才能生效:
bash
sys_ctl reload -D $KINGBASE_DATA
或者从SQL中执行:
sql
SELECT sys_reload_conf();
3.3 验证当前生效的认证规则
sql
SELECT type, user_name, address, auth_method
FROM sys_hba_file_rules
WHERE user_name != '{system}'::text[];
3.4 两种认证流程的差异
SCRAM-SHA-256流程(安全):
客户端和服务器之间通过挑战-响应机制验证身份。网络传输的不是密码本身,也不是简单的哈希值,而是经过加盐、多次迭代计算后的证明。每次认证使用不同的随机数,可以防止重放攻击。
SM3流程(相对简单):
客户端直接计算密码的SM3哈希值发送给服务器验证。流程更简单,但缺乏挑战-响应机制,不具备抗重放能力。
这就是为什么国密合规场景中,更推荐使用scram-sm3而不是sm3------前者在满足国密要求的同时,提供了更高的安全性。
第四章:当存储遇上认证------兼容性测试
理解了存储和认证各自的原理,接下来看看它们如何协同工作------以及不匹配时会发生什么。
4.1 当前配置状态
经过前面的配置,四个测试用户的状态如下:
| 用户 | 存储算法 | 认证方法 | 预期结果 |
|---|---|---|---|
| fin_app | SCRAM-SHA-256 | scram-sha-256 | 成功 |
| teaching_assistant | SCRAM-SHA-256 | scram-sha-256 | 成功 |
| legacy_mercury | md5 | md5 | 成功 |
| gov_finance | sm3 | sm3 | 成功 |
4.2 测试一:全部改用MD5认证
临时修改sys_hba.conf,将所有用户的认证方法改为md5,重载配置后测试:
bash
ksql -U legacy_mercury -d demodb -h 192.168.126.16 # 成功
ksql -U fin_app -d demodb -h 192.168.126.16 # 成功
ksql -U gov_finance -d demodb -h 192.168.126.16 # 成功
结论:MD5认证方式兼容所有存储算法。但它安全性较低,只适合遗留系统过渡。
4.3 测试二:全部改用SCRAM-SHA-256认证
将所有用户的认证方法改为scram-sha-256:
bash
ksql -U legacy_mercury -d demodb -h 192.168.126.16 # 失败
ksql -U fin_app -d demodb -h 192.168.126.16 # 成功
ksql -U gov_finance -d demodb -h 192.168.126.16 # 失败
结论:SCRAM-SHA-256只兼容同族算法(即存储格式也是scram-sha-256的用户)。
4.4 测试三:全部改用SM3认证
将所有用户的认证方法改为sm3:
bash
ksql -U legacy_mercury -d demodb -h 192.168.126.16 # 失败
ksql -U fin_app -d demodb -h 192.168.126.16 # 失败
ksql -U gov_finance -d demodb -h 192.168.126.16 # 成功
结论:SM3认证只兼容SM3存储算法。
4.5 兼容性矩阵
| 认证方法 \ 存储算法 | MD5 | SCRAM-SHA-256 | SM3 |
|---|---|---|---|
| md5 | ✅ | ✅ | ✅ |
| scram-sha-256 | ❌ | ✅ | ❌ |
| sm3 | ❌ | ❌ | ✅ |
核心规律:
- MD5认证可以验证所有存储算法(但安全性低)
- 同族算法的存储和认证完全兼容
- 通用算法与国密算法互不兼容
第五章:实际运维中的场景与排查
5.1 场景一:创建新用户(标准流程)
sql
-- 第一步:确认当前默认算法
SHOW password_encryption;
-- 第二步:创建用户
CREATE USER new_fin_app WITH PASSWORD 'StrongPwd!2026';
-- 第三步:验证存储格式
SELECT rolname,
CASE WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'SCRAM-SHA-256'
WHEN rolpassword LIKE 'md5%' THEN 'MD5'
END AS algorithm
FROM sys_authid WHERE rolname = 'new_fin_app';
配套HBA配置:
host demodb new_fin_app 192.168.126.0/24 scram-sha-256
5.2 场景二:密码正确但认证失败(最常见的问题)
现象:用户报错认证失败,但密码确认无误。
排查步骤:
第一步,查存储格式:
sql
SELECT rolpassword FROM sys_authid WHERE rolname = '用户名';
看前缀判断是SCRAM-SHA-256、md5还是sm3。
第二步,查认证规则:
sql
SELECT auth_method FROM sys_hba_file_rules
WHERE user_name = '{用户名}'::text[];
第三步,对照兼容性矩阵判断:
- 同族 → 正常,检查其他原因
- 存储MD5 + 认证SCRAM → 需要重置密码升级
- 跨族(SCRAM vs SM3) → 需要统一算法
根因:存储算法与认证方法不匹配。
解决方案:二选一
- 方案A(推荐):重置密码,使用与认证方法匹配的存储算法
- 方案B(临时):修改HBA配置,使用兼容的认证方法
5.3 场景三:密码过期
现象:用户突然无法登录,提示密码过期。
排查:
sql
SELECT rolname, rolvaliduntil FROM sys_authid WHERE rolname = '用户名';
如果rolvaliduntil不为空且小于当前时间,说明密码已过期。
解决方案:
sql
ALTER USER 用户名 VALID UNTIL 'infinity';
-- 或者设置一个新的过期时间
ALTER USER 用户名 VALID UNTIL '2027-01-01 00:00:00';
5.4 场景四:新建用户仍是旧算法
现象 :明明改了password_encryption参数,新建的用户却还是MD5格式。
排查:
sql
-- 查看当前会话的参数值
SHOW password_encryption;
-- 检查是否有用户级覆盖
SELECT rolname, rolconfig FROM sys_authid WHERE rolname = '用户名';
根因:会话级或用户级的参数设置覆盖了全局配置。
解决方案:
sql
-- 重置当前会话的参数
RESET password_encryption;
-- 或重置用户级参数
ALTER ROLE 用户名 RESET password_encryption;
第六章:快速诊断工具箱
6.1 一键查看所有用户状态
sql
SELECT
rolname,
CASE
WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'SCRAM'
WHEN rolpassword LIKE 'md5%' THEN 'MD5'
WHEN rolpassword LIKE 'sm3%' THEN 'SM3'
WHEN rolpassword IS NULL THEN '未设置'
END AS 存储算法,
(SELECT auth_method FROM sys_hba_file_rules
WHERE user_name = ('{' || rolname || '}')::text[]
OR user_name = '{all}'::text[]
LIMIT 1) AS 认证方法,
rolvaliduntil AS 密码过期时间
FROM sys_authid
WHERE rolcanlogin = true
AND rolname NOT IN ('system', 'kingbase');
6.2 排查要点速查
| 故障现象 | 可能原因 | 关键检查 | 解决方案 |
|---|---|---|---|
| 密码正确但认证失败 | 存储与认证不匹配 | 查rolpassword和HBA | 重置密码或修改HBA |
| 新建用户仍是MD5 | 会话级参数覆盖 | SHOW password_encryption | RESET参数 |
| 提示密码过期 | 超过有效期 | 查rolvaliduntil | ALTER USER设置有效期 |
| 国密用户连不上 | 存储用SCRAM,认证用SM3 | 查存储格式和HBA | 重置密码为国密格式 |
写在最后
回到开头那个让人困惑的问题:密码正确为什么连不上?
答案其实不复杂:密码存储和口令认证是两套独立的机制。存储算法决定密码怎么存在数据文件里,认证方法决定客户端连接时怎么验证。两者必须兼容,否则就会认证失败。
理解了这个逻辑,排查这类问题时就有了清晰的思路:先查sys_authid看存储格式,再查sys_hba.conf看认证方法,然后对照兼容性矩阵判断问题所在。
数据库安全是一个系统工程,而用户认证是第一道关口。希望这篇文章能帮助你在日常运维中少踩一些坑。