概述
MongoDB客户端在建立服务连接时的认证过程,根据所采用的认证方式,可能会有所不同,社区版MongoDB通常支持SCRAM(Salted Challenge Response Authentication Mechanism)盐化挑战响应身份验证机制和基于X.509证书的认证,而企业版MongoDB还提供了与许多外部身份验证机制的集成,如Kerberos和LDAP等。从MongoDB 3.0开始,SCRAM成为了MongoDB的默认身份验证机制,不同的认证方式也可以通过配置文件或启动参数来启用。
SCRAM认证简介
SCRAM(Salted Challenge Response Authentication Mechanism)是一种基于加盐哈希和HMAC摘要的用于客户端和服务器之间的用户认证机制,它通过一个挑战-响应协议来验证用户的身份。SCRAM是MongoDB的默认认证机制,并且被设计为既安全又易于实现。下面介绍SCRAM认证的基本概念和认证步骤。
基本概念
- 用户名(Username):用户的唯一标识符。
- 密码(Password):用户的认证凭据,存储时会经过处理。
- 盐(Salt):一个随机值,用于与密码结合生成存储的凭证,增加密码存储的安全性。
- 迭代次数(Iteration Count):密码处理时的迭代次数,增加密码哈希的计算复杂度。
SCRAM认证步骤
- 客户端发送用户名:客户端首先向服务器发送包含用户名的请求。
- 服务器发送挑战:服务器收到请求后,会生成一个随机的挑战(server-nonce),这个挑战是随机生成的,通常是一个足够长的随机字符串,以确保安全性。
- 客户端生成响应:
- 客户端接收到挑战后,会使用用户的密码和服务器提供的盐以及迭代次数来生成一个密码验证器(Password Verifier)。
- 客户端使用密码、盐和迭代次数通过一个单向函数(通常是SCRAM-SHA-1)生成一个存储密钥(Stored Key)。
- 然后,客户端使用存储密钥和挑战来生成客户端证据(Client Evidence),通常是对存储密钥和挑战的HMAC。
- 服务器验证响应:
- 服务器接收到客户端的证据后,会使用存储在数据库中的密码验证器和客户端发送的挑战来生成服务器证据。
- 服务器比较客户端证据和服务器证据,如果两者匹配,则认证成功。
- 服务器发送成功或失败消息:如果认证成功,服务器会发送一个成功的消息给客户端;如果失败,会发送失败的消息。
在SCRAM认证中,第2步骤中的随机数(server-nonce)的生成是非常重要的,因为它增加了认证过程的安全性。MongoDB中是利用操作系统提供的API,通过标准库函数读取/dev/urandom文件来获取随机数。在Linux系统中,/dev/urandom 是一个特殊文件,它提供了一个伪随机数生成器(PRNG),用于生成加密安全的伪随机数。虽然它是伪随机的,但被认为是加密安全的,因为其生成的随机数序列对于所有实用目的来说都是不可预测的。
认证示例
初始
- 服务端使用一个salt和一个iteration-count,对password进行加盐哈希(使用H表示哈希函数,这里就是SHA1,iteration-count就是哈希迭代次数),得到一个password[s]:
*password[s] = H(password, salt, iteration-count)*
- 服务端拿这个password[s]分别和字符串『Client Key』和『Server Key』进行计算HMAC摘要,得到一个key[c]和一个key[s]:
*key[c] = HMAC(password[s], "Client Key")
key[s] = HMAC(password[s], "Server Key")*
- 服务端保存username、H(key[c])、key[s]、salt和iteration-count,没有保存真正的password
认证
- 客户端发送client-first-message给服务端,包含username和client-nonce,其中client-nonce是客户端随机生成的字符串。
- 服务端返回客户端server-first-message,包含salt,iteration-count和client-nonce|server-nonce,其中server-nonce是服务端随机生成的字符串。
- 客户端发送client-final-message给服务端,包含client-nonce|server-nonce和一个proof[c]。这个proof[c]就是客户端的身份证明。首先构造出这次认证的变量Auth如下:
Auth = client-first-message, server-first-message, client-final-message(without proof[c])
然后使用从服务端获取的salt和iteration-count,根据已知的password计算出加盐哈希password[s],然后根据password[s]得到key[c],再拿这个key[c]和Auth变量经过如下计算得到:
proof[c] = key[c] XOR HMAC(H(key[c]), Auth)
- 服务端使用其保存的H(key[c])和Auth计算HMAC摘要,再和proof[c]进行异或,得出key[c],再对这个key[c]进行哈希,和其保存的H(key[c])进行比较是否一致。如果一致,则客户端的认证通过,服务端接下来会构造一个proof[s]用来向客户端证明自己是服务端:
*proof[s] = HMAC(key[s], Auth)*
- 客户端使用password[s]得到key[s],然后使用相同算法计算key[s]和Auth的HMAC摘要,验证服务端发送过来的proof[s]是否和计算出来的一致,从而认证服务端的身份。