飞信2010分析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
飞信2010分析 – SSI 登录
2010年4月1日| 分类: 飞信哪些事儿| 标签: 一个月前,移动飞信发布了飞信2010版,也就是 V3.6版(飞信2008版为 V3.5),并升级了飞信的通信协 议。
飞信2010版最显著的功能更新就是非移动用户也可以使用飞信了,只需使用一个邮箱注册即可。
但限 制还是比较多的,只能已经开通飞信的人发送消息,注意不是短信,当该用户不在线的时候以离线的消息 发送。
但可以每个月可以免费发送三条短信,如果要发送更多,还得绑定手机号。
。
其实很早就想写关于飞信2010版的内容。
在飞信2010发布没几天我就开始分析 SSI 登录和 SIPC 验证。
SSI 登录很快就搞定了,但 SIPC 验证总是失败。
查了很久都没有结果,于是就放了有两周吧。
两周后的 某一天实在是无聊,翻出以前分析飞信2010的代码,拖出 Reflector 和 VC# Express 又分析一通,总算 搞定了。
。
网上关于飞信2010的资料还少,我还是简单的写出来,和大家分享我的飞信的研究心得吧,如 果我说得不对,别拍我板砖哈~~ 废话少说,简单说下飞信2010版协议的变化: 1、 SSi 登录使用新的 V4版本, 地址是: https:///ssiportal/SSIAppSignInV4.aspx 2、飞信通信协议版本升级为 SIP/C-4.0,主要的操作协议已经是 V4了,比如 GetContactInfoV4 3、通信协议中很多地方为了减少流量都简写了,比如 presence 简写为 pr,local-name 简写为 l
飞信登录其实是分三步的:第一步获取自适应的系统配置,第二步:SSI 登录,第三步:SIPC 登录验证。
先说一下第一步,获取自适应的系统配置。
自适应的配置包含了很多和飞信客户端有关的信息。
其中里面最重要的一个信息就是飞信服务器的地址。
不同的地方的飞信用户登录到了不同的飞信服务器。
比如四川的飞信的用户就登录到四川的飞信服务器上。
至于为什么需要登录到本地的飞信服务器上,我个人觉得从技术上来说可能是做负载均衡,从运营上来说 可能是不同的省的移动子公司的运营方式不同。
完成获取自适应的配置其实很简单,做一个简单的 HTTP POST 就行了。
地址是固定的。
参数大部分也是 固定的。
地址为 /nav/getsystemconfig.aspx,POST 的内容如下
<config><user mobile-no="159xxxxxxxx" /><client type="PC" version="3.6.2000" platform="W5.1" /><servers version="0" /><service-no version="0" /><parameters
version="0" /><hints version="0" /><http-applications version="0" /><client-config version="0" /><services version="0" /></config> 如果模拟飞信客户端登录,只需改变一下 mobile-no,其他可以全不变。
POST /nav/getsystemconfig.aspx HTTP/1.1 User-Agent: IIC2.0/PC 3.6.1860 Host: Content-Length: 286 Connection: Close
<config><user mobile-no="159xxxxxx" /><client type="PC" version="3.6.1860" platform="W5.1" /><servers version="0" /><service-no version="0" /><parameters version="0" /><hints version="0" /><http-applications version="0" /><client-config version="0" /><services version="0" /></config>
HTTP/1.1 200 OK Connection: close Date: Thu, 04 Mar 2010 15:22:04 GMT Server: Microsoft-IIS/6.0 X-Powered-By: X-AspNet-Version: 2.0.50727 Cache-Control: private Content-Type: text/html; charset=utf-8 Content-Length: 13319
<?xml version="1.0" encoding="utf-8" ?> <config carrier="CMCC">
<servers version="117">
<ssi-app-sign-in-v2>https:///ssiportal/SSIAppSignInV4.aspx< /ssi-app-sign-in-v2> <get-pic-code>/nav/GetPicCodeV4.aspx</get-pic-code> <web-cm-service-url></web-cm-service-url> <sipc-proxy>221.130.46.141:8080</sipc-proxy> <sipc-ssl-proxy>221.130.46.141:443</sipc-ssl-proxy> <http-tunnel>HTTP://221.130.46.141/ht/sd.aspx</http-tunnel> <get-uri>/HDS_Pool05/geturi.aspx</get-uri> <sub-service>https:///nav/Subscribe.aspx</sub-service>
<ssi-app-sign-in>https:///ssiportal/SSIAppSignIn.aspx</ssiapp-sign-in>
<ssi-app-sign-out>/ssiportal/SSIAppSignOut.aspx</ssi -app-sign-out> .. </server> ... </config> 下面开始分析飞信2010版的 SSi 登录部分。
什么是 SSI 呢? 我个人理解就是 Single Sign-In,就是单点登录。
做过大型网站都会存在这样一个问题,网站有很多的应用,但都需要用户登录验证时才能使用服务。
但往 往这些应用都在不同的服务器,一个用户登录了这个应用,但打开另一个应用却又需要用户登录验证,因 为在不同的服务器,彼此之间是独立的状态。
为了达到用户在一个应用登录之后就可以自动的登录其他的 应用,可以设置一个专一登录服务器,如果用户登录之后,就把这个状态推送到各个应用服务器,或者应 用服务器在用户访问的时候主动向登录服务器询问当前用户是否登录。
这个实现方法可以不同。
飞信应该采用的是第二种实现方案。
飞信其实是一种混合多种软件体系结构的一个软件。
比如飞信客户端 通信采用的 C/S 软件体系结构, 而获取用户头像, 传输文件等却又是面向 SOA 的软件体系结构 (如 HTTP, SOAP,REST,飞信是用的 HTTP 建立的应用) 。
为了在这不同的软件体系结构共享用户状态,就必须要 设置一个登录服务器来完成用户登录操作,然后返回一个用户标志,这就是 SSIC(Single Sign-In Credential),比如获取用户定义的头像,事实上就是一个 HTTP 的应用,但 HTTP 是无状态的,如何让 服务器判断这个请求是否合法呢?这里就需要把 SSI 登录成功的 SSIC 作为 Cookie 传递给服务器帮助判 断请求是否合法。
如果是基于 HTTP 传输模式的话,这个 SSIC 是放在 Cookie 中的。
飞信是怎么实现 SSI 的呢?其实很简单, 做个 HTTPS 的 GET 就行了。
在飞信2006的时候, 使用的 SignV1 就直接把用户手机号和原始密码作为两个参数传递过去就行了,但飞信2008的就用了 SignV2,新增了验 证图片验证,可以防止恶意程序猜测密码,并且支持把密码加密后作为 digest 传递过去,保障了安全性。
到飞信2010启用了 SignV4验证,同样有验证码验证,并且改变了密码的加密方式。
下面就详细的说明飞信2010采用的 SignV4的 SSI 登录的过程和加密方法。
SSI 登录的地址是:https:///ssiportal/SSIAppSignInV4.aspx 简单的说下参数,以 GET 方式传递,就目前发现的有八个 mobile:手机号 sid:飞信号 domains:获取 SSIC 的域名,不同的域名以分号(;)分开。
不同的域名的 SSIC 是不同的,这里飞信客户 端默认传递了三个域名:%% 这三个域好像都是移 动的域名。
。
v4digest-type:加密的方式。
可以取两个值 1:V4Temp,2:V4 v4digest:加密后的密码,以16进制表示,20字节 pid:验证码图片编号 pic:用户输入的验证码 algorithm:图片生成算法(这个待验证,我觉得更像是验证原因…) 其中飞信号和手机号有且只能有一个,必须的。
(这两个参数都传递是什么结果我还没试过呢) 。
domains 也是必须的,默认值为 %% v4digest-type 和 V4digest 也是必须的。
下面的三个参数:pid,pic,algorithm 只有在需要用户输入验证码验证才有效,而且三者都是必须的。
如果 不需验证,则可以不传递。
当 v4digest-type=1时,v4digest 的算法很简单,就是把 字符串 “:”的 UTF8编码的字 节数组和用户明文密码的 UTF8字节编码数组连接在一起,然后计算 SHA1的值就是加密结果。
主要加密代码如下:
public static String encryptV4(String plainpass) { return doHash(ConvertHelper.string2Byte(":"), ConvertHelper.string2Byte(plainpass)); } 当 v4digest-type=2 时,v4digest 的算法就需要用到 userid 和 v4digest-type=1时加密密码后的结 果。
userid 是一个整型数,把 userid 转换为字节数组,注意低字节在前。
然后和用 v4digest-type=1 时加密结果转换为字节数组连接在一起,再计算 SHA1的值就是加密结果了。
主要加密代码如下:
public static String encryptV4(int userid, String plainpass) { String passHex = encryptV4(plainpass); return doHash(ConvertHelper.int2Byte(userid), ConvertHelper.hexString2ByteNoSpace(passHex)); } 这是加密用的工具函数,就是把两个字节数组合成一个数组然后计算这个数组的 SHA1的值,返回的是16 进制的字节字符串
private static String doHash(byte[] b1, byte[] b2) { byte [] dst = new byte[b1.length+b2.length]; System.arraycopy(b1, 0, dst, 0, b1.length); System.arraycopy(b2, 0, dst, b1.length, b2.length); byte[] res = DigestHelper.SHA1(dst); return ConvertHelper.byte2HexStringWithoutSpace(res);
} 当然用户第一次登录时只有飞信号或者手机号, 所以就直接使用 v4digest-type=1加密方法就行了, 但如 果知道了用户的 userid 就可以使用 v4digest-type=2的加密方式了,比如登录成功之后网络断了的重登 录。
如果服务器返回的状态码是200,表明验证成功,这个时候就会传回用户的 uri,userid,还有最重 要的 ssic,注意这个 ssic 是作为一个 cookie 返回的,并没有在返回的正文中。
其他的数据我也还没有分 析,不过是 XML 的,很容易理解,这里不说了。
当然服务器可能会返回其他的状态码,这里列出一部分。
401:密码错误; 420:输入的验证码不正确; 421:当前登录的用户需要图片验证; 424:登录尝试超过规定次数; 431:用户正在更换飞信号,暂时无法登陆 432:用户已启用新的飞信号,当前飞信号已停止使用 435:您未绑定安全邮箱或手机号
GET /ssiportal/SSIAppSignInV4.aspx?mobileno=159xxxxxx&domains=%3bm1 %&v4digest-type=1&v4digest=66F6B084EC004E25078CF6626A F05E881403C27E HTTP/1.1 User-Agent: IIC2.0/PC 3.6.2000 Host: Connection: Keep-Alive
HTTP/1.1 200 OK Date: Sat, 03 Apr 2010 08:39:11 GMT Server: Microsoft-IIS/6.0 X-Powered-By: X-AspNet-Version: 2.0.50727 Set-Cookie:
ssic=DhIOAABEcX+O5EqnUrCfZQD2/PYXJ12j8mxBOzEiWMJiRzhlfqVrxprxQJcNhAvtusbjSqs JtUq9bX4D+POJlWEyXWDFkCbfR0qwJCbj6z0HnylkvjJ5GkiwMFAVA3bVD8AAeJ0AAA==; path=/ Cache-Control: private Content-Type: text/html; charset=utf-8 Content-Length: 714
<?xml version="1.0" encoding="utf-8" ?> <results status-code="200"> <user uri="sip:123456789@;p=6721" mobile-no="159xxxxx" user-status="101" user-id="987654321"> <credentials> <credential domain="" c="CBIOAABAyrAQQWMLS1Ze6Auwn01TCYTf4zAc9KVcfkQMWc1hrQrkhe1vXI9xYrrSQUDfK5Ys/ af/zLYdK2iwvSmELkTj2sBBtEL1w5vpLguHLWyH5I6+OkRM7nP9+l6PEiQi2eQAAA==" /><credential domain="" c="CRAOAAA4C0dsXXdiP82bkjsB9aGA1L1wNAaXmqddFRx04Dk7uDCZMyri+OTJdwbTMQF0cckDb SgHVNEkcYBIGc8NJspEPxWzykMOGli8/m+mStMo2w==" /><credential domain="" c="CBAOAADDAL1rw2Z0QfyhPSiV/g6BkYjWdCyjDMKKqk3RAWETHRVdH3VwLRxD23TMwrCQ2Hk4L BAieDPC4h2RfK3uTLlKysCd62ZZ44cuth7iiThXtA==" /> </credentials> </user> </results> 当 SSI 登录时返回了421,当前登录操作就需要验证图片验证。
新版 V4的协议在 SIPC 验证注册的过程 中也加入的图片验证的过程,获取验证图片也是同样的地址。
验证图片的获取是用另外的 URL,地址是 /nav/GetPicCodeV4.aspx。
方法 为 GET。
参数就一个,就是验证图片的算法。
(从字面上来说是这样,但这里说的可能不准确,我感觉应该是获取验
证码的原因。
) 。
目前可能的取值为:picc-ChangeMachine,picc-PasswordErrorMax。
不过不用担心,这个参数在需 要验证的时候都可以得到, 比如 SSI 登录的时候, 需要验证的时候通常都会把这个参数写入返回的结果里, 请各位自行分析了,很简单。
直接发起这样的 HTTP 请求然后就可以获得验证图片信息。
返回的是 XML,很容易读取,其中 /results/pic-certificate@id 就是图片的编号,以后服务器验证用户输入是否正确就需要这个图片编号。
/results/pic-certificate@pic 就是经过 base64编码过后的图片,应该是 png 格式的。
使用 base64解 码过后就是图片的二进制代码,保存至文件或者渲染到图片框里就可以了。
GET /nav/GetPicCodeV4.aspx?algorithm=picc-ChangeMachine HTTP/1.1 User-Agent: IIC2.0/PC 3.6.1860 Host:
HTTP/1.1 200 OK Date: Thu, 04 Mar 2010 15:22:08 GMT Server: Microsoft-IIS/6.0 X-Powered-By: X-AspNet-Version: 2.0.50727 Cache-Control: private Content-Type: text/html; charset=utf-8 Content-Length: 3113
<?xml version="1.0" encoding="UTF-8"?> <results> <pic-certificate id="ffe70af4-9748-46ce-ba94-0a900da0674d" pic="/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGB......." /> </results>
注意这里一定要设置 User-Agent: IIC2.0/PC 3.6.2000,不然会出错。
飞信2010版的 SSI 登录分析到这,下篇文章分析 SIPC 验证和注册。
飞信2010分析 – SIPC 验证
2010年4月3日| 分类: 飞信哪些事儿| 标签: OK,接上文,继续分析。
飞信登录的第三部就是连接 SIPC 服务器,验证并获取好友列表等信息。
SIPC 是什么含义呢? SIP 是会话初始协议(Session initializtion Protocol) ,是一个标准的协议,其 RFC 可以在这里下载。
协议的规定如何开始一个会话。
通常和这个协议一起使用的还有 SDP 协议。
但飞信只用了 SIP,并且把 SIP 协议做了一些拓展, 所以后面有个 C。
可能代表 China Mobile。
C 关于 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 服务器上后,在获取好友列表等信息之前是需要完成验证的。
首先是发起注册请求。
(姑且就这样叫吧)
R SIP-C/4.0 F: 123456789 I: 1 Q: 1 R CN: 441F7DBA5C3153B61C0660C622F01354 CL: type="pc" ,version="3.6.1860" ------SIP-C/4.0 401 Unauthoried F: 123456789 I: 1 Q: 1 R W: Digest algorithm="SHA1-sess-v4",nonce="11F38E891D330436110471D742A7C08E",key="CAE3B 6C60FC46B7A6FE4316FBABD4E9CC21DD01E330CE449F5BA46818A51F589C7ECD548BC4F6D8AA 20BDA43FC75F89164E8EB70A20348251AB56B0059452508A516C955BE1463C1B7D82ED97CEDB D03DFD1DF7C5368FF1636A34E855B10BD19B6624DC68BC921771BE8C5F1E3EE1E5EBB1DB41CF 1D0CB4BA41FACC2A54D6AF9010001",signature="57F1AD6CA5082C9BAA8DE5DD5521149903 E9A85E4BDC9BE89CEFE39313DF836319E546AF01FE006F40B7243EF2099D813AEC746EDAE4C4 003AAA88A1DBE6302C20505784D2458F0510B596D9DC32E2BF4E609BCF18EE46822B84D6EACD D463E0833E5D1CEBF6864920E6CB126456DF9A063385AC9828A34467AEDFEFA2B347A2" 请求:这个请求主要是向服务器请求验证的 RSA 公钥 其中 F 是飞信号就是用户 uri 中@前面的数字,如123456789@;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 信息可以参考维基百科: /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 中解析出公钥的代码
/** * 从服务器返回的 key 字符串解析出 RSA 公钥 * @param publicKey 服务器返回的 key 字符串
* @return 解析出来的 RSA 公钥,可以用这个公钥加密数据 * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException
*/ private RSAPublicKey parsePublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException { String modulusText = publicKey.substring(0,0x100);
String exponentText = publicKey.substring(0x100); BigInteger modulus = new BigInteger(1,
ConvertHelper.hexString2ByteNoSpace(modulusText)); BigInteger exponent = new BigInteger(1, ConvertHelper.hexString2ByteNoSpace(exponentText)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKeySpec bobPubKeySpec = new RSAPublicKeySpec(modulus, exponent); RSAPublicKey rsapublicKey = (RSAPublicKey) keyFactory.generatePublic(bobPubKeySpec); return rsapublicKey; } 接下来就是很关键的一步,SIPC 验证注册
R SIP-C/4.0 F: 123456789 I: 1 Q: 2 R A: Digest response="6AC3FEE164709828DCDBA1FC71BAFE9FDD83980DA83959E0993912EA74BF836BC7 6F196F9C99BD71F64732C00BEEEC1A516C134B637EEFA71BBAF26447B5B310BE3BC3A58FD2E6 B22094F16B1CF85F2E5B6AD5C9A60FF6055C7DD8C476A28C97C7A6876176C5EF738FC21CEACB 400190B1BF538EC930429DED246F49A9CE7C90",algorithm="SHA1-sess-v4" AK: ak-value
L: 428
<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=";;;" /><presence><basic value="400" desc="" /></presence></args> -----SIP-C/4.0 200 OK I: 1 Q: 2 R X: 600 L: 12545
<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 就是发送给服务器的结果。
/** * 生成加密结果 * @param publicKey 67Bytes(134Chars) * @param password V4加密的密码,指用 userid 和明文密码加密过后的结果(两次 sha1) , RSA 公钥,从返回的 W 头部的 key 获取值,16进制表示的字节数组
16进制表示的字节数组 20Bytes(40Chars) * @param nonce * @param aeskey * @return 服务器返回的随机字符串,看做字符串 16Bytes(32Chars) AES 算法的密钥,估计是加密或者解密用户配置的 32Bytes(64Chars) 生成的结果,16进制表示的字节数组
* @throws NoSuchAlgorithmException * @throws InvalidKeySpecException */ public String generate(String publicKey, String password, String nonce, String aeskey) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] pb = ConvertHelper.hexString2ByteNoSpace(password); byte[] nb = ConvertHelper.string2Byte(nonce); byte[] ab = ConvertHelper.hexString2ByteNoSpace(aeskey);
byte[] res = new byte[pb.length+nb.length+ab.length]; System.arraycopy(nb, 0, res, 0, nb.length); System.arraycopy(pb, 0, res, nb.length, pb.length); System.arraycopy(ab, 0, res, pb.length+nb.length, ab.length);
byte[] some = encrypt(parsePublicKey(publicKey), res);
return ConvertHelper.byte2HexStringWithoutSpace(some); } 后面的参数不是很重要,也简单说一下吧,machine-code 是指的是当前活动网卡的 MAC 地址,可以固 定。
caps 可能是 capbilities 的缩写,在 HTTP 连接方式下为 ff,在直接连接和 SSL 连接方式下为1ff, user-info 里面的信息可以从 SSI 登录成功中得到,后面一大堆的 version 指的是本地数据的版本,类似 于版本控制, 如果和服务器的版本相同就不回复相应的信息了。
可以固定的设置为0, 后面还有 presence, 这个是登录状态的,不同的取值表示的含义不同:400-在线,0-隐身,600-忙碌,100-离开,desc 可 以对现在这个状态加以描述,默认为空。
这里给出 RSA 加密方法(就是上面的 encrypt()方法)
/** * 使用 RSA 加密字节数组 * @param publicKey RSA 公钥 * @param obj 要加密的字节数组
* @return byte[] 加密后的字节数组 */ protected byte[] encrypt(RSAPublicKey publicKey, byte[] obj) { if (publicKey != null) { try { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(obj); } catch (Exception e){ e.printStackTrace(); }
} return null; }
如果一切正常的话,服务器就会返回200,登录成功。
返回的数据很多。
有登录记录,个人信息,好友分 组,好友列表,个人配置等。
当然,如果你验证的时候传递了记录的版本号,如果和服务器相同的话,服 务器就不会返回数据了 。
因为是XML格式的,很容易理解,不赘述了。
如果服务返回的是421 Extension Required,这就需要验证了。
SIP-C/4.0 421 Extension Required I: 1 Q: 2 R W: Verify algorithm="picc-ChangeMachine",type="GeneralPic" L: 191
<results><reason text="飞信发现您本次变更了登录地点。
为保证您的帐号安全,需要您输入验证 码,这可以防止恶意程序的自动登录。
" tips=""/></results> 需要验证的原因也给出来了。
在上一篇文章中详细的说明了如何获取验证图片。
这里也一样。
飞信 SSI 登 录和 SIPC 注册的验证图片的获取是同一个地址。
获取验证图片需要一个参数 alg,这里可以从 SIP 返回的 W 头的 algorithm 中取得。
获取图片之后,得到了一个图片编号,即 pid 和图片数据,把图片解码出来保存为文件或者渲染到图片控 件并让用户识别后,会得到用户输入的验证字符。
假设图片 pid 为6cbcdacb-44c2-4bd3-82a3-07d9e2e3f967,用户识别上面的字符为:qyxfyd。
获取到这些信息之后,就可以再一次发起注册请求,基本上和上一次的请求相同,只不过多了一个 SIP 头, Verify。
重复第二步:SIPC 验证注册
R SIP-C/4.0 F: 123456789
I: 1 Q: 2 R A: Digest response="3C10B5F148EA52FB42441F640D235D27556920D6753624C8CDABFC0254FCDA8952 2A5B72FE37BC8D828BF9B7EBB1859B8BB4558D56A83115E724541B4B34316B4F56BBD76002EB DB44AC2E65FC000913E737242A12CB52A6B83A3EE6F38AD36DDEA2528667CDE547DBF57A40E7 529D75096835AB621F56750B9857614836C43D",algorithm="SHA1-sess-v4" AK: ak-value A: Verify response="qyxfyd",algorithm="picc-ChangeMachine",type="GeneralPic",chid="6cb cdacb-44c2-4bd3-82a3-07d9e2e3f967" L: 436
<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" /></user-info><credentials domains=";;;" /><presence><basic value="400" desc="" /></presence></args> 很容易看出 Verify 头中,response 就是用户输入的字符串,algorithm 就是验证图片的算法,chid 就是 图片的 pid。
如果很幸运,验证成功,就会和上面的返回结果一样,但假如用户识别错了,验证失败,就会返回420, 如下 buddy-attributes="v4default"
SIP-C/4.0 420 Bad Extension F: 685592830 I: 1 Q: 2 R
W: Verify algorithm="picc-ChangeMachine",type="GeneralPic" L: 191
<results><reason text="飞信发现您本次变更了登录地点。
为保证您的帐号安全,需要您输入验证 码,这可以防止恶意程序的自动登录。
" tips=""/></results> 这也一样,继续上面的操作,获取验证图片,提示用户识别,再注册验证,直到登录成功。
当上面的验证成功之后,你当前已经是在线了,就可以向服务器发起其他请求了,比如添加好友,发送消 息等等。
假如还需要支持群,就需要获取群列表,群成员消息。
但这个时候还不能收到好友的在线情况的,刚才只是返回了好友的列表,好友的状态还是没有发送过来, 登录之后如何处理才能获得好友状态,请留意我下篇文章。
更新记录: 2010.04.27 修正了 key 的长度错误, 以前写的是67字节, 修改为131字节, 我没有仔细数。
感谢 Felix 。
指出! ! 2010.05.05 添加文中缺少的 encrypt 方法,感谢 supertrouper 指出! !
飞信2010分析 – 成功登录之后
2010年4月12日| 分类: 飞信哪些事儿| 标签: 文章更新晚了,请大家见谅,上周的事情比较多,呵呵~ 标题里面的成功登录之后指的是 SIPC 验证成功之后。
也就是说登录成功是指在完成第一步获取系统配置 信息,SSI 登录成功,SIPC 验证成功之后才能算登录成功。
在 SIPC 验证成功之后,服务器会返回很多信息,登录记录,个人信息,好友分组,好友列表,个人配置 等,这些信息在2008的协议里面都是逐个发起请求来获取的,现在2010的协议登录成功就直接把这些所 有的数据都返回来了,这个改进还不错,减少了很多操作。
如果没有返回某一类的信息,表明本地存储的 信息版本和服务器一致,就直接读取缓存,如果不做缓存,也就是说本地版本总是0,服务器也会总是返回 数据。
这里再详细的的说下其中的好友列表,这是其中的一个好友节点: <b i="987654321" u="sip:123456789@;p=1234" n=" 阿 呆 " l="5" f="0" r="1" o="0" p="identity=1;phone=1;email=1;birthday=1;presence=1;"/> 为了便于分析,这里转为完整的拼写如下: <buddy user-id="987654321" uri="sip:123456789@;p=1234"
local-name=" 阿 呆 " buddy-lists="5" feike-read-version="0" relation-status="1"
online-notify="0" permission="identity=1;phone=1;email=1;birthday=1;presence=1;"/> 简单的说下简写代表的含义: b = buddy : 好友,飞信好友分成两种,飞信好友和手机好友,顾名思义,飞信好友就是开通了飞信的好 友,其中的 uri 的格式是 sip:123456789@;p=1234;sip 和@符号之间的就是飞信号, 登录的时候可以用这个飞信号登录。
p=1234我也不知道什么含义。
手机好友指的是没有开通飞信的好友, 其 uri 的格式 tel:159xxxxxxxxx,后面是手机号。
没有开通飞信的好友没有昵称,Email 等好友信息。
i = user-id : 用户编号。
注意这个不是飞信号。
所谓的用户编号指的是移动所有的用户如果使用飞信的话 就会自动分配一个用户编号。
这个编号无论用户开通还是没有开通飞信这个编号都存在而且不会改变。
飞 信的 URI 可能会改变,比如用户原来没有开通飞信现在开通了飞信 URI 就会变。
u = uri : URI。
用户标志,类似于 URL。
很重要,飞信里面的操作都是基于 URI 来识别用户的。
如发送 消息说明给那个用户发送就是使用 URI。
实在是搞不懂为啥飞信使用两个唯一的字段来标志用户。
n = local-name : 用户设置的本地昵称。
这个是用户设置,不是好友的昵称,如果用户没有设置本地昵 称,就为空,如果要显示好友昵称,就需要发起获取好友详细信息请求来获取昵称。
l = buddy-lists : 好友所属的分组编号列表,多个列表以;隔开。
飞信做得挺有意思的,一个好友可以属 于多个分组,实现起来也麻烦,这个完全是个错误的需求。
。
f = feike-read-version : 这个是判断飞客是否有更新的 r = relation-status : 好友和用户的关系。
有三个值 1:添加对方后对方没有回复 2:对方同意了你添 加好友的请求 3:对方拒绝了你添加好友的请求 o = online-notify : 这个含义未知。
。
p = permission : 该好友对用户设置的权限。
飞信的权限有五种: identity=1: 向对方公开我手机号码和姓名 birthday=1: 向对方公开我的生日和年龄 phone =1: 向对方公开我的电话号码 (不是手机。
。
飞信为啥这里还搞个电话号码。
) 。
email =1: 向对方公开我的电子邮件 business=1: 向对方公开我的商务信息 presence=1: 向对方公开我的在线的状态信息,如果不公开,默认为离线状态 当相应的值为1的时候,就表明向对方公开相应的信息。
飞信的权限设置的很复杂很乱,很多都是没有必要 的。
当对方权限设置为0的时候,获取对方的信息的时候就会是空值。
真搞不懂飞信把权限做的这么复杂。
chat-friend 这个什么含义还不清楚,black-list 里面的好友列表不能看见用户的在线状态,也不能向用户 发送信息。
上篇文章最后说登录成功好友的状态信息没有发送过来,这就需要做个请求,订阅好友的在线状态。
为啥要定义好友的状态呢?订阅的目的就是当好友状态改变的时候,能及时发送给客户端。
其实这里的状 态包含了很多的信息,比如用户新更改了昵称,个性签名,更换了头像,或者用户登录,下线了等等,都 是通过状态改变发送给客户端的。
如果不订阅,服务器是不会主动的发送状态改变信息到客户端的。
SUB SIP-C/4.0 F: 123456789 I: 10 Q: 1 SUB N: PresenceV4 L: 87
。