理解SCRAM认证机制


SCRAM是密码学中的一种认证机制,全称Salted Challenge Response Authentication Mechanism。它适用于使用基于“用户名:密码”这种简单认证模型的连接协议。SCRAM是一个抽象的机制,在其设计中需要用到一个哈希函数,这个哈希函数是客户端和服务端协商好的,包含在具体的机制名称中。比如SCRAM-SHA1,使用SHA1作为其哈希函数。
SCRAM是一套包含服务器和客户端双向确认的用户认证体系,配合信道加密可以比较好的抵御中间人、拖库、伪造等攻击。信道应另外加密以增强安全性,如使用可靠TLS方法实现的HTTPS加密。
基于“用户名:密码”这种简单认证模型的协议中,客户端和服务端都知道一个用户名(username)对应一个密码(password)。在不对信道进行加密的前提下,无论是直接使用明文传输“username:password”,还是给password加个哈希的方法都很容易被黑客攻击。一个安全性高的认证机制起码应该是具备双向认证的,即:
服务端需要验证连接上来的客户端知道对应的password
客户端需要知道这个声称是服务端的人是真正的服务端
来看看SCRAM是怎么做的。
初始
服务端使用一个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]是否和计算出来的一致,从而认证服务端的身份。
相关字段
定义:
Hash(content)指安全Hash函数,如SHA256。
HMAC()指使用Hash(content)作为基础实现的HMAC函数,如HMAC(SHA256,Key,Content)。
UserIdentify:仅用于登录签名的用户身份标识字符串,每次登录时可根据业务任意选用用户名、手机号、邮箱、三方ID等,下次登录可以选另一种,仅作为本次通讯的标识之一使用。
Password = Hash(明文密码)。
Salt:随机生成的盐。
Iteration:加盐计算时的迭代次数。
SaltedPassword = pbkdf2(Password, Salt, Iteration),已加盐的密码。
ClientNonce:客户端在Step1时随机生成的字符串,用于对本次交互签名。
ServerNonce:服务器在Step1时随机生成的字符串,用于对本次交互签名。
MixNonce = ClientNonce | ServerNonce,ClientNonce和ServerNonce的按位或结果。
ServerPub:用于服务器签名的公钥,不用保密,但也别改。
ClientPub:用于客户端签名的公钥,不用保密,但也别改。
ServerSignedPassword = HMAC(ServerPub, SaltedPassword)。
ClientSignedPassword = HMAC(ClientPub, SaltedPassword)。
HashedClientSignedPassword = Hash(ClientSignedPassword)。
Auth = UserIdentify + ClientNonce + Salt + MixNonce + MixNonce,直接连接即可,用于构造本次通讯上下文的签名。
SignedAuth = HMAC(Auth, HashedClientSignedPassword)。
ServerProof = HMAC(ClientSignedPassword, Auth),服务器给客户端的身份证明。
ClientProof = ClientSignedPassword XOR SignedAuth,客户端给服务器的身份证明。
工作流程
Client-Step1
客户端发送UserIdentify、ClientNonce给服务器端。
Server-Step1
服务器根据UserIdentify读取HashPasswordClient、PasswordServer、Salt、Iteration。
生成ServerNonce、MixNonce。
将Salt、Iteration、MixNonce返回给客户端。
Client-Step2
客户端根据前文公式生成Salt,Iteration及Passowrd计算出SaltedPassword。
算出Auth = UserIdentify + ClientNonce + Salt + MixNonce + MixNonce。
算出SignedAuth = HMAC(Auth, HashedClientSignedPassword)。
算出ClientProof = ClientSignedPassword XOR SignedAuth。
将MixNonce和ClientProof返回给服务器。
Server-Step2
算出Auth = UserIdentify + ClientNonce + Salt + MixNonce + MixNonce;注意第一个MixNonce应使用Server-Step1发出的,第二个MixNonce应使用Client-Step2发来的。
算出SignedAuth = HMAC(Auth, HashedClientSignedPassword)。
根据异或运算算出客户端的ClientSignedPassword = ClientProof XOR SignedAuth。
算出HashedClientSignedPassword = Hash(ClientSignedPassword)。
比对上一步算出的HashedClientSignedPassword与数据库存储的HashedClientSignedPassword是否一致,如果一致则说明密码校验成功。
算出ServerProof = HMAC(ServerSignedPassword, Auth)。
将ServerProof发给客户端。
Client-Step3
算出ServerSignedPassword = HMAC(ServerPub, SaltedPassword)。
算出ServerProof = HMAC(ServerSignedPassword, Auth)。
将ServerProof与服务器发来的ServerProof进行比较,如果一致则说明校验成功。
相关问题
Q:proof[c]为什么不能是和proof[s]一样使用HMAC(key[c], Auth)算得?
A:服务端没有保存key[c],而是保存H(key[c]),这是因为如果服务端的key[c]泄露,那么黑客可以轻易构造出proof[c],从而伪装成客户端了。
Q:那么直接使用HMAC(H(key[c]), Auth)呢?
A:同样,如果服务端的H(key[c])泄露,黑客又可以轻易构造出proof[c],从而伪装成客户端了。因此必须要加上key[c]的异或,从而证明客户端知道key[c]的值。
Q:但是如果服务端的H(Key[c])泄露,再通过网络泄露了proof[c]和Auth,就可以根据proof[c]和HMAC(H(key[c]), Auth)异或得到key[c]了?
A:是的,所以如果要求更高的安全性,还是推荐使用信道加密。
Q:nonce的作用?
A:client-nonce和server-nonce都是随机生成的字符串,这主要是为了每次认证都有个不同的Auth变量,以防止被重放攻击。
安全性分析
分析下以下几种情况下SCRAM的安全性:
网络流量被监听:如果某次认证的网络流量被监听,黑客可以拿到salt、iteration-count和Auth,但是无法伪装成服务端,因为没有key[s],无法生成proof[s],完成不了最后一次认证。
服务端被拖库:如果服务端被拖库,黑客可以拿到username、H(key[c])、key[s]、salt和iteration-count,但是生成不了key[c],因此无法生成proof[c],不能伪装成客户端。
如果很不幸的网络流量被监听,服务端又被拖库,那么对不起,要应对这种情况,只能使用信道加密了。
SCRAM是一套包含服务器和客户端双向确认的用户认证体系,配合信道加密可以比较好的抵御中间人、拖库、伪造等攻击。信道应另外加密以增强安全性,如使用可靠TLS方法实现的HTTPS加密。
基于“用户名:密码”这种简单认证模型的协议中,客户端和服务端都知道一个用户名(username)对应一个密码(password)。在不对信道进行加密的前提下,无论是直接使用明文传输“username:password”,还是给password加个哈希的方法都很容易被黑客攻击。一个安全性高的认证机制起码应该是具备双向认证的,即:
服务端需要验证连接上来的客户端知道对应的password
客户端需要知道这个声称是服务端的人是真正的服务端
来看看SCRAM是怎么做的。
初始
服务端使用一个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]是否和计算出来的一致,从而认证服务端的身份。
相关字段
定义:
Hash(content)指安全Hash函数,如SHA256。
HMAC()指使用Hash(content)作为基础实现的HMAC函数,如HMAC(SHA256,Key,Content)。
UserIdentify:仅用于登录签名的用户身份标识字符串,每次登录时可根据业务任意选用用户名、手机号、邮箱、三方ID等,下次登录可以选另一种,仅作为本次通讯的标识之一使用。
Password = Hash(明文密码)。
Salt:随机生成的盐。
Iteration:加盐计算时的迭代次数。
SaltedPassword = pbkdf2(Password, Salt, Iteration),已加盐的密码。
ClientNonce:客户端在Step1时随机生成的字符串,用于对本次交互签名。
ServerNonce:服务器在Step1时随机生成的字符串,用于对本次交互签名。
MixNonce = ClientNonce | ServerNonce,ClientNonce和ServerNonce的按位或结果。
ServerPub:用于服务器签名的公钥,不用保密,但也别改。
ClientPub:用于客户端签名的公钥,不用保密,但也别改。
ServerSignedPassword = HMAC(ServerPub, SaltedPassword)。
ClientSignedPassword = HMAC(ClientPub, SaltedPassword)。
HashedClientSignedPassword = Hash(ClientSignedPassword)。
Auth = UserIdentify + ClientNonce + Salt + MixNonce + MixNonce,直接连接即可,用于构造本次通讯上下文的签名。
SignedAuth = HMAC(Auth, HashedClientSignedPassword)。
ServerProof = HMAC(ClientSignedPassword, Auth),服务器给客户端的身份证明。
ClientProof = ClientSignedPassword XOR SignedAuth,客户端给服务器的身份证明。
工作流程
Client-Step1
客户端发送UserIdentify、ClientNonce给服务器端。
Server-Step1
服务器根据UserIdentify读取HashPasswordClient、PasswordServer、Salt、Iteration。
生成ServerNonce、MixNonce。
将Salt、Iteration、MixNonce返回给客户端。
Client-Step2
客户端根据前文公式生成Salt,Iteration及Passowrd计算出SaltedPassword。
算出Auth = UserIdentify + ClientNonce + Salt + MixNonce + MixNonce。
算出SignedAuth = HMAC(Auth, HashedClientSignedPassword)。
算出ClientProof = ClientSignedPassword XOR SignedAuth。
将MixNonce和ClientProof返回给服务器。
Server-Step2
算出Auth = UserIdentify + ClientNonce + Salt + MixNonce + MixNonce;注意第一个MixNonce应使用Server-Step1发出的,第二个MixNonce应使用Client-Step2发来的。
算出SignedAuth = HMAC(Auth, HashedClientSignedPassword)。
根据异或运算算出客户端的ClientSignedPassword = ClientProof XOR SignedAuth。
算出HashedClientSignedPassword = Hash(ClientSignedPassword)。
比对上一步算出的HashedClientSignedPassword与数据库存储的HashedClientSignedPassword是否一致,如果一致则说明密码校验成功。
算出ServerProof = HMAC(ServerSignedPassword, Auth)。
将ServerProof发给客户端。
Client-Step3
算出ServerSignedPassword = HMAC(ServerPub, SaltedPassword)。
算出ServerProof = HMAC(ServerSignedPassword, Auth)。
将ServerProof与服务器发来的ServerProof进行比较,如果一致则说明校验成功。
相关问题
Q:proof[c]为什么不能是和proof[s]一样使用HMAC(key[c], Auth)算得?
A:服务端没有保存key[c],而是保存H(key[c]),这是因为如果服务端的key[c]泄露,那么黑客可以轻易构造出proof[c],从而伪装成客户端了。
Q:那么直接使用HMAC(H(key[c]), Auth)呢?
A:同样,如果服务端的H(key[c])泄露,黑客又可以轻易构造出proof[c],从而伪装成客户端了。因此必须要加上key[c]的异或,从而证明客户端知道key[c]的值。
Q:但是如果服务端的H(Key[c])泄露,再通过网络泄露了proof[c]和Auth,就可以根据proof[c]和HMAC(H(key[c]), Auth)异或得到key[c]了?
A:是的,所以如果要求更高的安全性,还是推荐使用信道加密。
Q:nonce的作用?
A:client-nonce和server-nonce都是随机生成的字符串,这主要是为了每次认证都有个不同的Auth变量,以防止被重放攻击。
安全性分析
分析下以下几种情况下SCRAM的安全性:
网络流量被监听:如果某次认证的网络流量被监听,黑客可以拿到salt、iteration-count和Auth,但是无法伪装成服务端,因为没有key[s],无法生成proof[s],完成不了最后一次认证。
服务端被拖库:如果服务端被拖库,黑客可以拿到username、H(key[c])、key[s]、salt和iteration-count,但是生成不了key[c],因此无法生成proof[c],不能伪装成客户端。
如果很不幸的网络流量被监听,服务端又被拖库,那么对不起,要应对这种情况,只能使用信道加密了。