桑普萨尔stram-sha1认证
XMPP SASL SCRAM-SHA1 Authentication
最近,我能够按照以下两个网站上的说明在 Swift IOS 中获得适用于 XMPP 流的 MD5 身份验证(我使用了 Apple 的 CommonCrypto C 库的 CC-MD5 函数对于实际的散列):
http://wiki.xmpp.org/web/SASLandDIGEST-MD5
http://www.deusty.com/2007/09/example-please.html
我正在寻找有关如何使其他散列 SASL 身份验证方案(尤其是 SCRAM-SHA1)起作用的类似解释。我找到了官方 RFC5802 文档,但我很难理解它(它也不是特定于 XMPP 的)。我希望有一个更简单的解释或一些简单的可读代码(C、PHP、C++、Javascript、Java)特定于 XMPP 身份验证,除了实际哈希。
我有兴趣了解该过程,不打算使用 ios XMPP 框架。任何帮助将不胜感激。
SCRAM-SHA-1
此机制如何工作的基本概述是:
- 客户端发送它想要验证的用户名。
- 服务器发回该用户的 salt 和迭代次数(通过生成它们或在其数据库中查找给定用户名)。
- 客户端在给定的迭代次数内使用给定的盐对密码进行哈希处理。
- 客户端发回结果
- 服务器进行散列的变体并将其结果发送回客户端,因此客户端也可以验证服务器是否具有密码的 password/a 散列。
您需要的加密算法是 SHA-1、带 SHA-1 的 HMAC 和带 SHA-1 的 PBKDF2。您应该在 language/framework 中查看如何使用它们,因为我不建议从头开始实施它们。
详细
首先规范化密码(使用SASLprep),这将是normalizedPassword
。这是为了确保 UTF8 编码不能包含相同密码的变体。
随机选择一个字符串(例如 32 个十六进制编码字节)。这将是 clientNonce
.
initialMessage
是 "n=" .. username .. ",r=" .. clientNonce
(我使用 ..
进行字符串连接)。
客户端将 GS2 header ("n,,"
) 添加到 initialMessage 并对结果进行 base64 编码。它作为第一条消息发送:
<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="SCRAM-SHA-1">
biwsbj1yb21lbyxyPTZkNDQyYjVkOWU1MWE3NDBmMzY5ZTNkY2VjZjMxNzhl
</auth>
服务器响应质询。挑战的数据是base64编码的:
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY=
</challenge>
客户端base64解码:
r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096
客户端解析为:
r=
这是serverNonce
。客户端必须确保它以它在初始消息中发送的 clientNonce
开始。
s=
这是salt
,base64编码的(是的,这是base64编码的两次!)
i=
这是迭代次数,i
.
客户端计算:
clientFinalMessageBare = "c=biws,r=" .. serverNonce
saltedPassword = PBKDF2-SHA-1(normalizedPassword, salt, i)
clientKey = HMAC-SHA-1(saltedPassword, "Client Key")
storedKey = SHA-1(clientKey)
authMessage = initialMessage .. "," .. serverFirstMessage .. "," .. clientFinalMessageBare
clientSignature = HMAC-SHA-1(storedKey, authMessage)
clientProof = clientKey XOR clientSignature
serverKey = HMAC-SHA-1(saltedPassword, "Server Key")
serverSignature = HMAC-SHA-1(serverKey, authMessage)
clientFinalMessage = clientFinalMessageBare .. ",p=" .. base64(clientProof)
客户端 base64 编码 clientFinalMessage
并将其作为响应发送:
<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0=
</response>
如果一切顺利,您将从服务器收到 <success>
响应:
<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289
</success>
Base64解码后包含:
v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=
客户端必须确保 v
的值是 serverSignature
.
的 base64 编码
额外内容
这是算法的基本版本。你可以扩展它来做:
频道绑定。这将一些来自 TLS 连接的信息混合到程序中以防止 MitM 攻击。
哈希存储。如果服务器始终发送相同的 salt
和 i
值,则客户端只能存储 saltedPassword
而不是用户密码。这更安全(因为客户端不需要存储密码,只是一个难以逆转的加盐哈希)并且更快,因为客户端不需要每次都进行所有密钥扩展。
服务器也可以使用散列存储:服务器只能存储salt
、i
、storedKey
和serverKey
。更多信息 here。
可能,还添加 SCRAM-SHA-256(虽然服务器支持似乎 non-existent)。
陷阱
一些常见的陷阱:
- 不要假设随机数的长度或
salt
(尽管如果您生成它们,请确保它们足够长且密码随机)。
salt
是 base64 编码的,可以包含任何数据(嵌入 NUL
s)。
- 不使用 SASLprep 对于使用 ASCII 密码的人来说可能没问题,但对于使用其他脚本的人来说可能会完全中断登录。
-
authMessage
的 initialMessage
部分不包括 GS2 header(在大多数情况下,这是 "n,,"
)。
测试向量
如果您想测试您的实现,这里是来自 RFC 示例的所有中间结果:
用户名:user
密码:pencil
客户端生成随机数 fyko+d2lbbFgONRv9qkxdawL
初始消息:n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
服务器生成随机数 3rfcNHYJY1ZVvWVs7j
服务器回复:r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
盐(十六进制):4125c247e43ab1e93c6dff76
客户最后留言:c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
加盐密码(十六进制):1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d
客户端密钥(十六进制):e234c47bf6c36696dd6d852b99aaa2ba26555728
存储的密钥(十六进制):e9d94660c39d65c38fbad91c358f14da0eef2bd6
授权信息:n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
客户端签名(十六进制):5d7138c486b0bfabdf49e3e2da8bd6e5c79db613
客户端证明(十六进制):bf45fcbf7073d93d022466c94321745fe1c8e13b
服务器密钥(十六进制):0fe09258b3ac852ba502cc62ba903eaacdbf7d31
服务器签名(十六进制):ae617da6a57c4bbb2e0286568dae1d251905b0a4
客户最后留言:[=63=
服务器最终消息:v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
服务器的服务器签名(十六进制):ae617da6a57c4bbb2e0286568dae1d251905b0a4
最近,我能够按照以下两个网站上的说明在 Swift IOS 中获得适用于 XMPP 流的 MD5 身份验证(我使用了 Apple 的 CommonCrypto C 库的 CC-MD5 函数对于实际的散列):
http://wiki.xmpp.org/web/SASLandDIGEST-MD5
http://www.deusty.com/2007/09/example-please.html
我正在寻找有关如何使其他散列 SASL 身份验证方案(尤其是 SCRAM-SHA1)起作用的类似解释。我找到了官方 RFC5802 文档,但我很难理解它(它也不是特定于 XMPP 的)。我希望有一个更简单的解释或一些简单的可读代码(C、PHP、C++、Javascript、Java)特定于 XMPP 身份验证,除了实际哈希。
我有兴趣了解该过程,不打算使用 ios XMPP 框架。任何帮助将不胜感激。
SCRAM-SHA-1
此机制如何工作的基本概述是:
- 客户端发送它想要验证的用户名。
- 服务器发回该用户的 salt 和迭代次数(通过生成它们或在其数据库中查找给定用户名)。
- 客户端在给定的迭代次数内使用给定的盐对密码进行哈希处理。
- 客户端发回结果
- 服务器进行散列的变体并将其结果发送回客户端,因此客户端也可以验证服务器是否具有密码的 password/a 散列。
您需要的加密算法是 SHA-1、带 SHA-1 的 HMAC 和带 SHA-1 的 PBKDF2。您应该在 language/framework 中查看如何使用它们,因为我不建议从头开始实施它们。
详细
首先规范化密码(使用SASLprep),这将是
normalizedPassword
。这是为了确保 UTF8 编码不能包含相同密码的变体。随机选择一个字符串(例如 32 个十六进制编码字节)。这将是
clientNonce
.initialMessage
是"n=" .. username .. ",r=" .. clientNonce
(我使用..
进行字符串连接)。客户端将 GS2 header (
"n,,"
) 添加到 initialMessage 并对结果进行 base64 编码。它作为第一条消息发送:<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="SCRAM-SHA-1"> biwsbj1yb21lbyxyPTZkNDQyYjVkOWU1MWE3NDBmMzY5ZTNkY2VjZjMxNzhl </auth>
服务器响应质询。挑战的数据是base64编码的:
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY= </challenge>
客户端base64解码:
r=6d442b5d9e51a740f369e3dcecf3178ec12b3985bbd4a8e6f814b422ab766573,s=QSXCR+Q6sek8bf92,i=4096
客户端解析为:
r=
这是serverNonce
。客户端必须确保它以它在初始消息中发送的clientNonce
开始。s=
这是salt
,base64编码的(是的,这是base64编码的两次!)i=
这是迭代次数,i
.
客户端计算:
clientFinalMessageBare = "c=biws,r=" .. serverNonce saltedPassword = PBKDF2-SHA-1(normalizedPassword, salt, i) clientKey = HMAC-SHA-1(saltedPassword, "Client Key") storedKey = SHA-1(clientKey) authMessage = initialMessage .. "," .. serverFirstMessage .. "," .. clientFinalMessageBare clientSignature = HMAC-SHA-1(storedKey, authMessage) clientProof = clientKey XOR clientSignature serverKey = HMAC-SHA-1(saltedPassword, "Server Key") serverSignature = HMAC-SHA-1(serverKey, authMessage) clientFinalMessage = clientFinalMessageBare .. ",p=" .. base64(clientProof)
客户端 base64 编码
clientFinalMessage
并将其作为响应发送:<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> Yz1iaXdzLHI9NmQ0NDJiNWQ5ZTUxYTc0MGYzNjllM2RjZWNmMzE3OGVjMTJiMzk4NWJiZDRhOGU2ZjgxNGI0MjJhYjc2NjU3MyxwPXlxbTcyWWxmc2hFTmpQUjFYeGFucG5IUVA4bz0= </response>
如果一切顺利,您将从服务器收到
<success>
响应:<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289 </success>
Base64解码后包含:
v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=
客户端必须确保
的 base64 编码v
的值是serverSignature
.
额外内容
这是算法的基本版本。你可以扩展它来做:
频道绑定。这将一些来自 TLS 连接的信息混合到程序中以防止 MitM 攻击。
哈希存储。如果服务器始终发送相同的
salt
和i
值,则客户端只能存储saltedPassword
而不是用户密码。这更安全(因为客户端不需要存储密码,只是一个难以逆转的加盐哈希)并且更快,因为客户端不需要每次都进行所有密钥扩展。服务器也可以使用散列存储:服务器只能存储
salt
、i
、storedKey
和serverKey
。更多信息 here。可能,还添加 SCRAM-SHA-256(虽然服务器支持似乎 non-existent)。
陷阱
一些常见的陷阱:
- 不要假设随机数的长度或
salt
(尽管如果您生成它们,请确保它们足够长且密码随机)。 salt
是 base64 编码的,可以包含任何数据(嵌入NUL
s)。- 不使用 SASLprep 对于使用 ASCII 密码的人来说可能没问题,但对于使用其他脚本的人来说可能会完全中断登录。
-
authMessage
的initialMessage
部分不包括 GS2 header(在大多数情况下,这是"n,,"
)。
测试向量
如果您想测试您的实现,这里是来自 RFC 示例的所有中间结果:
用户名:
user
密码:
pencil
客户端生成随机数
fyko+d2lbbFgONRv9qkxdawL
初始消息:
n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
服务器生成随机数
3rfcNHYJY1ZVvWVs7j
服务器回复:
r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
盐(十六进制):
4125c247e43ab1e93c6dff76
客户最后留言:
c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
加盐密码(十六进制):
1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d
客户端密钥(十六进制):
e234c47bf6c36696dd6d852b99aaa2ba26555728
存储的密钥(十六进制):
e9d94660c39d65c38fbad91c358f14da0eef2bd6
授权信息:
n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
客户端签名(十六进制):
5d7138c486b0bfabdf49e3e2da8bd6e5c79db613
客户端证明(十六进制):
bf45fcbf7073d93d022466c94321745fe1c8e13b
服务器密钥(十六进制):
0fe09258b3ac852ba502cc62ba903eaacdbf7d31
服务器签名(十六进制):
ae617da6a57c4bbb2e0286568dae1d251905b0a4
客户最后留言:[=63=
服务器最终消息:
v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
服务器的服务器签名(十六进制):
ae617da6a57c4bbb2e0286568dae1d251905b0a4