XMPP(Extensible Messaging and Presence Protocol)是一个应用于实时通信的开放协议,定义了有关即时消息通信的各方面内容,本文主要是关于XMPP安全机制的介绍以及设计实现思考。
XMPP包含一个保证流安全的方法来防止篡改和偷听,包括两个层次的安全机制,分别是TLS(Tansport Layer Security)和 SASL(Simple Authentication Security Layer)。
TLS主要用于保证传输通道安全,SASL用于用户鉴权认证,协商流程如下:
1. 客户端发起,流初始化(建立TCP连接,发送如下格式数据)
<stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
2. 服务端应答流初始化
<stream:stream from='im.example.com' id='t7AMCin9zjMNwQKDnplntZPIDEI=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
3. 服务端发送TLS流特征说明
<stream:features> <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'> <required/> </starttls> </stream:features>
4. 客户端发起TLS握手
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
5. 服务端应答
-- 握手成功,继续
<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
-- 握手失败,结束流,关闭TCP连接
<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> </stream:stream>
6. 握手成功后,客户端重新初始化加密流,并采用安全加密传输(通常由SSL实现),注意:这一步之后的交互数据全部经过加密传输TLS协商完成。
<stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
7. 服务端应答加密流初始化
<stream:stream from='im.example.com' id='vgKi/bkYME8OAj4rlXMkpucAqe4=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>8.服务端发送SASL特征说明,mechanism指出了服务端支持的认证机制,有关SASL认证的机制可参考RFC4422
<stream:features> <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <mechanism>EXTERNAL</mechanism> <mechanism>SCRAM-SHA-1-PLUS</mechanism> <mechanism>SCRAM-SHA-1</mechanism> <mechanism>PLAIN</mechanism> </mechanisms> </stream:features>
9. 客户端选择认证机制
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AGp1bGlldAByMG0zMG15cjBtMzA=</auth>
以下认证过程根据选择的认证机制有所不同,实践中真正的实现一般就具体采用一种认证,依赖具体的用户权限系统进行设计。
这里说说其中一种常见Challenge-Response认证机制
客户端在发送<auth>请求认证时,如上xml片段所示,<auth>元素中包含了一段BASE64编码的字符串,可以是用户ID(UID)向服务端表明身份id。
服务端接收到认证请求后,发回挑战码,挑战码由服务器每次随机生成(挑战码也经过BASE64编码)
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg== </challenge>
客户端接收到挑战码后,根据用户输入的密码(原文)按注册用户时保存密码采用Hash算法进行同样的计算,得到与服务后端数据库存储的密码Hash同样的值,再以此为种子对挑战码进行特定算法计算。
具体算法可以用对称加密、二次加盐hash等,经过计算后的挑战码作为响应发回给服务端,如下:
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i T0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAw MDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20i LHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNo YXJzZXQ9dXRmLTgK </response>
服务端根据之前提供的UID获取用户保存的密码hash值,对响应码进行相同的算法计算后与客户端传递的挑战码响应进行碰撞认证
-- 成功,返回
<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
-- 失败,返回
<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> </stream:stream>结束流,并关闭TCP连接