你还在用 password 认证方式?PostgreSQL 安全认证的“隐形杀手”
很多 PostgreSQL 用户会认为,只要启用了 SSL/TLS,数据库连接就已经足够安全。
但这种认知并不完整。
SSL/TLS 负责保护数据传输过程,而真正决定登录阶段安全性的,是 PostgreSQL 所使用的认证方式。
在这篇文章中,我将带你 深入剖析,为什么 PostgreSQL 的 password 认证方式,实际上是一个 “隐形的安全杀手”,而 md5 和 scram 认证可以提供更强的防护。
01
理论篇:三种认证方式的底层
原理解析
这三种认证方式的核心差异,在于密码在网络通信中是如何传输的,以及如何对抗网络嗅探(Sniffing)、重放攻击(Replay Attack)和拖库攻击。
1
password 认证:在互联网上“裸奔”
工作原理
当客户端尝试连接数据库时,服务端要求输入密码。
客户端接收到用户输入的密码后,不加任何处理,直接以纯明文的形式(Plaintext)封装在 TCP 数据包中发给服务端。
安全致命点
任何位于同一局域网、路由器节点或通信链路上的攻击者,只要使用网络抓包工具(如 tcpdump、Wireshark),就能像看普通文本一样直接读取你的数据库密码。
2
md5 认证:经典的“挑战-响应”机制
为了解决明文传输的问题,PostgreSQL 引入了基于 MD5 的挑战-响应(Challenge-Response)机制。
工作原理
a) 挑战
客户端发起连接,服务端随机生成一个 4字节的盐(Salt) 发给客户端。
b)计算
客户端使用用户输入的密码、用户名以及服务端发来的盐,进行双重哈希计算:Hash = MD5( MD5(password + username) + salt )。
c)响应
客户端将计算出的最终 Hash 值发给服务端,服务端用相同规则比对。
为什么安全
密码的明文从未在网络上出现过。
攻击者抓包只能看到服务端的“盐”和客户端的“Hash响应”。
由于每次连接“盐”都是随机变化的,攻击者即使截获了这次的 Hash 值,也无法在下次连接时用来重放登录。
3
scram-sha-256 认证:现代密码学的终极
防线
虽然 MD5 能防网络嗅探,但 MD5 算法本身已不安全,且存在“哈希传递攻击(Pass-the-Hash)”的弱点(即:如果系统表 pg_authid 被盗,攻击者可以直接用里面存储的 MD5 值冒充客户端登录)。
因此,PG 10 引入了 SCRAM(Salted Challenge Response Authentication Mechanism,RFC 7677)。
工作原理
它是一种复杂的双向认证机制。
客户端和服务端会互相交换自己生成的随机数(Nonce),并通过 PBKDF2-SHA-256 算法进行成千上万次的迭代运算,最终生成复杂的 ClientProof(客户端证明)和 ServerSignature(服务端签名)
为什么最安全
- 极强的防嗅探能力
网络上传输的仅仅是基于随机数和迭代计算的动态证明。
- 防伪造服务器
不仅服务端验证客户端,客户端也能验证服务端(防止中间人伪造数据库节点)。
- 防拖库传递
即使数据库密码表泄露,攻击者也无法反推明文,更无法直接使用泄露的密文进行登录。
02
实战篇:全流程 tcpdump 抓包与
协议解析
为了让理论落地,我们设计了以下完整的实验流程。
我们将通过 tcpdump 深入 PostgreSQL 的通讯协议层(Wire Protocol),亲眼见证数据包里的秘密。
1
实验环境准备
我们需要开启两个终端窗口:终端 A 用于执行抓包,终端 B 用于模拟客户端登录。
- 测试用户
dbuser
- 测试密码
Secret@123
- 抓包命令
sudo tcpdump -i any -A -s 0 port 5432
- -i any
监听所有网卡接口。
- -A
核心参数,将数据包的 Payload 以 ASCII 明文形式打印,便于观察。
- -s 0
捕获完整的数据包,防止报文截断。
注意
为了确保抓包能看到认证协议,实验全程使用 PGSSLMODE=disable 环境变量强制关闭 SSL 链路加密,模拟纯明文通道下的认证过程。
2
实验 1:令人触目惊心的 password 抓包
Step 1: 修改配置并重载
在数据库服务器上,编辑 pg_hba.conf,将 IPv4 本地连接的认证方式改为 password:
# TYPE DATABASE USER ADDRESS METHOD
host all dbuser 0.0.0.0/0 passwor
执行重载命令生效:SELECT pg_reload_conf();
Step 2: 开启抓包
在 终端 A 执行:
sudo tcpdump -i any -A -s 0 port 5432
Step 3: 模拟客户端登录
在 终端 B 发起连接并输入密码 Secret@123:
PGSSLMODE=disable psql -U dbuser -h 127.0.0.1 -W
Step 4: 报文解析
回到终端 A,你会看到长串的 TCP 交互。
我们在其中精准定位到客户端发送密码的数据包:

深度剖析
注意最后一行 ....p...Secret@123.。在 PostgreSQL 协议中:
-
字母 p 代表这是一个 PasswordMessage。
-
紧接着的 4 个字节代表消息长度。
-
后面跟着的,就是毫无遮掩的纯明文密码 Secret@123,最后以 \0 结尾。
-
结论
在没有配置 SSL 的情况下使用 password 认证,任何能接触到网络流量的人都能瞬间盗取你的数据库最高权限。
3
实验 2:md5 是如何隐藏密码的?
Step 1: 修改配置与密码加密方式
修改 pg_hba.conf 的 METHOD 为 md5 并重载配置。
# TYPE DATABASE USER ADDRESS METHOD
host all dbuser 0.0.0.0/0 md5
同时,我们需要确保数据库以 md5 格式存储该用户的密码(PostgreSQL 14+ 默认是 scram,需回退演示):
ALTER SYSTEM SET password_encryption = 'md5';
SELECT pg_reload_conf();
\password dbuser -- 重新输入 Secret@123,使其以 md5 哈希存入系统表
Step 2: 开启抓包并登录
保持终端 A 的 tcpdump 运行,终端 B 再次执行登录:
PGSSLMODE=disable psql -U dbuser -h 127.0.0.1 -W
Step 3: 报文解析
这一次,我们在抓包结果中找到了两次关键交互(挑战与响应):
以下是一份真实的、原汁原味的 Linux 生产环境抓包日志。
我们将逐个数据包拆解 MD5 是如何隐藏密码的。
数据包 1:服务端发起 MD5 挑战(下发随机 Salt)
18:51:15.869475 IP localhost.postgres > localhost.61332: Flags [P.], seq 1:14, ack 81, win 128, options [nop,nop,TS val 159740625 ecr 159740623], length 13
E..A..@.@.f(.........8...R.o.s.......5.....
.r. .r.R........iJ..
分析
1)TCP 干扰项
开头的 .r. .r. 是底层 TCP 时间戳选项(TCP Timestamp Options)被强制转换为 ASCII 的结果,与数据库无关。
2)PG 协议载荷
真正的核心是最后的 R........iJ..(总长 13 字节)。
- R
代表消息类型为 AuthenticationRequest。
-
紧接着不可见的 4 字节代表认证类型,值为 5(MD5 认证)。
-
iJ..
这就是服务端这次随机生成的 4 字节盐(Salt)。
数据包 2:客户端发送 MD5 响应(Hash 值)
18:51:15.870407 IP localhost.61332 > localhost.postgres: Flags [P.], seq 81:122, ack 14, win 128, options [nop,nop,TS val 159740626 ecr 159740625], length 41
E..]')@.@..p...........8.s...R.|.....Q.....
.r. .r.p...(md585eba7f665f54bf44e628307c6d2c3f1.
逐字节拆解
这是 MD5 认证最核心的一步,PG 负载内容为 p...(md585eba7f665f54bf44e628307c6d2c3f1.:
1)p
代表消息类型 PasswordMessage。
2) ...(
表示消息长度。
有趣的是,括号 ( 在 ASCII 码表中的十进制正是 40。
加上开头的 p,总长度恰好是 41 字节。
3) md585eba7f66...
这是客户端使用公式 MD5(MD5(密码+用户名) + "iJ..") 计算出的响应值。
结论
我们在这个包里找不到任何明文密码。
黑客即便截获了这一串 md585eba... 也无法反推密码,且因为下一次登录服务端会下发全新的 Salt,这个旧哈希值无法用于重放攻击。
数据包 3:认证成功与参数下发(意外的安全启示)
18:51:15.872288 IP localhost.postgres > localhost.61332: Flags [P.], seq 14:467, ack 122, win 128, options [nop,nop,TS val 159740628 ecr 159740626], length 453
E.....@.@.do.........8...R.|.s.............
.r. .r.R........S....in_hot_standby.off.S....integer_datetimes.on.S....TimeZone.Asia/Shanghai.S....IntervalStyle.postgres.S... search_path."$user", public.S....is_superuser.off.S....application_name.psql.S...&default_transaction_read_only.off.S....scram_iterations.4096.S....DateStyle.ISO, MDY.S...#standard_conforming_strings.on.S...!session_authorization.dbuser.S....client_encoding.UTF8.S....server_version.18.3.S....server_encoding.UTF8.K.......&.r..Z....I
逐字节拆解
你可能会对这段突然冒出的大量“明文”感到疑惑。
这恰恰证明了MD5 认证已经通过!
1) R........
这里的 R 代表 AuthenticationOk(认证成功)。
2)大段的 S....
代表 ParameterStatus。
服务端开始向客户端下发明文会话参数,如 TimeZone.Asia/Shanghai、server_version.18.3、client_encoding.UTF8 等。
3)最后的 Z....I
代表 ReadyForQuery,状态为 I(Idle)。
至此,连接建立完成,可以敲 SQL 了。
3
实验 3:scram-sha-256 的硬核交互过程
虽然 MD5 防住了抓包,但 MD5 算法本身容易被碰撞破解,且如果黑客拿到了数据库的 pg_authid 系统表,可以直接用里面的 MD5 值冒充客户端登录。
我们来看 SCRAM 是怎么解决的。
Step 1: 修改配置与密码加密方式
修改 pg_hba.conf 的 METHOD 为 scram-sha-256 并重载。
# TYPE DATABASE USER ADDRESS METHOD
host all dbuser 0.0.0.0/0 scram-sha-256
同时,将会话加密方式切换为 scram 并重置密码:
ALTER SYSTEM SET password_encryption = 'scram-sha-256';
SELECT pg_reload_conf();
\password dbuser -- 重新输入 Secret@123,以高强度 SCRAM 格式存储
Step 2: 开启抓包并登录
执行抓包和 psql 登录命令。
PGSSLMODE=disable psql -U dbuser -h 127.0.0.1 -W、
Step 3: 报文解析
SCRAM 的认证过程基于 SASL(简单认证与安全层)框架,交互极度复杂,我们在 tcpdump 中会看到三次握手级的认证报文:
报文 1:Client First Message (客户端发起 SASL 认证,包含客户端生成的随机数 Nonce)
18:54:07.123827 IP localhost.13366 > localhost.postgres: Flags [P.], seq 81:136, ack 25, win 128, options [nop,nop,TS val 159911880 ecr 159911879], length 55
E..k..@.@.".........46.8 h....K......_.....
... ...p...6SCRAM-SHA-256.... n,,n=,r=3ewP3o9XZ8s/WTY4RXKJKxNY
报文 2:Server First Message (服务端返回自己的随机数、Salt、以及 PBKDF2 迭代次数)
18:54:07.123967 IP localhost.postgres > localhost.13366: Flags [P.], seq 25:118, ack 136, win 128, options [nop,nop,TS val 159911880 ecr 159911880], length 93
E...2!@.@.
D.........846..K. h.............
... ...R...\....r=3ewP3o9XZ8s/WTY4RXKJKxNYaR5rBiV4x/WhGbfOU81L1qjY,s=aYJIjMymmcu7HyWPrtzJUg==,i=4096
深度剖析
可以看到服务端带回了 s= (Base64编码的盐) 和 i=4096 (告诉客户端:你需要把密码进行 4096 次 SHA-256 迭代计算!),以此极大增加暴力破解的算力成本。
报文 3:Client Final Message (客户端发送复杂的计算证明)
18:54:07.129549 IP localhost.13366 > localhost.postgres: Flags [P.], seq 136:245, ack 118, win 128, options [nop,nop,TS val 159911885 ecr 159911880], length 109
E.....@.@."k........46.8 h....K{...........
... ...p...lc=biws,r=3ewP3o9XZ8s/WTY4RXKJKxNYaR5rBiV4x/WhGbfOU81L1qjY,p=c10/1yrKOH3VPAfYzTJ5IG069ZTxVBx+kglex3qsNGY=
深度剖析
客户端最终发回的 p= 是 ClientProof。它是通过 ClientKey 和 ServerSignature 进行异或运算得出的。
这个证明不仅向服务端证实了“我知道密码”,同时也验证了“当前连接的服务端是真的”,防住了中间人攻击。
结论
全程没有密码影子,黑客即便抓包拿到了所有随机数和 Proof,面对 SHA-256 和 4096 次迭代,也根本无法逆向出密码。
03
结论
通过上述完整的环境配置、tcpdump 的 -A 抓包实验以及协议层解析,我们用实打实的数据包证明了结论:

避坑指南
即便你使用了 scram-sha-256,它保护的也仅仅是认证阶段的密码安全。
一旦认证通过,你执行的 SQL 语句和业务数据依然是明文传输的(如前文 tcpdump 演示,SQL 语句也会被一览无余)。
因此,在生产环境中,配置 SCRAM-SHA-256 认证 + 强制开启 SSL/TLS 加密(将 pg_hba.conf 中的 host 改为 hostssl),才是保障数据库链路安全的终极黄金法则。
写在最后
你在生产环境中是否曾遇到过认证安全问题?
你使用的是哪种认证方式?
是否曾经因认证漏洞造成性能或安全隐患?
欢迎在评论区分享你的经验,探讨更多 PostgreSQL 安全防护 的最佳实践!
如果想要更好的阅读体验,请查看原文链接:https://mp.weixin.qq.com/s/2K6G4aYPjyqafRIAlZWgQA
更多推荐
所有评论(0)