飞信2010分析 – SIPC验证

本文深入分析了飞信2010版客户端登录过程中SIPC验证的具体步骤,包括SIPC服务器连接配置、RSA公钥获取、密码加密验证及图形验证码处理等内容。

 

飞信2010分析 – SIPC验证

2010年4月3日  | 分类: 飞信哪些事儿  | 标签:

OK,接上文,继续分析。
飞信登录的第三部就是连接SIPC服务器,验证并获取好友列表等信息。
SIPC是什么含义呢? SIP是会话初始协议(Session initializtion Protocol),是一个标准的协议,其RFC可以在这里下载。协议的规定如何开始一个会话。通常和这个协议一起使用的还有SDP协议。
但飞信只用了SIP,并且把SIP协议做了一些拓展,所以后面有个C。C可能代表China Mobile。关于SIP协议的格式和移动在SIPC上的拓展和不同,请参照nathan2007的文章。这里就不多说了。

上文说过,飞信SIPC服务器的地址是在第一步获取自适应配置中获得,有三个配置有用。
含义    位置         结果举例
标准SIPC直连接 /config/server/sipc-proxy   221.130.46.141:8080
SSLSIPC连接  /config/server/sipc-ssl-proxy  221.130.46.141:443
HTTP代理连接   /config/server/http-tunnel   HTTP://221.130.46.141/ht/sd.aspx

从上面可以看出飞信支持三种连接方式,
标准SIPC连接方式就是直接连接服务器的8080端口,SIPC信令直接放在TCP包中,
SSLSIPC连接方式连接服务器的443端口,虽说连接的是443端口,却没有使用SSL加密,仍是明文传输,不做任何处理,和标准直连接没有任何区别,只是端口号变了而已。
HTTP代理连接方式是在只能访问80端口的情况下才启用的连接方式,使用POST方式,SIPC信令就放在POST的数据包中,这个我会详细的写文章分析的。

可见飞信对网络环境的适应能力是非常强的。基本能在限制比较多的网络环境中登录成功。还有个细节不知各位注意没,标准直连接和HTTP连接是连接到同一个服务器上,这就需要这个服务器同时运行两个服务:SIPC服务和WEB服务,这对服务器的稳定性和性能要求还是比较高的。

飞信客户端连接到飞信SIPC服务器上后,在获取好友列表等信息之前是需要完成验证的。

首先是发起注册请求。(姑且就这样叫吧)

01. R fetion.com.cn SIP-C/4.0
02. F: 123456789
03. I: 1
04. Q: 1 R
05. CN: 441F7DBA5C3153B61C0660C622F01354
06. CL: type="pc" ,version="3.6.1860"
07. -------
08. SIP-C/4.0 401 Unauthoried
09. F: 123456789
10. I: 1
11. Q: 1 R
12. W: Digest algorithm="SHA1-sess-v4",nonce="11F38E891D330436110471D742A7C08E",key="CAE3B6C60FC46B7A6FE4316FBABD4E9CC21DD01E330CE449F5BA46818A51F589C7ECD548BC4F6D8AA20BDA43FC75F89164E8EB70A20348251AB56B0059452508A516C955BE1463C1B7D82ED97CEDBD03DFD1DF7C5368FF1636A34E855B10BD19B6624DC68BC921771BE8C5F1E3EE1E5EBB1DB41CF1D0CB4BA41FACC2A54D6AF9010001",signature="57F1AD6CA5082C9BAA8DE5DD5521149903E9A85E4BDC9BE89CEFE39313DF836319E546AF01FE006F40B7243EF2099D813AEC746EDAE4C4003AAA88A1DBE6302C20505784D2458F0510B596D9DC32E2BF4E609BCF18EE46822B84D6EACDD463E0833E5D1CEBF6864920E6CB126456DF9A063385AC9828A34467AEDFEFA2B347A2"

请求:这个请求主要是向服务器请求验证的RSA公钥
其中F是飞信号就是用户uri中@前面的数字,如123456789@fetion.com.cn;p=1234,I是CallId,Q是Sequence,我仍然没有找到规律。。。。(详细说明IQ)
CN:是Cnoce,是客户端随机生成的16字节的16进制表示的字符串,可能服务器需要用这个来生成RSA的密钥的吧,这个没法验证了,只能猜测
CL:是Client,发送的客户端的版本号和平台类型,固定

回复:RSA公钥和一个随机字符,用于登录验证
返回的状态码是401,需要验证
nonce:这个就是一个服务器生成随机字符串,可能根据请求中CN来生成,仅用于验证,没有含义。
key: 这个比较重要,RSA算法中的公钥,使用16进制表示,转换为字节数组后共131字节。后面的signature,也是16进制表示的字节数组,共128字节(256字符),在目前还没有发现含义,至少在登录过程中没有使用,暂且忽略。

因为飞信验证用到了RSA算法,我对算法也不是很懂,上百度google了一下,大致了解了点,可能部分朋友对RSA不熟,我也简单的说明下吧。
RSA算法是第一个能同时用于加密和数字签名的算法。安全性依赖于大素数分解。采用不对称加密和解密。
RSA可以用于数据加密。首先服务器生成一个密钥对:一个公钥和私钥,公钥用于加密,私钥用于解密。服务器保存好私钥,然后把公钥发送给客户端,客户端用这个公钥加密一些数据,并发回给服务器,服务器用刚才保存的私钥解密。公钥是公开的,任何人都可以使用公钥加密发送给服务器,但私钥是不公开的,只有公钥的发布才会持有,公钥加密的信息只有私钥才能解密。
可以看出,RSA可以保证数据在传输过程中的安全性,因为只有私钥才能解密,即使知道了公钥也没用。
当然反过来用也行,私钥加密过的数据,只有公钥才能解密,这个可以用于数字签名。
私钥的参数很多,用不上就不说了,公钥的参数有两个:
modulus:128字节 加密系数,主要的参数
publicExponent:3字节 公共系数,一般是固定的,0×010001
详细的RSA信息可以参考维基百科:http://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95

回到飞信。飞信主要用RSA来做数据加密。在注册请求中返回的W头域中的key就是RSA的公钥,前64字节(也就是128个字符)是modulus,后面的3字节(6个字符)是publicExponent。使用这个公钥来加密用户密码,nonce, Aeskey。下面会有详细的说明。
给出从key中解析出公钥的代码

01. /**
02.  * 从服务器返回的key字符串解析出RSA公钥
03.  * @param publicKey     服务器返回的key字符串
04.  * @return  解析出来的RSA公钥,可以用这个公钥加密数据
05.  * @throws NoSuchAlgorithmException
06.  * @throws InvalidKeySpecException
07.  */
08. private RSAPublicKey parsePublicKey(String publicKey) throwsNoSuchAlgorithmException, InvalidKeySpecException
09. {
10.     String modulusText  = publicKey.substring(0,0x100);
11.     String exponentText = publicKey.substring(0x100);
12.     BigInteger modulus  = new BigInteger(1, ConvertHelper.hexString2ByteNoSpace(modulusText));
13.     BigInteger exponent = new BigInteger(1, ConvertHelper.hexString2ByteNoSpace(exponentText));
14.     KeyFactory keyFactory = KeyFactory.getInstance("RSA");
15.     RSAPublicKeySpec bobPubKeySpec = new RSAPublicKeySpec(modulus, exponent);
16.     RSAPublicKey rsapublicKey = (RSAPublicKey) keyFactory.generatePublic(bobPubKeySpec);
17.     return  rsapublicKey;
18. }

接下来就是很关键的一步,SIPC验证注册

01. R fetion.com.cn SIP-C/4.0
02. F: 123456789
03. I: 1
04. Q: 2 R
05. A: Digest response="6AC3FEE164709828DCDBA1FC71BAFE9FDD83980DA83959E0993912EA74BF836BC76F196F9C99BD71F64732C00BEEEC1A516C134B637EEFA71BBAF26447B5B310BE3BC3A58FD2E6B22094F16B1CF85F2E5B6AD5C9A60FF6055C7DD8C476A28C97C7A6876176C5EF738FC21CEACB400190B1BF538EC930429DED246F49A9CE7C90",algorithm="SHA1-sess-v4"
06. AK: ak-value
07. L: 428
08.  
09. <args><device machine-code="001D0936BCB6" /><caps value="1ff" /><events value="7f" /><user-info mobile-no="159xxxxxx" user-id="987654321"><personal version="0" attributes="v4default" /><custom-config version="0" /><contact-list version="0"   buddy-attributes="v4default" /></user-info><credentials domains="fetion.com.cn;m161.com.cn;www.ikuwa.cn;games.fetion.com.cn" /><presence><basic value="400" desc="" /></presence></args>
10. ------
11. SIP-C/4.0 200 OK
12. I: 1
13. Q: 2 R
14. X: 600
15. L: 12545
16.  
17. <results><client public-ip="222.210.18.145" login-place="" last-login-ip="222.210.26.134" last-login-place="" last-login-time="4/1/2010 7:55:38 PM"/>.....

上面说到验证需要用的RSA,这里的response就是用第一步返回的key进行RSA加密后的结果。加密的内容只有三个数据:第一步服务器返回的nonce,V4加密过的密码,AESkey。
注意这里的密码是指用userid和明文密码加密过后的结果(两次sha1),AESkey是AES算法的密钥,估计是加密或者解密用户配置的,没有做验证,32字节,可以随机生成就行了。
假设这里的用户密码v4加密后的结果和AESKey都是用16进制表示的,nonce别把当字符串看,
要加密的数据就是
data = hex2byteArray(password)+getUTF8ByteArray(nonce)+hex2byteArray(AESKey)
(+表示字符数组的连接)
注意nonce不是转换为字节数组,而是获取UTF8编码的字节数组。我在这里郁闷了很久。。
rsakey = parsePublicKey(key);
resByteArray = RSAencrypt(data, rsakey);
response = byteArray2Hex(resByteArray);
response就是发送给服务器的结果。

01. /**
02.  * 生成加密结果
03.  * @param publicKey     RSA公钥,从返回的W头部的key获取值,16进制表示的字节数组 67Bytes(134Chars)
04.  * @param password      V4加密的密码,指用userid和明文密码加密过后的结果(两次sha1),16进制表示的字节数组 20Bytes(40Chars)
05.  * @param nonce         服务器返回的随机字符串,看做字符串 16Bytes(32Chars)
06.  * @param aeskey        AES算法的密钥,估计是加密或者解密用户配置的 32Bytes(64Chars)
07.  * @return              生成的结果,16进制表示的字节数组
08.  * @throws NoSuchAlgorithmException
09.  * @throws InvalidKeySpecException
10.  */
11. public String generate(String publicKey, String password, String nonce, String aeskey)throws NoSuchAlgorithmException, InvalidKeySpecException
12. {
13.     byte[] pb = ConvertHelper.hexString2ByteNoSpace(password);
14.     byte[] nb = ConvertHelper.string2Byte(nonce);
15.     byte[] ab = ConvertHelper.hexString2ByteNoSpace(aeskey);
16.  
17.     byte[] res = new byte[pb.length+nb.length+ab.length];
18.     System.arraycopy(nb, 0, res, 0, nb.length);
19.     System.arraycopy(pb, 0, res, nb.length, pb.length);
20.     System.arraycopy(ab, 0, res, pb.length+nb.length, ab.length);
21.  
22.     byte[] some = encrypt(parsePublicKey(publicKey), res);
23.  
24.     return ConvertHelper.byte2HexStringWithoutSpace(some);
25. }

后面的参数不是很重要,也简单说一下吧,machine-code是指的是当前活动网卡的MAC地址,可以固定。caps可能是capbilities的缩写,在HTTP连接方式下为ff,在直接连接和SSL连接方式下为1ff,user-info里面的信息可以从SSI登录成功中得到,后面一大堆的version指的是本地数据的版本,类似于版本控制,如果和服务器的版本相同就不回复相应的信息了。可以固定的设置为0,后面还有presence,这个是登录状态的,不同的取值表示的含义不同:400-在线,0-隐身,600-忙碌,100-离开,desc可以对现在这个状态加以描述,默认为空。

这里给出RSA加密方法(就是上面的encrypt()方法)

01. /**
02.  * 使用RSA加密字节数组
03.  * @param publicKey  RSA公钥
04.  * @param obj  要加密的字节数组
05.  * @return byte[] 加密后的字节数组
06.  */
07. protected byte[] encrypt(RSAPublicKey publicKey, byte[] obj) {
08.     if (publicKey != null) {
09.         try {
10.             Cipher cipher = Cipher.getInstance("RSA");
11.             cipher.init(Cipher.ENCRYPT_MODE, publicKey);
12.             return cipher.doFinal(obj);
13.         catch (Exception e){
14.             e.printStackTrace();
15.         }
16.     }
17.     return null;
18. }

如果一切正常的话,服务器就会返回200,登录成功。返回的数据很多。有登录记录,个人信息,好友分组,好友列表,个人配置等。当然,如果你验证的时候传递了记录的版本号,如果和服务器相同的话,服务器就不会返回数据了 。因为是XML格式的,很容易理解,不赘述了。

如果服务返回的是421 Extension Required,这就需要验证了。

1. SIP-C/4.0 421 Extension Required
2. I: 1
3. Q: 2 R
4. W: Verify algorithm="picc-ChangeMachine",type="GeneralPic"
5. L: 191
6.  
7. <results><reason text="飞信发现您本次变更了登录地点。为保证您的帐号安全,需要您输入验证码,这可以防止恶意程序的自动登录。" tips=""/></results>

需要验证的原因也给出来了。在上一篇文章中详细的说明了如何获取验证图片。这里也一样。飞信SSI登录和SIPC注册的验证图片的获取是同一个地址。
获取验证图片需要一个参数alg,这里可以从SIP返回的W头的algorithm中取得。
获取图片之后,得到了一个图片编号,即pid和图片数据,把图片解码出来保存为文件或者渲染到图片控件并让用户识别后,会得到用户输入的验证字符。

假设图片pid为6cbcdacb-44c2-4bd3-82a3-07d9e2e3f967,用户识别上面的字符为:qyxfyd。
获取到这些信息之后,就可以再一次发起注册请求,基本上和上一次的请求相同,只不过多了一个SIP头,Verify。

重复第二步:SIPC验证注册

01. R fetion.com.cn SIP-C/4.0
02. F: 123456789
03. I: 1
04. Q: 2 R
05. A: Digest response="3C10B5F148EA52FB42441F640D235D27556920D6753624C8CDABFC0254FCDA89522A5B72FE37BC8D828BF9B7EBB1859B8BB4558D56A83115E724541B4B34316B4F56BBD76002EBDB44AC2E65FC000913E737242A12CB52A6B83A3EE6F38AD36DDEA2528667CDE547DBF57A40E7529D75096835AB621F56750B9857614836C43D",algorithm="SHA1-sess-v4"
06. AK: ak-value
07. A: Verify response="qyxfyd",algorithm="picc-ChangeMachine",type="GeneralPic",chid="6cbcdacb-44c2-4bd3-82a3-07d9e2e3f967"
08. L: 436
09.  
10. <args><device machine-code="001D0936BCB6" /><caps value="1ff" /><events value="7f" /><user-info mobile-no="159xxxxxx" user-id="987654321"><personal version="0" attributes="v4default" /><custom-config version="318214543" /><contact-list version="0"   buddy-attributes="v4default" /></user-info><credentials domains="fetion.com.cn;m161.com.cn;www.ikuwa.cn;games.fetion.com.cn" /><presence><basic value="400" desc="" /></presence></args>

很容易看出Verify头中,response就是用户输入的字符串,algorithm就是验证图片的算法,chid就是图片的pid。
如果很幸运,验证成功,就会和上面的返回结果一样,但假如用户识别错了,验证失败,就会返回420,如下

1. SIP-C/4.0 420 Bad Extension
2. F: 685592830
3. I: 1
4. Q: 2 R
5. W: Verify algorithm="picc-ChangeMachine",type="GeneralPic"
6. L: 191
7.  
8. <results><reason text="飞信发现您本次变更了登录地点。为保证您的帐号安全,需要您输入验证码,这可以防止恶意程序的自动登录。" tips=""/></results>

这也一样,继续上面的操作,获取验证图片,提示用户识别,再注册验证,直到登录成功。

当上面的验证成功之后,你当前已经是在线了,就可以向服务器发起其他请求了,比如添加好友,发送消息等等。假如还需要支持群,就需要获取群列表,群成员消息。
但这个时候还不能收到好友的在线情况的,刚才只是返回了好友的列表,好友的状态还是没有发送过来,登录之后如何处理才能获得好友状态,请留意我下篇文章。

更新记录:
2010.04.27 修正了key的长度错误,以前写的是67字节,修改为131字节,我没有仔细数。。感谢 Felix指出!!
2010.05.05 添加文中缺少的encrypt方法,感谢 supertrouper指出!!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值