飞信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服务器上后,在获取好友列表等信息之前是需要完成验证的。

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

R fetion.com.cn 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="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中解析出公钥的代码

/**
 * 从服务器返回的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 fetion.com.cn SIP-C/4.0
F: 123456789
I: 1
Q: 2 R
A: Digest response="6AC3FEE164709828DCDBA1FC71BAFE9FDD83980DA83959E0993912EA74BF836BC76F196F9C99BD71F64732C00BEEEC1A516C134B637EEFA71BBAF26447B5B310BE3BC3A58FD2E6B22094F16B1CF85F2E5B6AD5C9A60FF6055C7DD8C476A28C97C7A6876176C5EF738FC21CEACB400190B1BF538EC930429DED246F49A9CE7C90",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="fetion.com.cn;m161.com.cn;www.ikuwa.cn;games.fetion.com.cn" /><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		RSA公钥,从返回的W头部的key获取值,16进制表示的字节数组 67Bytes(134Chars)
 * @param password		V4加密的密码,指用userid和明文密码加密过后的结果(两次sha1),16进制表示的字节数组 20Bytes(40Chars)
 * @param nonce			服务器返回的随机字符串,看做字符串 16Bytes(32Chars)
 * @param aeskey		AES算法的密钥,估计是加密或者解密用户配置的 32Bytes(64Chars)
 * @return				生成的结果,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 fetion.com.cn SIP-C/4.0
F: 123456789
I: 1
Q: 2 R
A: Digest response="3C10B5F148EA52FB42441F640D235D27556920D6753624C8CDABFC0254FCDA89522A5B72FE37BC8D828BF9B7EBB1859B8BB4558D56A83115E724541B4B34316B4F56BBD76002EBDB44AC2E65FC000913E737242A12CB52A6B83A3EE6F38AD36DDEA2528667CDE547DBF57A40E7529D75096835AB621F56750B9857614836C43D",algorithm="SHA1-sess-v4"
AK: ak-value
A: Verify response="qyxfyd",algorithm="picc-ChangeMachine",type="GeneralPic",chid="6cbcdacb-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"   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,如下

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指出!!

  1. 2010年4月3日20:52

    第一时间订阅了你的博客,挺佩服你在飞信上的研究
    真是小母牛拉火车–牛B轰轰-_-

    [回复]

  2. 2010年4月3日20:56

    @Terry
    你客气了,你做的Web飞信也很棒,很实用哦~

    [回复]

  3. 可可
    2010年4月30日10:41

    能不能请问一下 byte[] some = encrypt(parsePublicKey(publicKey), res); encrypt是怎么回事??

    [回复]

  4. money123a
    2010年5月5日14:07

    R fetion.com.cn SIP-C/4.0
    F: 123456
    I: 1
    Q: 1 R
    CN: 691285FC447A209D7A746EE9444ED071
    CL: type=”pc” ,version=”3.6.2020″

    登录都成功了,但到了SIP注册了没有任何反应,请问什么原因

    [回复]

    lys 回复:

    一样的情况啊,请问,你解决了吗?这个问题!谢谢!

    [回复]

  5. supertrouper
    2010年5月5日16:20

    到了sip注册那步返回了500错误,请问是什么原因?

    SIP-C/4.0 500 Server Internal Error
    I: 1
    Q: 2 R

    [回复]

  6. supertrouper
    2010年5月5日16:26

    请问V4的验证的byte[] some = encrypt(parsePublicKey(publicKey), res); encrypt是怎么回事??

    [回复]

  7. supertrouper
    2010年5月5日16:28

    我用下面的方法做了加密,结果返回500错误。。。。
    public static byte[] encryptByPublicKey(byte[] data, RSAPublicKey key) throws Exception {

    Cipher cipher = Cipher.getInstance(”RSA”);
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] cipherData = cipher.doFinal(data);
    return cipherData;

    }

    [回复]

  8. 2010年5月5日16:30

    @supertrouper
    encrypt其实就是用解析出来的RSA公钥加密数据。没仔细看,现在把源代码贴上去哈。

    [回复]

  9. 2010年5月5日16:37

    @supertrouper
    已经更新了。

    [回复]

  10. supertrouper
    2010年5月5日17:00

    我按照你写的上面的方法做了加密生成的response返回了500 Server Internal Error,response和正常的response的位数是一样的。

    如果用下面的加密返回406 Not Acceptable
    public static byte[] encryptByPublicKey(byte[] data, RSAPublicKey key) throws Exception {
    org.bouncycastle.jce.provider.BouncyCastleProvider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
    Cipher cipher = Cipher.getInstance(”RSA”,provider);
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] cipherData = cipher.doFinal(data);
    return cipherData;

    }

    你是否做了验证?上面的加密方法能返回200吗?

    [回复]

  11. 2010年5月5日17:08

    @supertrouper
    我当然验证了,只是我代码有点乱没有全部贴出来,等明天我整理下代码发出来吧·

    [回复]

  12. supertrouper
    2010年5月5日18:25

    晕倒,我的sip报文的envlope和payload之间多了一个换行。。。

    [回复]

  13. supertrouper
    2010年5月5日18:25

    谢谢了

    [回复]

  14. 2010年7月14日13:43

    HTTP代理连接 /config/server/http-tunnel 方式,表单提交到哪个URL? 为什么我抓包的时候没有抓到HTTP这种方式?

    [回复]

    solosky 回复:

    @fengsage, 关于HTTP模式我会些文章分析的,敬请留意。

    [回复]

  15. difordill
    2010年7月24日17:26

    我也很奇怪不知道那个url咋来的!还有加密的产生的结果位数问题…

    [回复]

    solosky 回复:

    @difordill, 请问是那个URL呢?

    [回复]

  16. 2010年7月27日00:21

    请教一个问题,就是sipc注册时计算response用到的password,和第一步ssi登录时候用的password的是不是相同的值?因为第一步可以不加user-id来计算psw(当v4digest-type=1时),也可以加uid计算。

    [回复]

    solosky 回复:

    @wei2005yh, 是sipc注册时的结果是加上user-id计算的结果。

    [回复]

  17. difordill
    2010年7月28日09:46

    ssiportal/SSIAppSignInV4.aspx?

    [回复]

  18. doylecnn
    2010年7月28日15:38

    @difordill

    看源码,在LocalSetting.load(User user)方法中
    String content的值觉定了服务器返回的值究竟是 ssiportal/SSIAppSignInV4.aspx,还是ssiportal/SSIAppSignInV2.aspx

    [回复]

  19. 2010年7月31日00:31

    我郁闷了,用的国外的主机,估计是被屏蔽了,访问sipc服务器一直返回超时……nnd移动……

    [回复]

  20. Jave
    2010年9月20日06:59

    我想请问一个问题,SIPC注册那个步骤应该将数据post到哪个地址呢?或者我的步骤有什么别的错误?我将注册请求发到”HTTP://221.130.46.141/ht/sd.aspx?t=i&i=1″后得到的返回总是只有一个”SIPP”是为什么?

    post的header为
    “User-Agent”, “IIC2.0/PC 4.0.0000″
    “Cookie”, “ssic=前面得到的ssic”
    “Content-Type”, “application/oct-stream”
    “Pragma”, “xz4BBcV” + guid

    [回复]

    solosky 回复:

    如果只有SIPP表明服务器没有返回任何结果,你还需要继续发起请求获取内容,请求的内容只有SIPP就行了。

    [回复]

    Jave 回复:

    非常感谢~~之前一直没有发现这个问题

    [回复]

  21. Jave
    2010年9月20日10:50

    另外请问SIPC验证注册时返回
    SIP-C/4.0 500 Server Internal Error
    I: 1
    Q: 2 R
    是为什么?我好像和#10的朋友遇到了一样的问题,可是我没看懂他在#12是怎么解决的

    [回复]

    solosky 回复:

    你可以参见这篇关于HTTP的文章,http://www.solosky.net/2010/08/fetion2010-http-connection-mode.html。

    [回复]

    Jave 回复:

    谢谢,发现问题了,好想是sip的payload里多了两个&nbsp,去掉就好了
    只是现在返回 430 Request Failure

    我觉得可能是我前面生成response的部分有错误,检查后再回来汇报
    另外请问能否将代码里提及的ConvertHelper发给我?jave419@gmail.com,谢谢了

    [回复]

  22. frankchen
    2010年9月21日11:26

    密码是指用userid和明文密码加密过后的结果(两次sha1),这个是怎么加的呢。能说明一下吗?

    [回复]

  23. Jave
    2010年9月24日09:17

    请问在发送response后返回430 Request Failure可能是什么问题呢?

    [回复]

    sutra 回复:

    请问返回430的问题处理好了吗,我也碰到这个问题了,谢谢!

    [回复]

    sutra 回复:

    在solosky的指导下,使用了uid来进行传入,即可,以前使用的sid,导致了430错误。

    [回复]

  24. 2010年10月6日15:38

    在博主博文指导下,终于完成了个简陋的php飞信2010webapi,地址feixin.ttjs.org,欢迎试用

    [回复]

    solosky 回复:

    呵呵,恭喜~ 刚试了下,不知道怎么打不开。
    最近写了个LiteFetion,使用的webFetion的协议,如果你有兴趣的话,也可以看一下:http://litefetion.googlecode.com

    [回复]

  25. 2010年10月6日21:34

    呵呵,没有时间写前端,而且飞信需要验证码要弄ajax我不会。所以只做的api调用方式,直接打开网页有提示应该。朋友给的空间支持socket,所以直接用socket协议,比http快多了

    [回复]

  26. 2010年10月6日21:43

    粗略看了下litefetion的代码,感觉这个每次发短信都要输入验证码,不适合做webapi……

    [回复]

  27. hm
    2010年11月16日06:34

    public static string GetResponse(string key, int userid, string password, string nonce)
    {

    //{两次两次sha1之后
    byte[] pswB = DoHash(BitConverter.GetBytes(userid), DoHash(Encoding.UTF8.GetBytes(”fetion.com.cn:”), Encoding.UTF8.GetBytes(password)));
    //}
    byte[] nonceB = Encoding.UTF8.GetBytes(nonce);
    nonceB = HexStringToByteArray(nonce);
    byte[] aesKeyB = HexStringToByteArray(”441F7DBA5C3153B61C0660C622F01354″);//这个32字节的16进制字符串可以乱整,没得实际用处
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.ImportParameters(KeyToRSAPublicKey(key));
    byte[] ab = ByteContact(pswB, nonceB);
    byte[] abc = ByteContact(ab, aesKeyB);
    return ByteToHexStr(rsa.Encrypt(abc,false));
    }总是不行,
    SIP-C/4.0 406 Not Acceptable
    F: 598851324
    I: 1
    Q: 2 R

    [回复]

  28. 七月和五月
    2010年11月23日21:21

    F: 315760796
    I: 1
    Q: 1 R
    A: Digest algorithm=”SHA1-sess-v4″,response=”469f2032b5796544b5311bc71654138d34e
    08cfb52d18364c8bfd17771bd198d9d6e5c8291c4d59bf76d5e97478d84606ae296e67b9ace9e350
    dda874fc673b4bb8c17d1817d6776f0812c3fd2f499eaa0cba0ff4a4629760153923706909d935d0
    98dfdbabdea41d31c24ff25ab677c5a7e817df38dbb0412258c2e33c298d7″
    AK: ak-value
    A: Verify response=”5252″,algorithm=”picc-ChangeMachine”,type=”GeneralPic”,chid=
    “39f1ed67-4d6e-465b-a38f-7e49e1d60024″
    L: 575

    提交了服务器怎么什么也不返回呢?

    [回复]

  29. 七月和五月
    2010年11月23日21:25

    注册时返回421,提交验证码后就不反回了,老大帮帮看看错在哪里 谢谢了

    [回复]

  30. 2010年12月5日06:33

    能不能发一份2010协议的源码学习下呃?

    [回复]

  31. xsser
    2010年12月21日01:19

    帮忙看看我这段C#的计算response的代码,总是返回401,我密码设置是正确的啊.

    string modulusText = key.Substring(0, 0×100);
    string exponentText = key.Substring(0×100);

    byte[] modulus = hexString2ByteNoSpace(modulusText);

    byte[] exponent = hexString2ByteNoSpace(exponentText);

    RSACryptoServiceProvider provider = new RSACryptoServiceProvider();

    RSAParameters rasKeyInfo = new RSAParameters();

    rasKeyInfo.Modulus=modulus;

    rasKeyInfo.Exponent = exponent;

    provider.ImportParameters(rasKeyInfo);

    string pwd_V1 = encryptV4_2(urserId, encryptV4_1(pwd));

    byte[] a1 = Encoding.UTF8.GetBytes(nonce);

    byte[] a2 = hexString2ByteNoSpace(pwd_V1);

    byte[] a3 = hexString2ByteNoSpace(”4A026855890197CFDF768597D07200B346F3D676411C6F87368B5C2276DCEDD2″);

    byte[] a4 = byteCopy(a1, a2);

    byte[] a5 = byteCopy(a4, a3);

    byte[] a6 = provider.Encrypt(a5, false);

    //计算response
    string responseStr = byte2HexStringWithoutSpace(a6);

    private static string doHash(byte[] b1, byte[] b2)
    {
    byte[] dst = byteCopy(b1, b2);

    byte[] res = SHA1(dst);

    string returnStr = byte2HexStringWithoutSpace(res);

    return returnStr;
    }

    private static byte[] hexString2ByteNoSpace(string src)
    {
    int i = 0;

    byte[] returnBytes = new byte[src.Length / 2];

    for (int n = 0; n < src.Length; n += 2)
    {
    returnBytes[i] = Convert.ToByte(Convert.ToInt32(src[n].ToString() + src[n + 1].ToString(), 16));

    i++;
    }

    return returnBytes;
    }

    private static string byte2HexStringWithoutSpace(byte[] src)
    {
    string returnStr = "";

    for (int k = 0; k < src.Length; k++)
    {
    if (src[k].ToString("X").Length == 1)
    {
    returnStr += "0" + src[k].ToString("X");
    }
    else
    {
    returnStr += src[k].ToString("X");
    }
    }

    return returnStr;
    }

    private static byte[] byteCopy(byte[] b1, byte[] b2)
    {
    byte[] returnByte = new byte[b1.Length+b2.Length];

    int i = 0;

    for (i = 0; i < b1.Length; i++)
    {
    returnByte[i] = b1[i];
    }

    for (; i – b1.Length < b2.Length; i++)
    {
    returnByte[i] = b2[i - b1.Length];
    }

    return returnByte;
    }

    private static string encryptV4_1(string passwd)
    {
    byte[] b1 = Encoding.UTF8.GetBytes("fetion.com.cn:" + passwd);

    byte[] res = SHA1(b1);

    string returnStr = byte2HexStringWithoutSpace(res);

    return returnStr;
    }

    private static string encryptV4_2(string userNum, string passwd)
    {
    string returnStr = userNum + passwd;

    byte[] dst = Encoding.UTF8.GetBytes(returnStr);

    byte[] res = SHA1(dst);

    returnStr = byte2HexStringWithoutSpace(res);

    return returnStr;
    }

    [回复]

  32. alex888
    2010年12月21日16:28

    遇到同样问题,可以加我qq探讨:309376431

    [回复]

  33. wuqisheng
    2011年1月2日13:10

    今天终于成功!!

    [回复]

  34. wuqisheng
    2011年1月2日13:11

    ~谢谢你提供到资料~~

    [回复]