差点吐血! 这破玩意搞了N天, 终于搞定了, 但是很遗憾的是, 还是不明就里, 暂时先把工作进行下去吧...
因为需要跟Java版的通信, Java加密, C#的再解密, 这样就带来很多问题, 不过最终还是OK了.
代码如下(那个迭代过程实在是让我头疼啊, 看来算法还得加强!):

1
///
<summary>
2 /// 加密解密QQ消息包的工具类.
3 /// </summary>
4 public static class QQCrypter
5 {
6 private static Random Rnd = new Random();
7 private static void code( byte [] In, int inOffset, int inPos, byte [] Out, int outOffset, int outPos, byte [] key)
8 {
9 if (outPos > 0 )
10 {
11 for ( int i = 0 ; i < 8 ; i ++ )
12 {
13 In[outOffset + outPos + i] = ( byte )(In[inOffset + inPos + i] ^ Out[outOffset + outPos + i - 8 ]);
14 }
15 }
16 uint [] formattedKey = FormatKey(key);
17 uint y = ConvertByteArrayToUInt(In, outOffset + outPos);
18 uint z = ConvertByteArrayToUInt(In, outOffset + outPos + 4 );
19 uint sum = 0 ;
20 uint delta = 0x9e3779b9 ;
21 uint n = 16 ;
22
23 while (n -- > 0 )
24 {
25 sum += delta;
26 y += ((z << 4 ) + formattedKey[ 0 ]) ^ (z + sum) ^ ((z >> 5 ) + formattedKey[ 1 ]);
27 z += ((y << 4 ) + formattedKey[ 2 ]) ^ (y + sum) ^ ((y >> 5 ) + formattedKey[ 3 ]);
28 }
29 Array.Copy(ConvertUIntToByteArray(y), 0 , Out, outOffset + outPos, 4 );
30 Array.Copy(ConvertUIntToByteArray(z), 0 , Out, outOffset + outPos + 4 , 4 );
31 if (inPos > 0 )
32 {
33 for ( int i = 0 ; i < 8 ; i ++ )
34 {
35 Out[outOffset + outPos + i] = ( byte )(Out[outOffset + outPos + i] ^ In[inOffset + inPos + i - 8 ]);
36 }
37 }
38 }
39
40 private static void decode( byte [] In, int inOffset, int inPos, byte [] Out, int outOffset, int outPos, byte [] key)
41 {
42 if (outPos > 0 )
43 {
44 for ( int i = 0 ; i < 8 ; i ++ )
45 {
46 Out[outOffset + outPos + i] = ( byte )(In[inOffset + inPos + i] ^ Out[outOffset + outPos + i - 8 ]);
47 }
48 }
49 else
50 {
51 Array.Copy(In, inOffset, Out, outOffset, 8 );
52 }
53 uint [] formattedKey = FormatKey(key);
54 uint y = ConvertByteArrayToUInt(Out, outOffset + outPos);
55 uint z = ConvertByteArrayToUInt(Out, outOffset + outPos + 4 );
56 uint sum = 0xE3779B90 ;
57 uint delta = 0x9e3779b9 ;
58 uint n = 16 ;
59
60 while (n -- > 0 )
61 {
62 z -= ((y << 4 ) + formattedKey[ 2 ]) ^ (y + sum) ^ ((y >> 5 ) + formattedKey[ 3 ]);
63 y -= ((z << 4 ) + formattedKey[ 0 ]) ^ (z + sum) ^ ((z >> 5 ) + formattedKey[ 1 ]);
64 sum -= delta;
65 }
66 Array.Copy(ConvertUIntToByteArray(y), 0 , Out, outOffset + outPos, 4 );
67 Array.Copy(ConvertUIntToByteArray(z), 0 , Out, outOffset + outPos + 4 , 4 );
68 }
69
70 /// <summary>
71 /// 解密
72 /// </summary>
73 /// <param name="In"> 密文 </param>
74 /// <param name="offset"> 密文开始的位置 </param>
75 /// <param name="len"> 密文长度 </param>
76 /// <param name="key"> 密钥 </param>
77 /// <returns> 返回明文 </returns>
78 public static byte [] Decrypt( byte [] In, int offset, int len, byte [] key)
79 {
80 // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
81 if ((len % 8 != 0 ) || (len < 16 ))
82 {
83 return null ;
84 }
85 byte [] Out = new byte [len];
86 for ( int i = 0 ; i < len; i += 8 )
87 {
88 decode(In, offset, i, Out, 0 , i, key);
89 }
90 for ( int i = 8 ; i < len; i ++ )
91 {
92 Out[i] = ( byte )(Out[i] ^ In[offset + i - 8 ]);
93 }
94 int pos = Out[ 0 ] & 0x07 ;
95 len = len - pos - 10 ;
96 byte [] res = new byte [len];
97 Array.Copy(Out, pos + 3 , res, 0 , len);
98 return res;
99 }
100
101 /// <summary>
102 /// 加密
103 /// </summary>
104 /// <param name="In"> 明文 </param>
105 /// <param name="offset"> 明文开始的位置 </param>
106 /// <param name="len"> 明文长度 </param>
107 /// <param name="key"> 密钥 </param>
108 /// <returns> 返回密文 </returns>
109 public static byte [] Encrypt( byte [] In, int offset, int len, byte [] key)
110 {
111 // 计算头部填充字节数
112 int pos = (len + 10 ) % 8 ;
113 if (pos != 0 )
114 {
115 pos = 8 - pos;
116 }
117 byte [] plain = new byte [len + pos + 10 ];
118 plain[ 0 ] = ( byte )((Rnd.Next() & 0xF8 ) | pos);
119 for ( int i = 1 ; i < pos + 3 ; i ++ )
120 {
121 plain[i] = ( byte )(Rnd.Next() & 0xFF );
122 }
123 Array.Copy(In, 0 , plain, pos + 3 , len);
124 for ( int i = pos + 3 + len; i < plain.Length; i ++ )
125 {
126 plain[i] = 0x0 ;
127 }
128 // 定义输出流
129 byte [] outer = new byte [len + pos + 10 ];
130 for ( int i = 0 ; i < outer.Length; i += 8 )
131 {
132 code(plain, 0 , i, outer, 0 , i, key);
133 }
134 return outer;
135 }
136
137 private static uint [] FormatKey( byte [] key)
138 {
139 if (key.Length == 0 )
140 {
141 throw new ArgumentException( " Key must be between 1 and 16 characters in length " );
142 }
143 byte [] refineKey = new byte [ 16 ];
144 if (key.Length < 16 )
145 {
146 Array.Copy(key, 0 , refineKey, 0 , key.Length);
147 for ( int k = key.Length; k < 16 ; k ++ )
148 {
149 refineKey[k] = 0x20 ;
150 }
151 }
152 else
153 {
154 Array.Copy(key, 0 , refineKey, 0 , 16 );
155 }
156 uint [] formattedKey = new uint [ 4 ];
157 int j = 0 ;
158 for ( int i = 0 ; i < refineKey.Length; i += 4 )
159 {
160 formattedKey[j ++ ] = ConvertByteArrayToUInt(refineKey, i);
161 }
162 return formattedKey;
163 }
164
165 private static byte [] ConvertUIntToByteArray( uint v)
166 {
167 byte [] result = new byte [ 4 ];
168 result[ 0 ] = ( byte )((v >> 24 ) & 0xFF );
169 result[ 1 ] = ( byte )((v >> 16 ) & 0xFF );
170 result[ 2 ] = ( byte )((v >> 8 ) & 0xFF );
171 result[ 3 ] = ( byte )((v >> 0 ) & 0xFF );
172 return result;
173 }
174
175 private static uint ConvertByteArrayToUInt( byte [] v, int offset)
176 {
177 if (offset + 4 > v.Length)
178 {
179 return 0 ;
180 }
181 uint output;
182 output = ( uint )(v[offset] << 24 );
183 output |= ( uint )(v[offset + 1 ] << 16 );
184 output |= ( uint )(v[offset + 2 ] << 8 );
185 output |= ( uint )(v[offset + 3 ] << 0 );
186 return output;
187 }
188 }
2 /// 加密解密QQ消息包的工具类.
3 /// </summary>
4 public static class QQCrypter
5 {
6 private static Random Rnd = new Random();
7 private static void code( byte [] In, int inOffset, int inPos, byte [] Out, int outOffset, int outPos, byte [] key)
8 {
9 if (outPos > 0 )
10 {
11 for ( int i = 0 ; i < 8 ; i ++ )
12 {
13 In[outOffset + outPos + i] = ( byte )(In[inOffset + inPos + i] ^ Out[outOffset + outPos + i - 8 ]);
14 }
15 }
16 uint [] formattedKey = FormatKey(key);
17 uint y = ConvertByteArrayToUInt(In, outOffset + outPos);
18 uint z = ConvertByteArrayToUInt(In, outOffset + outPos + 4 );
19 uint sum = 0 ;
20 uint delta = 0x9e3779b9 ;
21 uint n = 16 ;
22
23 while (n -- > 0 )
24 {
25 sum += delta;
26 y += ((z << 4 ) + formattedKey[ 0 ]) ^ (z + sum) ^ ((z >> 5 ) + formattedKey[ 1 ]);
27 z += ((y << 4 ) + formattedKey[ 2 ]) ^ (y + sum) ^ ((y >> 5 ) + formattedKey[ 3 ]);
28 }
29 Array.Copy(ConvertUIntToByteArray(y), 0 , Out, outOffset + outPos, 4 );
30 Array.Copy(ConvertUIntToByteArray(z), 0 , Out, outOffset + outPos + 4 , 4 );
31 if (inPos > 0 )
32 {
33 for ( int i = 0 ; i < 8 ; i ++ )
34 {
35 Out[outOffset + outPos + i] = ( byte )(Out[outOffset + outPos + i] ^ In[inOffset + inPos + i - 8 ]);
36 }
37 }
38 }
39
40 private static void decode( byte [] In, int inOffset, int inPos, byte [] Out, int outOffset, int outPos, byte [] key)
41 {
42 if (outPos > 0 )
43 {
44 for ( int i = 0 ; i < 8 ; i ++ )
45 {
46 Out[outOffset + outPos + i] = ( byte )(In[inOffset + inPos + i] ^ Out[outOffset + outPos + i - 8 ]);
47 }
48 }
49 else
50 {
51 Array.Copy(In, inOffset, Out, outOffset, 8 );
52 }
53 uint [] formattedKey = FormatKey(key);
54 uint y = ConvertByteArrayToUInt(Out, outOffset + outPos);
55 uint z = ConvertByteArrayToUInt(Out, outOffset + outPos + 4 );
56 uint sum = 0xE3779B90 ;
57 uint delta = 0x9e3779b9 ;
58 uint n = 16 ;
59
60 while (n -- > 0 )
61 {
62 z -= ((y << 4 ) + formattedKey[ 2 ]) ^ (y + sum) ^ ((y >> 5 ) + formattedKey[ 3 ]);
63 y -= ((z << 4 ) + formattedKey[ 0 ]) ^ (z + sum) ^ ((z >> 5 ) + formattedKey[ 1 ]);
64 sum -= delta;
65 }
66 Array.Copy(ConvertUIntToByteArray(y), 0 , Out, outOffset + outPos, 4 );
67 Array.Copy(ConvertUIntToByteArray(z), 0 , Out, outOffset + outPos + 4 , 4 );
68 }
69
70 /// <summary>
71 /// 解密
72 /// </summary>
73 /// <param name="In"> 密文 </param>
74 /// <param name="offset"> 密文开始的位置 </param>
75 /// <param name="len"> 密文长度 </param>
76 /// <param name="key"> 密钥 </param>
77 /// <returns> 返回明文 </returns>
78 public static byte [] Decrypt( byte [] In, int offset, int len, byte [] key)
79 {
80 // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
81 if ((len % 8 != 0 ) || (len < 16 ))
82 {
83 return null ;
84 }
85 byte [] Out = new byte [len];
86 for ( int i = 0 ; i < len; i += 8 )
87 {
88 decode(In, offset, i, Out, 0 , i, key);
89 }
90 for ( int i = 8 ; i < len; i ++ )
91 {
92 Out[i] = ( byte )(Out[i] ^ In[offset + i - 8 ]);
93 }
94 int pos = Out[ 0 ] & 0x07 ;
95 len = len - pos - 10 ;
96 byte [] res = new byte [len];
97 Array.Copy(Out, pos + 3 , res, 0 , len);
98 return res;
99 }
100
101 /// <summary>
102 /// 加密
103 /// </summary>
104 /// <param name="In"> 明文 </param>
105 /// <param name="offset"> 明文开始的位置 </param>
106 /// <param name="len"> 明文长度 </param>
107 /// <param name="key"> 密钥 </param>
108 /// <returns> 返回密文 </returns>
109 public static byte [] Encrypt( byte [] In, int offset, int len, byte [] key)
110 {
111 // 计算头部填充字节数
112 int pos = (len + 10 ) % 8 ;
113 if (pos != 0 )
114 {
115 pos = 8 - pos;
116 }
117 byte [] plain = new byte [len + pos + 10 ];
118 plain[ 0 ] = ( byte )((Rnd.Next() & 0xF8 ) | pos);
119 for ( int i = 1 ; i < pos + 3 ; i ++ )
120 {
121 plain[i] = ( byte )(Rnd.Next() & 0xFF );
122 }
123 Array.Copy(In, 0 , plain, pos + 3 , len);
124 for ( int i = pos + 3 + len; i < plain.Length; i ++ )
125 {
126 plain[i] = 0x0 ;
127 }
128 // 定义输出流
129 byte [] outer = new byte [len + pos + 10 ];
130 for ( int i = 0 ; i < outer.Length; i += 8 )
131 {
132 code(plain, 0 , i, outer, 0 , i, key);
133 }
134 return outer;
135 }
136
137 private static uint [] FormatKey( byte [] key)
138 {
139 if (key.Length == 0 )
140 {
141 throw new ArgumentException( " Key must be between 1 and 16 characters in length " );
142 }
143 byte [] refineKey = new byte [ 16 ];
144 if (key.Length < 16 )
145 {
146 Array.Copy(key, 0 , refineKey, 0 , key.Length);
147 for ( int k = key.Length; k < 16 ; k ++ )
148 {
149 refineKey[k] = 0x20 ;
150 }
151 }
152 else
153 {
154 Array.Copy(key, 0 , refineKey, 0 , 16 );
155 }
156 uint [] formattedKey = new uint [ 4 ];
157 int j = 0 ;
158 for ( int i = 0 ; i < refineKey.Length; i += 4 )
159 {
160 formattedKey[j ++ ] = ConvertByteArrayToUInt(refineKey, i);
161 }
162 return formattedKey;
163 }
164
165 private static byte [] ConvertUIntToByteArray( uint v)
166 {
167 byte [] result = new byte [ 4 ];
168 result[ 0 ] = ( byte )((v >> 24 ) & 0xFF );
169 result[ 1 ] = ( byte )((v >> 16 ) & 0xFF );
170 result[ 2 ] = ( byte )((v >> 8 ) & 0xFF );
171 result[ 3 ] = ( byte )((v >> 0 ) & 0xFF );
172 return result;
173 }
174
175 private static uint ConvertByteArrayToUInt( byte [] v, int offset)
176 {
177 if (offset + 4 > v.Length)
178 {
179 return 0 ;
180 }
181 uint output;
182 output = ( uint )(v[offset] << 24 );
183 output |= ( uint )(v[offset + 1 ] << 16 );
184 output |= ( uint )(v[offset + 2 ] << 8 );
185 output |= ( uint )(v[offset + 3 ] << 0 );
186 return output;
187 }
188 }
对外只留了两个方法用于加密和解密, 有兴趣的童鞋研究研究, 结果告诉我哦.
我找个两个版本的, 上面那个是最终使用通过的, 还有一个版本的感觉明显不对, 因为加密的时候貌似没有产生随机数的样子, 每次加密过后的密文都相同, 这我很想不通, 所以更放弃了对那段代码的研究. 后来自己尝试改写Java版的, 无功而返.
下面附Java版的代码, 带部分注释(网上找的).

1
package
mobi.bbase.ahome.utils;
2
3 import java.io.ByteArrayOutputStream;
4 import java.util.Random;
5
6
7 /**
8 * 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
9 * 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
10 * f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt) ˆ
11 * prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ
12 * preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
13 * 填充的字节数与原始明文长度有关,填充的方法是:
14 *
15 * <pre>
16 * <code>
17 *
18 * ------- 消息填充算法 -----------
19 * a = (明文长度 + 10) mod 8
20 * if(a 不等于 0) a = 8 - a;
21 * b = 随机数 & 0xF8 | a; 这个的作用是把a的值保存了下来
22 * plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
23 * plain[1 至 a+2] = 随机数 & 0xFF; 这里用随机数填充明文的第1到第a+2个字节
24 * plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
25 * plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
26 * ------- 消息填充算法 ------------
27 *
28 * </code>
29 * </pre>
30 *
31 * @author luma
32 * @author notXX
33 */
34 public class Crypter {
35 // 指向当前的明文块
36 private byte [] plain;
37 // 这指向前面一个明文块
38 private byte [] prePlain;
39 // 输出的密文或者明文
40 private byte [] out;
41 // 当前加密的密文位置和上一次加密的密文块位置,他们相差8
42 private int crypt, preCrypt;
43 // 当前处理的加密解密块的位置
44 private int pos;
45 // 填充数
46 private int padding;
47 // 密钥
48 private byte [] key;
49 // 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
50 // 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
51 private boolean header = true ;
52 // 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
53 // 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
54 private int contextStart;
55 // 随机数对象
56 private static Random random = new Random();
57 // 字节输出流
58 private ByteArrayOutputStream baos;
59 /**
60 * 构造函数
61 */
62 public Crypter() {
63 baos = new ByteArrayOutputStream( 8 );
64 }
65 /**
66 * 把字节数组从offset开始的len个字节转换成一个unsigned int, 因为java里面没有unsigned,所以unsigned
67 * int使用long表示的, 如果len大于8,则认为len等于8。如果len小于8,则高位填0 <br>
68 * (edited by notxx) 改变了算法, 性能稍微好一点. 在我的机器上测试10000次, 原始算法花费18s, 这个算法花费12s.
69 *
70 * @param in
71 * 字节数组.
72 * @param offset
73 * 从哪里开始转换.
74 * @param len
75 * 转换长度, 如果len超过8则忽略后面的
76 * @return
77 */
78 private static long getUnsignedInt( byte [] in, int offset, int len) {
79 long ret = 0 ;
80 int end = 0 ;
81 if (len > 8 )
82 end = offset + 8 ;
83 else
84 end = offset + len;
85 for ( int i = offset; i < end; i ++ ) {
86 ret <<= 8 ;
87 ret |= in[i] & 0xff ;
88 }
89 return (ret & 0xffffffffl ) | (ret >>> 32 );
90 }
91
92 /**
93 * 解密
94 * @param in 密文
95 * @param offset 密文开始的位置
96 * @param len 密文长度
97 * @param k 密钥
98 * @return 明文
99 */
100 public byte [] decrypt( byte [] in, int offset, int len, byte [] k) {
101 // 检查密钥
102 if (k == null )
103 return null ;
104
105 crypt = preCrypt = 0 ;
106 this .key = k;
107 int count;
108 byte [] m = new byte [offset + 8 ];
109
110 // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
111 if ((len % 8 != 0 ) || (len < 16 )) return null ;
112 // 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
113 prePlain = decipher(in, offset);
114 pos = prePlain[ 0 ] & 0x7 ;
115 // 得到真正明文的长度
116 count = len - pos - 10 ;
117 // 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
118 if (count < 0 ) return null ;
119
120 // 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
121 // 第一个8字节块也没有preCrypt,所有这里建一个全0的
122 for ( int i = offset; i < m.length; i ++ )
123 m[i] = 0 ;
124 // 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
125 out = new byte [count];
126 // 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
127 preCrypt = 0 ;
128 // 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
129 crypt = 8 ;
130 // 自然这个也是8
131 contextStart = 8 ;
132 // 加1,和加密算法是对应的
133 pos ++ ;
134
135 // 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
136 // 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
137 // 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
138 // 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
139 padding = 1 ;
140 while (padding <= 2 ) {
141 if (pos < 8 ) {
142 pos ++ ;
143 padding ++ ;
144 }
145 if (pos == 8 ) {
146 m = in;
147 if ( ! decrypt8Bytes(in, offset, len)) return null ;
148 }
149 }
150
151 // 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
152 // 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
153 int i = 0 ;
154 while (count != 0 ) {
155 if (pos < 8 ) {
156 out[i] = ( byte )(m[offset + preCrypt + pos] ^ prePlain[pos]);
157 i ++ ;
158 count -- ;
159 pos ++ ;
160 }
161 if (pos == 8 ) {
162 m = in;
163 preCrypt = crypt - 8 ;
164 if ( ! decrypt8Bytes(in, offset, len))
165 return null ;
166 }
167 }
168
169 // 最后的解密部分,上面一个while已经把明文都解出来了,就剩下尾部的填充了,应该全是0
170 // 所以这里有检查是否解密了之后是不是0,如果不是的话那肯定出错了,返回null
171 for (padding = 1 ; padding < 8 ; padding ++ ) {
172 if (pos < 8 ) {
173 if ((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0 )
174 return null ;
175 pos ++ ;
176 }
177 if (pos == 8 ) {
178 m = in;
179 preCrypt = crypt;
180 if ( ! decrypt8Bytes(in, offset, len))
181 return null ;
182 }
183 }
184 return out;
185 }
186
187 /**
188 * @param in
189 * 需要被解密的密文
190 * @param inLen
191 * 密文长度
192 * @param k
193 * 密钥
194 * @return Message 已解密的消息
195 */
196 public byte [] decrypt( byte [] in, byte [] k) {
197 return decrypt(in, 0 , in.length, k);
198 }
199
200 /**
201 * 加密
202 * @param in 明文字节数组
203 * @param offset 开始加密的偏移
204 * @param len 加密长度
205 * @param k 密钥
206 * @return 密文字节数组
207 */
208 public byte [] encrypt( byte [] in, int offset, int len, byte [] k) {
209 // 检查密钥
210 if (k == null )
211 return in;
212
213 plain = new byte [ 8 ];
214 prePlain = new byte [ 8 ];
215 pos = 1 ;
216 padding = 0 ;
217 crypt = preCrypt = 0 ;
218 this .key = k;
219 header = true ;
220
221 // 计算头部填充字节数
222 pos = (len + 0x0A ) % 8 ;
223 if (pos != 0 )
224 pos = 8 - pos;
225 // 计算输出的密文长度
226 out = new byte [len + pos + 10 ];
227 // 这里的操作把pos存到了plain的第一个字节里面
228 // 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
229 plain[ 0 ] = ( byte )((rand() & 0xF8 ) | pos);
230
231 // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
232 for ( int i = 1 ; i <= pos; i ++ )
233 plain[i] = ( byte )(rand() & 0xFF );
234 pos ++ ;
235 // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
236 for ( int i = 0 ; i < 8 ; i ++ )
237 prePlain[i] = 0x0 ;
238
239 // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
240 padding = 1 ;
241 while (padding <= 2 ) {
242 if (pos < 8 ) {
243 plain[pos ++ ] = ( byte )(rand() & 0xFF );
244 padding ++ ;
245 }
246 if (pos == 8 )
247 encrypt8Bytes();
248 }
249
250 // 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
251 int i = offset;
252 while (len > 0 ) {
253 if (pos < 8 ) {
254 plain[pos ++ ] = in[i ++ ];
255 len -- ;
256 }
257 if (pos == 8 )
258 encrypt8Bytes();
259 }
260
261 // 最后填上0,以保证是8字节的倍数
262 padding = 1 ;
263 while (padding <= 7 ) {
264 if (pos < 8 ) {
265 plain[pos ++ ] = 0x0 ;
266 padding ++ ;
267 }
268 if (pos == 8 )
269 encrypt8Bytes();
270 }
271
272 return out;
273 }
274
275 /**
276 * @param in
277 * 需要加密的明文
278 * @param inLen
279 * 明文长度
280 * @param k
281 * 密钥
282 * @return Message 密文
283 */
284 public byte [] encrypt( byte [] in, byte [] k) {
285 return encrypt(in, 0 , in.length, k);
286 }
287
288 /**
289 * 加密一个8字节块
290 *
291 * @param in
292 * 明文字节数组
293 * @return
294 * 密文字节数组
295 */
296 private byte [] encipher( byte [] in) {
297 // 迭代次数,16次
298 int loop = 0x10 ;
299 // 得到明文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
300 // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
301 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
302 long y = getUnsignedInt(in, 0 , 4 );
303 long z = getUnsignedInt(in, 4 , 4 );
304 long a = getUnsignedInt(key, 0 , 4 );
305 long b = getUnsignedInt(key, 4 , 4 );
306 long c = getUnsignedInt(key, 8 , 4 );
307 long d = getUnsignedInt(key, 12 , 4 );
308 // 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
309 // 这个数是TEA算法的delta,实际是就是(sqr(5) - 1) * 2^31 (根号5,减1,再乘2的31次方)
310 long sum = 0 ;
311 long delta = 0x9E3779B9 ;
312 delta &= 0xFFFFFFFFL ;
313
314 // 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
315 while (loop -- > 0 ) {
316 sum += delta;
317 sum &= 0xFFFFFFFFL ;
318 y += ((z << 4 ) + a) ^ (z + sum) ^ ((z >>> 5 ) + b);
319 y &= 0xFFFFFFFFL ;
320 z += ((y << 4 ) + c) ^ (y + sum) ^ ((y >>> 5 ) + d);
321 z &= 0xFFFFFFFFL ;
322 }
323
324 // 最后,我们输出密文,因为我用的long,所以需要强制转换一下变成int
325 baos.reset();
326 writeInt(( int )y);
327 writeInt(( int )z);
328 return baos.toByteArray();
329 }
330
331 /**
332 * 解密从offset开始的8字节密文
333 *
334 * @param in
335 * 密文字节数组
336 * @param offset
337 * 密文开始位置
338 * @return
339 * 明文
340 */
341 private byte [] decipher( byte [] in, int offset) {
342 // 迭代次数,16次
343 int loop = 0x10 ;
344 // 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
345 // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
346 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
347 long y = getUnsignedInt(in, offset, 4 );
348 long z = getUnsignedInt(in, offset + 4 , 4 );
349 long a = getUnsignedInt(key, 0 , 4 );
350 long b = getUnsignedInt(key, 4 , 4 );
351 long c = getUnsignedInt(key, 8 , 4 );
352 long d = getUnsignedInt(key, 12 , 4 );
353 // 算法的一些控制变量,sum在这里也有数了,这个sum和迭代次数有关系
354 // 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
355 // 得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样才能解密呀~~
356 long sum = 0xE3779B90 ;
357 sum &= 0xFFFFFFFFL ;
358 long delta = 0x9E3779B9 ;
359 delta &= 0xFFFFFFFFL ;
360
361 // 迭代开始了, @_@
362 while (loop -- > 0 ) {
363 z -= ((y << 4 ) + c) ^ (y + sum) ^ ((y >>> 5 ) + d);
364 z &= 0xFFFFFFFFL ;
365 y -= ((z << 4 ) + a) ^ (z + sum) ^ ((z >>> 5 ) + b);
366 y &= 0xFFFFFFFFL ;
367 sum -= delta;
368 sum &= 0xFFFFFFFFL ;
369 }
370
371 baos.reset();
372 writeInt(( int )y);
373 writeInt(( int )z);
374 return baos.toByteArray();
375 }
376
377 /**
378 * 写入一个整型到输出流,高字节优先
379 *
380 * @param t
381 */
382 private void writeInt( int t) {
383 baos.write(t >>> 24 );
384 baos.write(t >>> 16 );
385 baos.write(t >>> 8 );
386 baos.write(t);
387 }
388
389 /**
390 * 解密
391 *
392 * @param in
393 * 密文
394 * @return
395 * 明文
396 */
397 private byte [] decipher( byte [] in) {
398 return decipher(in, 0 );
399 }
400
401 /**
402 * 加密8字节
403 */
404 private void encrypt8Bytes() {
405 // 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
406 for (pos = 0 ; pos < 8 ; pos ++ ) {
407 if (header)
408 plain[pos] ^= prePlain[pos];
409 else
410 plain[pos] ^= out[preCrypt + pos];
411 }
412 // 这个完成我上面说的 f(plain ^ preCrypt)
413 byte [] crypted = encipher(plain);
414 // 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了
415 System.arraycopy(crypted, 0 , out, crypt, 8 );
416
417 // 这个完成了 f(plain ^ preCrypt) ^ prePlain,ok,下面拷贝一下就行了
418 for (pos = 0 ; pos < 8 ; pos ++ )
419 out[crypt + pos] ^= prePlain[pos];
420 System.arraycopy(plain, 0 , prePlain, 0 , 8 );
421
422 // 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
423 preCrypt = crypt;
424 crypt += 8 ;
425 pos = 0 ;
426 header = false ;
427 }
428
429 /**
430 * 解密8个字节
431 *
432 * @param in
433 * 密文字节数组
434 * @param offset
435 * 从何处开始解密
436 * @param len
437 * 密文的长度
438 * @return
439 * true表示解密成功
440 */
441 private boolean decrypt8Bytes( byte [] in , int offset, int len) {
442 // 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
443 for (pos = 0 ; pos < 8 ; pos ++ ) {
444 if (contextStart + pos >= len)
445 return true ;
446 prePlain[pos] ^= in[offset + crypt + pos];
447 }
448
449 // 好,这里执行到了 d(crypt ^ prePlain)
450 prePlain = decipher(prePlain);
451 if (prePlain == null )
452 return false ;
453
454 // 解密完成,最后一步好像没做?
455 // 这里最后一步放到decrypt里面去做了,因为解密的步骤有点不太一样
456 // 调整这些变量的值先
457 contextStart += 8 ;
458 crypt += 8 ;
459 pos = 0 ;
460 return true ;
461 }
462
463 /**
464 * 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值
465 * 随机因子可以使相同的明文每次加密出来的密文都不一样
466 *
467 * @return
468 * 随机因子
469 */
470 private int rand() {
471 return random.nextInt();
472 }
473 }
2
3 import java.io.ByteArrayOutputStream;
4 import java.util.Random;
5
6
7 /**
8 * 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
9 * 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
10 * f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt) ˆ
11 * prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ
12 * preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
13 * 填充的字节数与原始明文长度有关,填充的方法是:
14 *
15 * <pre>
16 * <code>
17 *
18 * ------- 消息填充算法 -----------
19 * a = (明文长度 + 10) mod 8
20 * if(a 不等于 0) a = 8 - a;
21 * b = 随机数 & 0xF8 | a; 这个的作用是把a的值保存了下来
22 * plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
23 * plain[1 至 a+2] = 随机数 & 0xFF; 这里用随机数填充明文的第1到第a+2个字节
24 * plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
25 * plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
26 * ------- 消息填充算法 ------------
27 *
28 * </code>
29 * </pre>
30 *
31 * @author luma
32 * @author notXX
33 */
34 public class Crypter {
35 // 指向当前的明文块
36 private byte [] plain;
37 // 这指向前面一个明文块
38 private byte [] prePlain;
39 // 输出的密文或者明文
40 private byte [] out;
41 // 当前加密的密文位置和上一次加密的密文块位置,他们相差8
42 private int crypt, preCrypt;
43 // 当前处理的加密解密块的位置
44 private int pos;
45 // 填充数
46 private int padding;
47 // 密钥
48 private byte [] key;
49 // 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
50 // 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
51 private boolean header = true ;
52 // 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
53 // 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
54 private int contextStart;
55 // 随机数对象
56 private static Random random = new Random();
57 // 字节输出流
58 private ByteArrayOutputStream baos;
59 /**
60 * 构造函数
61 */
62 public Crypter() {
63 baos = new ByteArrayOutputStream( 8 );
64 }
65 /**
66 * 把字节数组从offset开始的len个字节转换成一个unsigned int, 因为java里面没有unsigned,所以unsigned
67 * int使用long表示的, 如果len大于8,则认为len等于8。如果len小于8,则高位填0 <br>
68 * (edited by notxx) 改变了算法, 性能稍微好一点. 在我的机器上测试10000次, 原始算法花费18s, 这个算法花费12s.
69 *
70 * @param in
71 * 字节数组.
72 * @param offset
73 * 从哪里开始转换.
74 * @param len
75 * 转换长度, 如果len超过8则忽略后面的
76 * @return
77 */
78 private static long getUnsignedInt( byte [] in, int offset, int len) {
79 long ret = 0 ;
80 int end = 0 ;
81 if (len > 8 )
82 end = offset + 8 ;
83 else
84 end = offset + len;
85 for ( int i = offset; i < end; i ++ ) {
86 ret <<= 8 ;
87 ret |= in[i] & 0xff ;
88 }
89 return (ret & 0xffffffffl ) | (ret >>> 32 );
90 }
91
92 /**
93 * 解密
94 * @param in 密文
95 * @param offset 密文开始的位置
96 * @param len 密文长度
97 * @param k 密钥
98 * @return 明文
99 */
100 public byte [] decrypt( byte [] in, int offset, int len, byte [] k) {
101 // 检查密钥
102 if (k == null )
103 return null ;
104
105 crypt = preCrypt = 0 ;
106 this .key = k;
107 int count;
108 byte [] m = new byte [offset + 8 ];
109
110 // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
111 if ((len % 8 != 0 ) || (len < 16 )) return null ;
112 // 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
113 prePlain = decipher(in, offset);
114 pos = prePlain[ 0 ] & 0x7 ;
115 // 得到真正明文的长度
116 count = len - pos - 10 ;
117 // 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
118 if (count < 0 ) return null ;
119
120 // 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
121 // 第一个8字节块也没有preCrypt,所有这里建一个全0的
122 for ( int i = offset; i < m.length; i ++ )
123 m[i] = 0 ;
124 // 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
125 out = new byte [count];
126 // 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
127 preCrypt = 0 ;
128 // 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
129 crypt = 8 ;
130 // 自然这个也是8
131 contextStart = 8 ;
132 // 加1,和加密算法是对应的
133 pos ++ ;
134
135 // 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
136 // 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
137 // 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
138 // 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
139 padding = 1 ;
140 while (padding <= 2 ) {
141 if (pos < 8 ) {
142 pos ++ ;
143 padding ++ ;
144 }
145 if (pos == 8 ) {
146 m = in;
147 if ( ! decrypt8Bytes(in, offset, len)) return null ;
148 }
149 }
150
151 // 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
152 // 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
153 int i = 0 ;
154 while (count != 0 ) {
155 if (pos < 8 ) {
156 out[i] = ( byte )(m[offset + preCrypt + pos] ^ prePlain[pos]);
157 i ++ ;
158 count -- ;
159 pos ++ ;
160 }
161 if (pos == 8 ) {
162 m = in;
163 preCrypt = crypt - 8 ;
164 if ( ! decrypt8Bytes(in, offset, len))
165 return null ;
166 }
167 }
168
169 // 最后的解密部分,上面一个while已经把明文都解出来了,就剩下尾部的填充了,应该全是0
170 // 所以这里有检查是否解密了之后是不是0,如果不是的话那肯定出错了,返回null
171 for (padding = 1 ; padding < 8 ; padding ++ ) {
172 if (pos < 8 ) {
173 if ((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0 )
174 return null ;
175 pos ++ ;
176 }
177 if (pos == 8 ) {
178 m = in;
179 preCrypt = crypt;
180 if ( ! decrypt8Bytes(in, offset, len))
181 return null ;
182 }
183 }
184 return out;
185 }
186
187 /**
188 * @param in
189 * 需要被解密的密文
190 * @param inLen
191 * 密文长度
192 * @param k
193 * 密钥
194 * @return Message 已解密的消息
195 */
196 public byte [] decrypt( byte [] in, byte [] k) {
197 return decrypt(in, 0 , in.length, k);
198 }
199
200 /**
201 * 加密
202 * @param in 明文字节数组
203 * @param offset 开始加密的偏移
204 * @param len 加密长度
205 * @param k 密钥
206 * @return 密文字节数组
207 */
208 public byte [] encrypt( byte [] in, int offset, int len, byte [] k) {
209 // 检查密钥
210 if (k == null )
211 return in;
212
213 plain = new byte [ 8 ];
214 prePlain = new byte [ 8 ];
215 pos = 1 ;
216 padding = 0 ;
217 crypt = preCrypt = 0 ;
218 this .key = k;
219 header = true ;
220
221 // 计算头部填充字节数
222 pos = (len + 0x0A ) % 8 ;
223 if (pos != 0 )
224 pos = 8 - pos;
225 // 计算输出的密文长度
226 out = new byte [len + pos + 10 ];
227 // 这里的操作把pos存到了plain的第一个字节里面
228 // 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
229 plain[ 0 ] = ( byte )((rand() & 0xF8 ) | pos);
230
231 // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
232 for ( int i = 1 ; i <= pos; i ++ )
233 plain[i] = ( byte )(rand() & 0xFF );
234 pos ++ ;
235 // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
236 for ( int i = 0 ; i < 8 ; i ++ )
237 prePlain[i] = 0x0 ;
238
239 // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
240 padding = 1 ;
241 while (padding <= 2 ) {
242 if (pos < 8 ) {
243 plain[pos ++ ] = ( byte )(rand() & 0xFF );
244 padding ++ ;
245 }
246 if (pos == 8 )
247 encrypt8Bytes();
248 }
249
250 // 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
251 int i = offset;
252 while (len > 0 ) {
253 if (pos < 8 ) {
254 plain[pos ++ ] = in[i ++ ];
255 len -- ;
256 }
257 if (pos == 8 )
258 encrypt8Bytes();
259 }
260
261 // 最后填上0,以保证是8字节的倍数
262 padding = 1 ;
263 while (padding <= 7 ) {
264 if (pos < 8 ) {
265 plain[pos ++ ] = 0x0 ;
266 padding ++ ;
267 }
268 if (pos == 8 )
269 encrypt8Bytes();
270 }
271
272 return out;
273 }
274
275 /**
276 * @param in
277 * 需要加密的明文
278 * @param inLen
279 * 明文长度
280 * @param k
281 * 密钥
282 * @return Message 密文
283 */
284 public byte [] encrypt( byte [] in, byte [] k) {
285 return encrypt(in, 0 , in.length, k);
286 }
287
288 /**
289 * 加密一个8字节块
290 *
291 * @param in
292 * 明文字节数组
293 * @return
294 * 密文字节数组
295 */
296 private byte [] encipher( byte [] in) {
297 // 迭代次数,16次
298 int loop = 0x10 ;
299 // 得到明文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
300 // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
301 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
302 long y = getUnsignedInt(in, 0 , 4 );
303 long z = getUnsignedInt(in, 4 , 4 );
304 long a = getUnsignedInt(key, 0 , 4 );
305 long b = getUnsignedInt(key, 4 , 4 );
306 long c = getUnsignedInt(key, 8 , 4 );
307 long d = getUnsignedInt(key, 12 , 4 );
308 // 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
309 // 这个数是TEA算法的delta,实际是就是(sqr(5) - 1) * 2^31 (根号5,减1,再乘2的31次方)
310 long sum = 0 ;
311 long delta = 0x9E3779B9 ;
312 delta &= 0xFFFFFFFFL ;
313
314 // 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
315 while (loop -- > 0 ) {
316 sum += delta;
317 sum &= 0xFFFFFFFFL ;
318 y += ((z << 4 ) + a) ^ (z + sum) ^ ((z >>> 5 ) + b);
319 y &= 0xFFFFFFFFL ;
320 z += ((y << 4 ) + c) ^ (y + sum) ^ ((y >>> 5 ) + d);
321 z &= 0xFFFFFFFFL ;
322 }
323
324 // 最后,我们输出密文,因为我用的long,所以需要强制转换一下变成int
325 baos.reset();
326 writeInt(( int )y);
327 writeInt(( int )z);
328 return baos.toByteArray();
329 }
330
331 /**
332 * 解密从offset开始的8字节密文
333 *
334 * @param in
335 * 密文字节数组
336 * @param offset
337 * 密文开始位置
338 * @return
339 * 明文
340 */
341 private byte [] decipher( byte [] in, int offset) {
342 // 迭代次数,16次
343 int loop = 0x10 ;
344 // 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
345 // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
346 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
347 long y = getUnsignedInt(in, offset, 4 );
348 long z = getUnsignedInt(in, offset + 4 , 4 );
349 long a = getUnsignedInt(key, 0 , 4 );
350 long b = getUnsignedInt(key, 4 , 4 );
351 long c = getUnsignedInt(key, 8 , 4 );
352 long d = getUnsignedInt(key, 12 , 4 );
353 // 算法的一些控制变量,sum在这里也有数了,这个sum和迭代次数有关系
354 // 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
355 // 得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样才能解密呀~~
356 long sum = 0xE3779B90 ;
357 sum &= 0xFFFFFFFFL ;
358 long delta = 0x9E3779B9 ;
359 delta &= 0xFFFFFFFFL ;
360
361 // 迭代开始了, @_@
362 while (loop -- > 0 ) {
363 z -= ((y << 4 ) + c) ^ (y + sum) ^ ((y >>> 5 ) + d);
364 z &= 0xFFFFFFFFL ;
365 y -= ((z << 4 ) + a) ^ (z + sum) ^ ((z >>> 5 ) + b);
366 y &= 0xFFFFFFFFL ;
367 sum -= delta;
368 sum &= 0xFFFFFFFFL ;
369 }
370
371 baos.reset();
372 writeInt(( int )y);
373 writeInt(( int )z);
374 return baos.toByteArray();
375 }
376
377 /**
378 * 写入一个整型到输出流,高字节优先
379 *
380 * @param t
381 */
382 private void writeInt( int t) {
383 baos.write(t >>> 24 );
384 baos.write(t >>> 16 );
385 baos.write(t >>> 8 );
386 baos.write(t);
387 }
388
389 /**
390 * 解密
391 *
392 * @param in
393 * 密文
394 * @return
395 * 明文
396 */
397 private byte [] decipher( byte [] in) {
398 return decipher(in, 0 );
399 }
400
401 /**
402 * 加密8字节
403 */
404 private void encrypt8Bytes() {
405 // 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
406 for (pos = 0 ; pos < 8 ; pos ++ ) {
407 if (header)
408 plain[pos] ^= prePlain[pos];
409 else
410 plain[pos] ^= out[preCrypt + pos];
411 }
412 // 这个完成我上面说的 f(plain ^ preCrypt)
413 byte [] crypted = encipher(plain);
414 // 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了
415 System.arraycopy(crypted, 0 , out, crypt, 8 );
416
417 // 这个完成了 f(plain ^ preCrypt) ^ prePlain,ok,下面拷贝一下就行了
418 for (pos = 0 ; pos < 8 ; pos ++ )
419 out[crypt + pos] ^= prePlain[pos];
420 System.arraycopy(plain, 0 , prePlain, 0 , 8 );
421
422 // 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
423 preCrypt = crypt;
424 crypt += 8 ;
425 pos = 0 ;
426 header = false ;
427 }
428
429 /**
430 * 解密8个字节
431 *
432 * @param in
433 * 密文字节数组
434 * @param offset
435 * 从何处开始解密
436 * @param len
437 * 密文的长度
438 * @return
439 * true表示解密成功
440 */
441 private boolean decrypt8Bytes( byte [] in , int offset, int len) {
442 // 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
443 for (pos = 0 ; pos < 8 ; pos ++ ) {
444 if (contextStart + pos >= len)
445 return true ;
446 prePlain[pos] ^= in[offset + crypt + pos];
447 }
448
449 // 好,这里执行到了 d(crypt ^ prePlain)
450 prePlain = decipher(prePlain);
451 if (prePlain == null )
452 return false ;
453
454 // 解密完成,最后一步好像没做?
455 // 这里最后一步放到decrypt里面去做了,因为解密的步骤有点不太一样
456 // 调整这些变量的值先
457 contextStart += 8 ;
458 crypt += 8 ;
459 pos = 0 ;
460 return true ;
461 }
462
463 /**
464 * 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值
465 * 随机因子可以使相同的明文每次加密出来的密文都不一样
466 *
467 * @return
468 * 随机因子
469 */
470 private int rand() {
471 return random.nextInt();
472 }
473 }
貌似比较长啊...童鞋们努力吧~~
哎呀, 头疼...