MongoDB 24. SCRAM
结合官网、RFC资料,也看明白一些SCRAM的概念,网上这方面的知识比较少,这里个人记录一下。
What is SCRAM?
SCRAM全称 Salted Chanllenge Response Authentication Mechanism, 我翻译过来就是带盐的挑战-响应认证机制, 这个概念呢是从 RFC5802提出来的,并不是MongoDB首次提出来的。
What is RFC?
RFC英文就是Requests For Comment,在互联网上我有个想法,我可以发表想法,别人可以提出他们的意见。和不同的网上发表想法不一的是,RFC是高大上一点的内容、前沿创新的想法,不是我们想到随便发帖吐槽那种。绝大部分网络标准的制定都是以RFC的形式开始。
What is RFC5802?
在rfc-editor上找到了 5802的具体信息。https://www.rfc-editor.org/rfc/rfc5802.txt 关于5082号RFC的具体内容,后面会简单描述下。
MongoDB和SCRAM的关系?
上面讲述到RFC5802可以理解为一种idea,一种想法,MongoDB就是将这种想法运用到了,客户端和服务器的认证就是通过实现SCRAM,是一种对RFC5802的具体实现。
Why is SCRAM?
先说说我经历的大多数WEB程序加密是怎么做的:比如服务部署到了公网,首先有个登录入口,用户名、密码、高级一点再来个验证码、短信验证码登录,认证通过才让他访问资源啦。 这样是普通情况没问题,但是在两种情况下就有问题了:1. 客户端不是真的客户端,即浏览器那头不是真的浏览器; 2. 服务器不是真的服务器,即服务器可能都是假的。
第一种常见的是客户端是虚拟的,比如暴力破解密码攻击;
第二种常见的是流量被劫持,网站被劫持
针对这两种情况就有人提出了SCRAM方案,用于服务器、客户端互相验证身份的机制。
How is SCRAM?
在RFC5802文件中没看到一张流程图,结合着MongoDB的一张图来描述这个过程。
1.第一步 client-first-message
客户端发送认证消息,该消息称为:client-first-message
,消息样例:n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
, 包含了用户名,以及一个nonce。 消息包含两个要素:1.user,用户名,2.nonce,一次性的随机串,防止重放攻击;
2.第二步 server-first-message
服务器返回响应,称为:server-first-message
,消息样例:r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
,可以看到:s是salt就是服务器对于用户密码加密的盐,i就是hash加密迭代次数,另外服务器也会返回一个nonce拼接在客户端的nonce后面;
- 第三步 client-final-message
客户端根据第二步进行计算、加密后,返回消息给服务器,该消息称为client-final-message
,消息样例为: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
,可以看到:包含服务器返回的nonce串,以及一个ClientProof给服务器。后面描述ClientProof计算方式。
- 第四步 server-final-message
服务器器校验nonce和proof,之后返回serverSignature给客户端;该消息称为server-final-message
,消息样例为:v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
。
客户端拿到serverSignature,和他自己计算出来的是否一致,如果校验一致通过则认为成功,失败后就丢掉连接。
以上就是鉴权交互流程的简单描述,其中很多细节下面记录下。
细节描述
服务器创建用户名密码的时候,就会开始计算下列信息;
SaltedPassword=Hi(password,salt,count), hi是加密函数.
ClientKey=HMAC(SaltedPassword,"Client Key"), HMAC算法计算ClientKey.
ServerKey=HMAC(SaltedPassword,"Server Key").
StoreKey=H(ClientKey), H是hash函数.
SCRAM-SHA1就是说hash函数用的是SHA1.
MongoDB可以查看用户使用的是哪种算法,以及部分秘钥. 如下图:加密算法是SCRAM-SHA-1,还有迭代次数count、salt,以及存储的storedKey、serverKey。
client-first-message: n,,n={{username}},r={{clientNonce}}
server-first-message: r={{clientNonce+serverNonce}},s={{salt}},i={{count}}
client-final-message: c=biws,r={{clientNonce+serverNonce}},p={{clientProof}}
server-final-message: v={{serverSignature}}
前两步比较简单,第三步客户端计算clientProof. 客户端端首先根据如下计算ClientKey以及StoredKey。
SaltedPassword=Hi(password,salt,count)
ClientKey=HMAC(SaltedPassword,"Client Key")
StoreKey=H(ClientKey)
然后通过公式得到AuthMessage-> ClientSignature-> ClientProof
AuthMessage := client-first-message-bare + "," +
server-first-message + "," +
client-final-message-without-proof
ClientSignature= HMAC(StoredKey, AuthMessage)
ClientProof= ClientKey XOR ClientSignature
以上就是客户端发送的proof,证明自己是客户端。然后服务器使用存储的StoredKey
和AuthMessage进行HMAC算法,就能得到 ClientSignature,通过
ClientProof XOR 服务器计算的ClientSignature = ClientKey
通过比较H(ClientKey) = StoredKey来判断客户端认证是否通过。ClientKey是计算出来的,再hash一次,然后和服务器保存的StoredKey比较。验证通过后,服务器会返回ServerSignature给到客户端,以此证明自己是服务端。
ServerSignature= HMAC(ServerKey, AuthMessage)
客户端通过计算:ServerKey=HMAC(SaltedPassword,"Server Key"), ServerSignature=HMAC(ServerKey, AuthMessage)
,认证通过就认为这是真实的服务端。
以上就完成了客户端-服务端的相互认证过程。
补充
- 服务端保存的是StoredKey,也就是H(ClientKey),而不是直接的ClientKey?
假如保存ClientKey,如果ClientKey泄露,那就可以推导出ClientProof,假装成客户端了。
StoreKey=H(ClientKey)
AuthMessage := client-first-message-bare + "," +
server-first-message + "," +
client-final-message-without-proof
ClientSignature= HMAC(StoredKey, AuthMessage)
ClientProof= ClientKey XOR ClientSignature
假如保存的StoredKey泄露,客户端可以模拟ClientSignature,所以ClientProof= ClientKey XOR ClientSignature,这一步保障了客户端是知道自己的ClientKey的。
- nonce是干嘛用的?
nonce是随机的字符串,客户端、服务器端都是不一样的,这样Auth变量就每次都不一样,防止黑客暴力尝试了。
MongoDB中的SCRAM
MongoDB中的StoredKey、ServerKey
查看system.users表保存了前面信息等。
tcpdump简单抓包
用tcpdump简单抓个包,就可以发现交互协议完全服务 RFC5802描述。
先到这里啦。