SCRAM-SHA-256(Salted Challenge Response Authentication Mechanism)是 PostgreSQL 10+ 默认推荐的密码认证方式,本质是基于挑战-响应(challenge-response)的 SASL 认证协议。它不会在网络上传输明文密码,也不会传输可以直接重放的密码哈希。
结合 PostgreSQL 协议来看,完整流程如下。
1. 用户密码保存阶段(服务器侧)
用户执行如下命令设置密码:
ALTER USER test PASSWORD 'mypassword';
如果服务器配置了:
password_encryption = scram-sha-256
那么服务器不会保存明文密码,而是保存类似下面的验证器(verifier):
SCRAM-SHA-256$4096:<salt>$<StoredKey>:<ServerKey>
其中:
salt:随机盐4096:迭代次数(默认)StoredKeyServerKey
生成过程:
SaltedPassword = Hi(password, salt, iterations)
ClientKey = HMAC(SaltedPassword, "Client Key")
StoredKey = SHA256(ClientKey)
ServerKey = HMAC(SaltedPassword, "Server Key")
服务器保存:
salt
iteration count
StoredKey
ServerKey
不保存:
明文密码
SaltedPassword
2. TCP连接建立
客户端连接 PostgreSQL:
client server
| |
|---- StartupMessage --------> |
| user=test |
| database=db |
| |
StartupMessage里面包含:
user=test
database=db
3. Server 发起 SCRAM 请求
服务器发现:
pg_hba.conf:
host all all 0.0.0.0/0 scram-sha-256
返回:
AuthenticationSASL
内容:
SCRAM-SHA-256
SCRAM-SHA-256-PLUS
表示支持哪些 SASL 机制。
协议:
server
|
| AuthenticationSASL
|
client
4. Client First Message
客户端选择:
SCRAM-SHA-256
发送:
client-first-message
例如:
n,,n=test,r=fyko+d2lbbFgONRv9qkxdawL
含义:
| 字段 | 含义 |
|---|---|
| n | 不使用channel binding |
| n=test | 用户名 |
| r=xxx | 客户端随机nonce |
nonce是随机数。
5. Server First Message
服务器收到后:
-
根据用户名查找:
pg_authid
找到:
salt
iteration
StoredKey
ServerKey
- 生成自己的nonce
把:
client nonce
+
server nonce
拼接。
返回:
server-first-message
例如:
r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvW,
s=QSXCR+Q6sek8bf92,
i=4096
字段:
| 字段 | 含义 |
|---|---|
| r | 完整nonce |
| s | salt |
| i | 迭代次数 |
6. Client计算Proof
客户端现在知道:
password
salt
iteration
server nonce
计算:
第一步
计算:
SaltedPassword
例如:
Hi(
password,
salt,
4096
)
第二步
计算:
ClientKey
ClientKey =
HMAC(
SaltedPassword,
"Client Key"
)
第三步
计算:
StoredKey
StoredKey =
SHA256(ClientKey)
第四步
生成认证消息:
AuthMessage =
client-first-message
+
server-first-message
+
client-final-message-without-proof
第五步
计算:
ClientSignature =
HMAC(
StoredKey,
AuthMessage
)
第六步
计算:
ClientProof =
ClientKey XOR ClientSignature
发送:
client-final-message
例如:
c=biws,
r=xxx,
p=v0X8v3Bz...
其中:
p = ClientProof
7. Server验证
服务器收到:
ClientProof
自己计算:
ClientSignature =
HMAC(
StoredKey,
AuthMessage
)
然后:
ClientKey =
ClientProof XOR ClientSignature
得到:
ClientKey
再计算:
SHA256(ClientKey)
比较:
SHA256(ClientKey)
==
StoredKey
如果一致:
认证成功。
8. Server返回最终证明
服务器发送:
server-final-message
例如:
v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
这里:
v = ServerSignature
客户端验证:
ServerSignature =
HMAC(ServerKey, AuthMessage)
确认服务器是真的。
9. 最后 AuthenticationOk
服务器:
AuthenticationOk
连接建立。
整个交互图
Client Server
| |
| StartupMessage |
|----------------------------->|
| |
| AuthenticationSASL |
|<-----------------------------|
| |
| client-first-message |
|----------------------------->|
| |
| server-first-message |
|<-----------------------------|
| |
| client-final-message |
| (ClientProof) |
|----------------------------->|
| |
| server-final-message |
|<-----------------------------|
| |
| AuthenticationOk |
|<-----------------------------|
和 MD5 认证区别
| MD5 | SCRAM-SHA-256 | |
|---|---|---|
| 密码保存 | md5(password+user) | salt+StoredKey+ServerKey |
| 网络传输 | md5 response | proof |
| 防重放 | 一般 | 强 |
| 服务器是否知道密码 | 否 | 否 |
| 支持双向验证 | 否 | 是 |
| 支持channel binding | 否 | 是 |
结合你之前分析 PostgreSQL/openGauss startup packet、Proxy认证、compute连接流程,SCRAM最关键的一点是:
Proxy如果伪装 PostgreSQL Server,需要完整实现 SCRAM 状态机,不能只转发用户名密码。
因为客户端发给 Proxy 的不是密码,而是:
client-first-message
client-final-message(ClientProof)
Proxy必须:
- 保存用户SCRAM verifier(或转发到真正认证端)
- 生成server nonce
- 返回server-first-message
- 验证client proof
这也是 Neon Proxy / PostgreSQL兼容代理实现认证时比较复杂的地方。