以前看LumaQQ(2006版)时做的一点简单记录,主要是关于LumaQQ登陆服务器和通讯时数据包的加密和解密情况。
1
.QQ的登陆
1.1
. 登陆QQ服务器流程
启动登陆程序,输入QQ号码和登陆密码,选取登陆模式,进行网络设置。登陆QQ。
现在做一个测试。将完全在登陆状态下的LumaQQ打开调试窗口,并开始进行调试(点击Start Debug 按钮)。
选择下线,然后再选择上线。我们可以看到下面
捕获到的数据包。
当选择离线时,发送了Logout Packet包,此包并不需要服务器作出回应。再登陆的时候,QQ客户端向服务器发送一个请求登录令牌的数据包(
请求登录令牌
包是不加密的
),服务器接收到请求登录令牌后返回请求登录令牌的回复包给客户端,这个请求登录令牌的回复包是在服务器端生成的。
在
QQ
客户端得到登录令牌
回复包
之后,就会向服务器发送一个登录请求包(有资料声称:这个登录包除了随机密钥本身以外的部分都要用随机密钥进行加密),要求登录。服务器会验证客户端的登录信息是否与服务器上保存的登录信息匹配,返回一个登陆请求回复包。如果匹配就向客户端返回登录成功,不匹配返回登录失败。
![o_20070712lumaqq_login_logout_datapage.png](/images/cppblog_com/dwz-1987/6668/o_20070712lumaqq_login_logout_datapage.png)
登陆成功后,还附带有些其他的数据请求包和回复包,这里暂不讨论其他包。(注:
Request Key Packet
用来请求得到一些操作的密钥,比如文件中转,或者语音视频之类的都有可能)
1.2.
登陆信息保存到本地流程
在登陆数据从数据框输入之后和登陆操作进行之前,程序就会把数据保存到本地的文件上。其中的数据主要包括QQ号码,登陆密码,登陆模式,网络设置信息等等。
这里的登陆密码保存方式会根据登陆模式的不同而采取不同的数据加密方案。
用户从输入框的密码,系统取到原始密码值时马上进行两次md5。保存到变量md5pwd,当保存密码到本地时,如果是选择记住密码模式,则将md5pwd做一次base64再保存;如果是选择是非记住密码模式,则将md5pwd做一次base64之后再做一次md5,再来一次base64,最后在保存。
因为
MD5
的算法是不可逆的,没有办法直接解密。也没有用到密钥之类的东西。
2.
QQ
数据包的加密和解密
2.1.
数据包和密钥
登录数据包也是一个数据包,数据包在网络传输时一般都会经过加密(除了数据包的头部和尾部),登陆包是用初始密钥,即一开始构造QQ客户端对象时候生成的16位的十六进制的随机数(这里称作随机密钥吧),进行对登录数据包加密。加密算法暂时跳过。
服务器接收到请求登陆包后作出反应,向客户端发送一个请求登陆回复包,客户端用上面提到的md5pwd(原始密码的两次MD5后的数据,暂称之为密码密钥吧)作为密钥来解密请求登陆回复包,在请求登陆回复包会包含一个会话密钥,这个会话密钥就是聊天数据包加密和解密的密钥(有资料也提到一种例外,当好友离线的时,你发送信息给他,他下次上线的时候根本就不知道你当时发送信息的会话密钥,这是他会用密码密钥来解密信息数据包)。
这里小总结一下几种包所用到的密钥:
登陆请求包:
随机密钥。
登陆请求回复包:
密码密钥。
信息发送包:
会话密钥。
在线信息接收包:
会话密钥。
离线信息接收包:
密码密钥。
2.2.
数据包的加密和解密算法
QQ
采用了最初的TEA算法做其核心的加密算法,TEA是Tiny Encrypt Arithmetic的缩写。顾名思义就是一种比较简单的小型加密算法,是在1994年由英国剑桥大学的David Wheeler和Roger Needham所发明的一种加密方法。它用一个16字节的密钥去加密一个8字节的明文,得到一个8字节的密文,也可以反向从密文解密出明文。这种算法的可靠性是通过加密轮数而不是算法的复杂度来保证的。
QQ
使用的TEA虽然是标准的TEA,但是QQ在使用这个算法的时候,由于需要加密不定长的数据,所以使用了一些常规的填充办法和交织算法(也就是说,把前一组的加密结果和后一组未加密的结果进行运算,产生新的结果)。QQ消息被分为多个加密单元,每一个加密单元都是8字节,使用TEA进行加密,加密结果再作为下一个单元的密钥。如果明文本身的长度不是8的倍数,那么还要进行填充,使其成为8的倍数。填充的时候会用一个32位随机数存放于明文的开始位置,再在明文的最后用0填充为整个长度是8的倍数。由于会向后反馈,这样即使对于相同的明文,因为使用了不同的随机数,也会产生完全不同的密文。使用这种特殊的填充反馈算法所导致的结果就是,一段密文只能用加密它的密钥进行解密,如果使用不正确的密钥,就无法得到正确的填充结果。最常见的就是解密后得到的填充数值不是0,这样就判断解密失败。
QQ
消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
f
表示加密算法, 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt ) &circ
d
表示解密算法,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ
填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数填充的字节数与原始明文长度有关,填充的方法是:
-------
消息填充算法 -----------
a = (
明文长度 + 10) mod 8 //计算填充长度
if(a
不等于 0) a = 8 - a;
b =
随机数 & 0xF8 | a; //这个的作用是把a的值保存了下来
plain[0] = b; //
然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
plain[1
至 a+2] = 随机数 & 0xFF; // 这里用随机数填充明文的第1到第a+2个字节
plain[a+3
至 a+3+明文长度-1] = 明文; //从a+3字节开始才是真正的明文
plain[a+3+
明文长度, 最后] = 0; //在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
-------
消息填充算法 ------------
3.小结
3.1 小结一下
研究到现在为止,还没有发现密码密钥(即密码的两次MD5的结果)在网络上传输。但是,密码密钥是一个非常重要的密钥,虽然用到这个密钥并不比会话密钥频繁,但是要得到会话密钥首先要取得密码密钥。几个重要的密钥使用流程是这样的:
![r_20070712lumaqq.png](/images/cppblog_com/dwz-1987/6668/r_20070712lumaqq.png)
能正确解析各种包以及包的各部分和取得对应的加密解密密钥就能进行正常的数据通讯。
3.2 留下的问题
密码密钥是否在网络上传送?获取密码密钥是从本地获取还是网络上获取?
轉載自:http://www.blogjava.net/orangewhy/archive/2007/07/12/129829.html