///
/// 下面不是我写的,我所做的就是把所调到所有的类都集成在一起. ///
/// 我前面有一篇VB.NET版的就是根据这个写的. ///
/// 本人E-Mail: liwqbasic[AT]gamail.com ([AT]换成@) ///
/// QQ: &H12C214E9 [0x12C214E9] ///
///
1
using
System;
2 namespace OverredQQ.QQCore
3 {
4 /// <summary>
5 /// 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
6 /// 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
7 /// f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt) ˆ
8 /// prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ
9 /// preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
10 /// 填充的字节数与原始明文长度有关,填充的方法是:
11 ///
12 /// <code>
13 ///
14 /// ------- 消息填充算法 -----------
15 /// a = (明文长度 + 10) mod 8
16 /// if(a 不等于 0) a = 8 - a;
17 /// b = 随机数 & 0xF8 | a; 这个的作用是把a的值保存了下来
18 /// plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
19 /// plain[1 至 a+2] = 随机数 & 0xFF; 这里用随机数填充明文的第1到第a+2个字节
20 /// plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
21 /// plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
22 /// ------- 消息填充算法 ------------
23 ///
24 /// </code>
25 ///
26 /// </summary>
27 /// <author>
28 ///
29 /// </author>
30 /// <author>
31 ///
32 /// </author>
33 /// <author>
34 /// overred
35 /// </author>
36
37 public struct Crypter
38 {
39 /// <summary>
40 /// 指向当前的明文块
41 /// </summary>
42 private byte [] plain;
43 /// <summary>
44 /// 这指向前面一个明文块
45 /// </summary>
46 private byte [] prePlain;
47 /// <summary>
48 /// 输出的密文或者明文
49 /// </summary>
50 private byte [] output;
51 /// <summary>
52 /// 当前加密的密文位置和上一次加密的密文块位置,他们相差8
53 /// </summary>
54 private int crypt, preCrypt;
55 /// <summary>
56 /// 当前处理的加密解密块的位置
57 /// </summary>
58 private int pos;
59 /// <summary>
60 /// 填充数
61 /// </summary>
62 private int padding;
63 /// <summary>
64 /// 密钥
65 /// </summary>
66 private byte [] key;
67 /// <summary>
68 /// 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
69 /// 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
70 /// </summary>
71 private bool header;
72 /// <summary>
73 /// 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
74 /// 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
75 /// </summary>
76 private int contextStart;
77
78 /// <summary>
79 /// 随机类
80 /// </summary>
81 private static System.Random random_Renamed_Field;
82
83 /// <summary>
84 /// 随机数对象
85 /// </summary>
86 private static System.Random random;
87
88 /// <summary>
89 /// 随机数对象
90 /// </summary>
91 public static System.Random xRandom
92 {
93 get
94 {
95 if (random_Renamed_Field == null )
96 random_Renamed_Field = new System.Random();
97 return random_Renamed_Field;
98 }
99 }
100
101
102 public static byte [] ToBytes( uint a, uint b)
103 {
104 byte [] bytes = new byte [ 8 ];
105 bytes[ 0 ] = ( byte )(a >> 24 );
106 bytes[ 1 ] = ( byte )(a >> 16 );
107 bytes[ 2 ] = ( byte )(a >> 8 );
108 bytes[ 3 ] = ( byte )a;
109 bytes[ 4 ] = ( byte )(b >> 24 );
110 bytes[ 5 ] = ( byte )(b >> 16 );
111 bytes[ 6 ] = ( byte )(b >> 8 );
112 bytes[ 7 ] = ( byte )b;
113 return bytes;
114 }
115
116
117 /// <summary>
118 /// 把字节数组从offset开始的len个字节转换成一个unsigned int, 因为C#里面有unsigned,所以unsigned
119 /// int使用uint表示的。如果len大于4,则认为len等于4。如果len小于4,则高位填0 <br>
120 /// (edited by ) 改变了算法, 性能稍微好一点. 在我的机器上测试10000次, 原始算法花费18s, 这个算法花费12s.
121 /// </summary>
122 /// <param name="input">
123 /// 字节数组.
124 /// </param>
125 /// <param name="offset">
126 /// 从哪里开始转换.
127 /// </param>
128 /// <param name="len">
129 /// 转换长度, 如果len超过8则忽略后面的
130 /// </param>
131 /// <returns>
132 /// </returns>
133 public static uint GetUInt( byte [] input, int offset, int len)
134 {
135 uint ret = 0 ;
136 int end = (len > 4 ) ? (offset + 4 ) : (offset + len);
137 for ( int i = offset; i < end; i ++ )
138 {
139 ret <<= 8 ;
140 ret |= input[i];
141 }
142 return ret;
143 }
144
145 /// <param name="input"> 需要被解密的密文 </param>
146 /// <param name="key"> 密钥 </param>
147 /// <returns> Message 已解密的消息 </returns>
148 public static byte [] Decrypt( byte [] input, byte [] key)
149 {
150 Crypter crypter = new Crypter();
151 crypter.header = true ;
152 return crypter.Decrypt0(input, key);
153 }
154
155 /// <param name="input"> 需要被解密的密文 </param>
156 /// <param name="key"> 密钥 </param>
157 /// <returns> Message 已解密的消息 </returns>
158 public static byte [] Decrypt( byte [] input, int offset, int len, byte [] key)
159 {
160 Crypter crypter = new Crypter();
161 crypter.header = true ;
162 return crypter.Decrypt0(input, offset, len, key);
163 }
164
165 /// <param name="input"> 需要加密的明文 </param>
166 /// <param name="key"> 密钥 </param>
167 /// <returns> Message 密文 </returns>
168 public static byte [] Encrypt( byte [] input, byte [] key)
169 {
170 Crypter crypter = new Crypter();
171 crypter.header = true ;
172 return crypter.Encrypt0(input, key);
173 }
174
175 /// <param name="input"> 需要加密的明文 </param>
176 /// <param name="key"> 密钥 </param>
177 /// <returns> Message 密文 </returns>
178 public static byte [] Encrypt( byte [] input, int offset, int len, byte [] key)
179 {
180 Crypter crypter = new Crypter();
181 crypter.header = true ;
182 return crypter.Encrypt0(input, offset, len, key);
183 }
184
185 /// <summary>
186 /// 抛出异常。
187 /// </summary>
188 /// <param name="message"> 异常信息 </param>
189 private static void throwException( string message)
190 {
191 throw new CrypterException(message);
192 }
193
194 /// <summary> 解密 </summary>
195 /// <param name="input">
196 /// 密文
197 /// </param>
198 /// <param name="offset">
199 /// 密文开始的位置
200 /// </param>
201 /// <param name="len">
202 /// 密文长度
203 /// </param>
204 /// <param name="key">
205 /// 密钥
206 /// </param>
207 /// <returns> 明文
208 /// </returns>
209 public byte [] Decrypt0( byte [] input, int offset, int len, byte [] key)
210 {
211 crypt = preCrypt = 0 ;
212 this .key = key;
213 int count;
214 byte [] m = new byte [offset + 8 ];
215
216 // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
217 if ((len % 8 != 0 ) || (len < 16 ))
218 throwException( @" len is not correct. " );
219 // 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
220 prePlain = Decipher(input, offset);
221 pos = prePlain[ 0 ] & 0x7 ;
222 // 得到真正明文的长度
223 count = len - pos - 10 ;
224 // 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
225 if (count < 0 )
226 throwException( @" count is not correct " );
227
228 // 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
229 // 第一个8字节块也没有preCrypt,所有这里建一个全0的
230 for ( int i = offset; i < m.Length; i ++ )
231 m[i] = 0 ;
232 // 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
233 output = new byte [count];
234 // 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
235 preCrypt = 0 ;
236 // 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
237 crypt = 8 ;
238 // 自然这个也是8
239 contextStart = 8 ;
240 // 加1,和加密算法是对应的
241 pos ++ ;
242
243 // 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
244 // 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
245 // 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
246 // 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
247 padding = 1 ;
248 while (padding <= 2 )
249 {
250 if (pos < 8 )
251 {
252 pos ++ ;
253 padding ++ ;
254 }
255 if (pos == 8 )
256 {
257 m = input;
258 if ( ! Decrypt8Bytes(input, offset, len))
259 throwException( @" Decrypt8Bytes() failed. " );
260 }
261 }
262
263 // 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
264 // 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
265 int i2 = 0 ;
266 while (count != 0 )
267 {
268 if (pos < 8 )
269 {
270 output[i2] = ( byte )(m[offset + preCrypt + pos] ^ prePlain[pos]);
271 i2 ++ ;
272 count -- ;
273 pos ++ ;
274 }
275 if (pos == 8 )
276 {
277 m = input;
278 preCrypt = crypt - 8 ;
279 if ( ! Decrypt8Bytes(input, offset, len))
280 throwException( @" Decrypt8Bytes() failed. " );
281 }
282 }
283
284 // 最后的解密部分,上面一个while已经把明文都解出来了,到了这里还剩下什么?对了,还剩下尾部的填充,应该全是0
285 // 所以这里有检查是否解密了之后是0,如果不是的话那肯定出错了,所以返回null
286 for (padding = 1 ; padding < 8 ; padding ++ )
287 {
288 if (pos < 8 )
289 {
290 if ((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0 )
291 throwException( @" tail is not filled correct. " );
292 pos ++ ;
293 }
294 if (pos == 8 )
295 {
296 m = input;
297 preCrypt = crypt;
298 if ( ! Decrypt8Bytes(input, offset, len))
299 throwException( @" Decrypt8Bytes() failed. " );
300 }
301 }
302 return output;
303 }
304
305 /// <param name="input">
306 /// 需要被解密的密文
307 /// </param>
308 /// <param name="key">
309 /// 密钥
310 /// </param>
311 /// <returns> Message 已解密的消息
312 /// </returns>
313 public byte [] Decrypt0( byte [] input, byte [] key)
314 {
315 return Decrypt(input, 0 , input.Length, key);
316 }
317
318 /// <summary> 加密 </summary>
319 /// <param name="input"> 明文字节数组
320 /// </param>
321 /// <param name="offset"> 开始加密的偏移
322 /// </param>
323 /// <param name="len"> 加密长度
324 /// </param>
325 /// <param name="key"> 密钥
326 /// </param>
327 /// <returns> 密文字节数组
328 /// </returns>
329 public byte [] Encrypt0( byte [] input, int offset, int len, byte [] key)
330 {
331 plain = new byte [ 8 ];
332 prePlain = new byte [ 8 ];
333 pos = 1 ;
334 padding = 0 ;
335 crypt = preCrypt = 0 ;
336 this .key = key;
337 header = true ;
338
339 // 计算头部填充字节数
340 pos = (len + 0x0A ) % 8 ;
341 if (pos != 0 )
342 pos = 8 - pos;
343 // 计算输出的密文长度
344 output = new byte [len + pos + 10 ];
345 // 这里的操作把pos存到了plain的第一个字节里面
346 // 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
347 int t1 = 0x7648354F ;
348
349 plain[ 0 ] = ( byte )((t1 & 0xF8 ) | pos);
350
351 // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
352 for ( int i = 1 ; i <= pos; i ++ )
353 plain[i] = ( byte )(t1 & 0xFF );
354 pos ++ ;
355 // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
356 for ( int i = 0 ; i < 8 ; i ++ )
357 prePlain[i] = ( byte )( 0x0 );
358
359 // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
360 padding = 1 ;
361 while (padding <= 2 )
362 {
363 if (pos < 8 )
364 {
365 plain[pos ++ ] = ( byte )(t1 & 0xFF );
366 padding ++ ;
367 }
368 if (pos == 8 )
369 Encrypt8Bytes();
370 }
371
372 // 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
373 int i2 = offset;
374 while (len > 0 )
375 {
376 if (pos < 8 )
377 {
378 plain[pos ++ ] = input[i2 ++ ];
379 len -- ;
380 }
381 if (pos == 8 )
382 Encrypt8Bytes();
383 }
384
385 // 最后填上0,以保证是8字节的倍数
386 padding = 1 ;
387 while (padding <= 7 )
388 {
389 if (pos < 8 )
390 {
391 plain[pos ++ ] = ( byte )( 0x0 );
392 padding ++ ;
393 }
394 if (pos == 8 )
395 Encrypt8Bytes();
396 }
397
398 return output;
399 }
400
401 /// <param name="input">
402 /// 需要加密的明文
403 /// </param>
404 /// <param name="key">
405 /// 密钥
406 /// </param>
407 /// <returns> Message 密文
408 /// </returns>
409 public byte [] Encrypt0( byte [] input, byte [] key)
410 {
411 return Encrypt(input, 0 , input.Length, key);
412 }
413
414 /// <summary>
415 /// 加密一个8字节块
416 /// </summary>
417 /// <param name="input">
418 /// 明文字节数组
419 /// </param>
420 /// <returns>
421 /// 密文字节数组
422 /// </returns>
423 private byte [] Encipher( byte [] input)
424 {
425 if (key == null )
426 {
427 throwException( @" key is null. " );
428 }
429 // 迭代次数,16次
430 int loop = 0x10 ;
431 // 得到明文和密钥的各个部分,注意c#有无符号类型,所以为了表示一个无符号的整数
432 // 我们用了uint,这个uint的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的uint也都是一样的
433 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
434 uint y = GetUInt(input, 0 , 4 );
435 uint z = GetUInt(input, 4 , 4 );
436 uint a = GetUInt(key, 0 , 4 );
437 uint b = GetUInt(key, 4 , 4 );
438 uint c = GetUInt(key, 8 , 4 );
439 uint d = GetUInt(key, 12 , 4 );
440 // 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
441 // 这个数是TEA算法的delta,实际是就是sqr(5)-1 * 2^31
442 uint sum = 0 ;
443 uint delta = 0x9E3779B9 ;
444 // delta &= unchecked((int) 0xFFFFFFFFL);
445
446 // 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
447 while (loop -- > 0 )
448 {
449 sum += delta;
450 // sum &= unchecked((int) 0xFFFFFFFFL);
451 y += ((z << 4 ) + a) ^ (z + sum) ^ (z >> 5 ) + b;
452 // y &= unchecked((int) 0xFFFFFFFFL);
453 z += ((y << 4 ) + c) ^ (y + sum) ^ (y >> 5 ) + d;
454 // z &= unchecked((int) 0xFFFFFFFFL);
455 }
456
457 // 最后,我们输出密文,因为我用的uint,所以需要强制转换一下变成int
458
459 return ToBytes(y, z);
460 }
461
462 /// <summary>
463 /// 解密从offset开始的8字节密文
464 /// </summary>
465 /// <param name="input">
466 /// 密文字节数组
467 /// </param>
468 /// <param name="offset">
469 /// 密文开始位置
470 /// </param>
471 /// <returns>
472 /// 明文
473 /// </returns>
474 private byte [] Decipher( byte [] input, int offset)
475 {
476 if (key == null )
477 {
478 throwException( @" key is null. " );
479 }
480 // 迭代次数,16次
481 int loop = 0x10 ;
482 // 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
483 // 我们用了uint,这个uint的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的uint也都是一样的
484 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
485 uint y = GetUInt(input, offset, 4 );
486 uint z = GetUInt(input, offset + 4 , 4 );
487 uint a = GetUInt(key, 0 , 4 );
488 uint b = GetUInt(key, 4 , 4 );
489 uint c = GetUInt(key, 8 , 4 );
490 uint d = GetUInt(key, 12 , 4 );
491 // 算法的一些控制变量,为什么sum在这里也有数了呢,这个sum嘛就是和迭代次数有关系了
492 // 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
493 // 得到什么? Yeah,得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样
494 // 才能解密呀~~
495 uint sum = 0xE3779B90 ;
496 // sum &= unchecked((int) 0xFFFFFFFFL);
497 uint delta = 0x9E3779B9 ;
498 // delta &= unchecked((int) 0xFFFFFFFFL);
499
500 // 迭代开始了, #_#
501 while (loop -- > 0 )
502 {
503 z -= ((y << 4 ) + c) ^ (y + sum) ^ ((y >> 5 ) + d);
504 // z &= unchecked((int) 0xFFFFFFFFL);
505 y -= ((z << 4 ) + a) ^ (z + sum) ^ ((z >> 5 ) + b);
506 // y &= unchecked((int) 0xFFFFFFFFL);
507 sum -= delta;
508 // sum &= unchecked((int) 0xFFFFFFFFL);
509 }
510
511 // 输出明文,注意要转成int
512
513 return ToBytes(y, z);
514 }
515
516 /// <summary>
517 /// 解密
518 /// </summary>
519 /// <param name="input">
520 /// 密文
521 /// </param>
522 /// <returns>
523 /// 明文
524 /// </returns>
525 private byte [] Decipher( byte [] input)
526 {
527 return Decipher(input, 0 );
528 }
529
530 /// <summary>
531 /// 加密8字节
532 /// </summary>
533 private void Encrypt8Bytes()
534 {
535 // 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
536 for (pos = 0 ; pos < 8 ; pos ++ )
537 {
538 if (header)
539 plain[pos] ^= prePlain[pos];
540 else
541 plain[pos] ^= output[preCrypt + pos];
542 }
543 // 这个完成到了我上面说的 f(plain ^ preCrypt)
544 byte [] crypted = Encipher(plain);
545 // 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了
546 Array.Copy(crypted, 0 , output, crypt, 8 );
547
548 // 这个就是完成到了 f(plain ^ preCrypt) ^ prePlain,ok,完成了,下面拷贝一下就行了
549 for (pos = 0 ; pos < 8 ; pos ++ )
550 output[crypt + pos] ^= prePlain[pos];
551 Array.Copy(plain, 0 , prePlain, 0 , 8 );
552
553 // 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
554 preCrypt = crypt;
555 crypt += 8 ;
556 pos = 0 ;
557 header = false ;
558 }
559
560 /// <summary>
561 /// 解密8个字节
562 /// </summary>
563 /// <param name="input">
564 /// 密文字节数组
565 /// </param>
566 /// <param name="offset">
567 /// 从何处开始解密
568 /// </param>
569 /// <param name="len">
570 /// 密文的长度
571 /// </param>
572 /// <returns>
573 /// true表示解密成功
574 /// </returns>
575 private bool Decrypt8Bytes( byte [] input, int offset, int len)
576 {
577 // 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
578 for (pos = 0 ; pos < 8 ; pos ++ )
579 {
580 if (contextStart + pos >= len)
581 return true ;
582 prePlain[pos] ^= input[offset + crypt + pos];
583 }
584
585 // 好,这里执行到了 d(crypt ^ prePlain)
586 prePlain = Decipher(prePlain);
587 if (prePlain == null )
588 return false ;
589
590 // 解密完成,wait,没完成哦,最后一步没做哦?
591 // 这里最后一步放到Decrypt里面去做了,因为解密的步骤毕竟还是不太一样嘛
592 // 调整这些变量的值先
593 contextStart += 8 ;
594 crypt += 8 ;
595 pos = 0 ;
596 return true ;
597 }
598
599 /// <summary>
600 /// 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值。
601 /// 随机因子可以使相同的明文每次加密出来的密文都不一样。
602 /// </summary>
603 /// <returns>
604 /// 随机因子
605 /// </returns>
606 private int Rand()
607 {
608 return xRandom.Next();
609 }
610 static Crypter()
611 {
612 random = xRandom;
613 }
614 }
615
616 /// <summary>
617 /// 加密/解密出错异常。
618 /// </summary>
619 public class CrypterException : Exception
620 {
621 public CrypterException( string message)
622 : base (message)
623 {
624 }
625 }
626 }
2 namespace OverredQQ.QQCore
3 {
4 /// <summary>
5 /// 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
6 /// 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
7 /// f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt) ˆ
8 /// prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ
9 /// preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
10 /// 填充的字节数与原始明文长度有关,填充的方法是:
11 ///
12 /// <code>
13 ///
14 /// ------- 消息填充算法 -----------
15 /// a = (明文长度 + 10) mod 8
16 /// if(a 不等于 0) a = 8 - a;
17 /// b = 随机数 & 0xF8 | a; 这个的作用是把a的值保存了下来
18 /// plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
19 /// plain[1 至 a+2] = 随机数 & 0xFF; 这里用随机数填充明文的第1到第a+2个字节
20 /// plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
21 /// plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
22 /// ------- 消息填充算法 ------------
23 ///
24 /// </code>
25 ///
26 /// </summary>
27 /// <author>
28 ///
29 /// </author>
30 /// <author>
31 ///
32 /// </author>
33 /// <author>
34 /// overred
35 /// </author>
36
37 public struct Crypter
38 {
39 /// <summary>
40 /// 指向当前的明文块
41 /// </summary>
42 private byte [] plain;
43 /// <summary>
44 /// 这指向前面一个明文块
45 /// </summary>
46 private byte [] prePlain;
47 /// <summary>
48 /// 输出的密文或者明文
49 /// </summary>
50 private byte [] output;
51 /// <summary>
52 /// 当前加密的密文位置和上一次加密的密文块位置,他们相差8
53 /// </summary>
54 private int crypt, preCrypt;
55 /// <summary>
56 /// 当前处理的加密解密块的位置
57 /// </summary>
58 private int pos;
59 /// <summary>
60 /// 填充数
61 /// </summary>
62 private int padding;
63 /// <summary>
64 /// 密钥
65 /// </summary>
66 private byte [] key;
67 /// <summary>
68 /// 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
69 /// 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
70 /// </summary>
71 private bool header;
72 /// <summary>
73 /// 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
74 /// 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
75 /// </summary>
76 private int contextStart;
77
78 /// <summary>
79 /// 随机类
80 /// </summary>
81 private static System.Random random_Renamed_Field;
82
83 /// <summary>
84 /// 随机数对象
85 /// </summary>
86 private static System.Random random;
87
88 /// <summary>
89 /// 随机数对象
90 /// </summary>
91 public static System.Random xRandom
92 {
93 get
94 {
95 if (random_Renamed_Field == null )
96 random_Renamed_Field = new System.Random();
97 return random_Renamed_Field;
98 }
99 }
100
101
102 public static byte [] ToBytes( uint a, uint b)
103 {
104 byte [] bytes = new byte [ 8 ];
105 bytes[ 0 ] = ( byte )(a >> 24 );
106 bytes[ 1 ] = ( byte )(a >> 16 );
107 bytes[ 2 ] = ( byte )(a >> 8 );
108 bytes[ 3 ] = ( byte )a;
109 bytes[ 4 ] = ( byte )(b >> 24 );
110 bytes[ 5 ] = ( byte )(b >> 16 );
111 bytes[ 6 ] = ( byte )(b >> 8 );
112 bytes[ 7 ] = ( byte )b;
113 return bytes;
114 }
115
116
117 /// <summary>
118 /// 把字节数组从offset开始的len个字节转换成一个unsigned int, 因为C#里面有unsigned,所以unsigned
119 /// int使用uint表示的。如果len大于4,则认为len等于4。如果len小于4,则高位填0 <br>
120 /// (edited by ) 改变了算法, 性能稍微好一点. 在我的机器上测试10000次, 原始算法花费18s, 这个算法花费12s.
121 /// </summary>
122 /// <param name="input">
123 /// 字节数组.
124 /// </param>
125 /// <param name="offset">
126 /// 从哪里开始转换.
127 /// </param>
128 /// <param name="len">
129 /// 转换长度, 如果len超过8则忽略后面的
130 /// </param>
131 /// <returns>
132 /// </returns>
133 public static uint GetUInt( byte [] input, int offset, int len)
134 {
135 uint ret = 0 ;
136 int end = (len > 4 ) ? (offset + 4 ) : (offset + len);
137 for ( int i = offset; i < end; i ++ )
138 {
139 ret <<= 8 ;
140 ret |= input[i];
141 }
142 return ret;
143 }
144
145 /// <param name="input"> 需要被解密的密文 </param>
146 /// <param name="key"> 密钥 </param>
147 /// <returns> Message 已解密的消息 </returns>
148 public static byte [] Decrypt( byte [] input, byte [] key)
149 {
150 Crypter crypter = new Crypter();
151 crypter.header = true ;
152 return crypter.Decrypt0(input, key);
153 }
154
155 /// <param name="input"> 需要被解密的密文 </param>
156 /// <param name="key"> 密钥 </param>
157 /// <returns> Message 已解密的消息 </returns>
158 public static byte [] Decrypt( byte [] input, int offset, int len, byte [] key)
159 {
160 Crypter crypter = new Crypter();
161 crypter.header = true ;
162 return crypter.Decrypt0(input, offset, len, key);
163 }
164
165 /// <param name="input"> 需要加密的明文 </param>
166 /// <param name="key"> 密钥 </param>
167 /// <returns> Message 密文 </returns>
168 public static byte [] Encrypt( byte [] input, byte [] key)
169 {
170 Crypter crypter = new Crypter();
171 crypter.header = true ;
172 return crypter.Encrypt0(input, key);
173 }
174
175 /// <param name="input"> 需要加密的明文 </param>
176 /// <param name="key"> 密钥 </param>
177 /// <returns> Message 密文 </returns>
178 public static byte [] Encrypt( byte [] input, int offset, int len, byte [] key)
179 {
180 Crypter crypter = new Crypter();
181 crypter.header = true ;
182 return crypter.Encrypt0(input, offset, len, key);
183 }
184
185 /// <summary>
186 /// 抛出异常。
187 /// </summary>
188 /// <param name="message"> 异常信息 </param>
189 private static void throwException( string message)
190 {
191 throw new CrypterException(message);
192 }
193
194 /// <summary> 解密 </summary>
195 /// <param name="input">
196 /// 密文
197 /// </param>
198 /// <param name="offset">
199 /// 密文开始的位置
200 /// </param>
201 /// <param name="len">
202 /// 密文长度
203 /// </param>
204 /// <param name="key">
205 /// 密钥
206 /// </param>
207 /// <returns> 明文
208 /// </returns>
209 public byte [] Decrypt0( byte [] input, int offset, int len, byte [] key)
210 {
211 crypt = preCrypt = 0 ;
212 this .key = key;
213 int count;
214 byte [] m = new byte [offset + 8 ];
215
216 // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
217 if ((len % 8 != 0 ) || (len < 16 ))
218 throwException( @" len is not correct. " );
219 // 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
220 prePlain = Decipher(input, offset);
221 pos = prePlain[ 0 ] & 0x7 ;
222 // 得到真正明文的长度
223 count = len - pos - 10 ;
224 // 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
225 if (count < 0 )
226 throwException( @" count is not correct " );
227
228 // 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
229 // 第一个8字节块也没有preCrypt,所有这里建一个全0的
230 for ( int i = offset; i < m.Length; i ++ )
231 m[i] = 0 ;
232 // 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
233 output = new byte [count];
234 // 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
235 preCrypt = 0 ;
236 // 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
237 crypt = 8 ;
238 // 自然这个也是8
239 contextStart = 8 ;
240 // 加1,和加密算法是对应的
241 pos ++ ;
242
243 // 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
244 // 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
245 // 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
246 // 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
247 padding = 1 ;
248 while (padding <= 2 )
249 {
250 if (pos < 8 )
251 {
252 pos ++ ;
253 padding ++ ;
254 }
255 if (pos == 8 )
256 {
257 m = input;
258 if ( ! Decrypt8Bytes(input, offset, len))
259 throwException( @" Decrypt8Bytes() failed. " );
260 }
261 }
262
263 // 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
264 // 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
265 int i2 = 0 ;
266 while (count != 0 )
267 {
268 if (pos < 8 )
269 {
270 output[i2] = ( byte )(m[offset + preCrypt + pos] ^ prePlain[pos]);
271 i2 ++ ;
272 count -- ;
273 pos ++ ;
274 }
275 if (pos == 8 )
276 {
277 m = input;
278 preCrypt = crypt - 8 ;
279 if ( ! Decrypt8Bytes(input, offset, len))
280 throwException( @" Decrypt8Bytes() failed. " );
281 }
282 }
283
284 // 最后的解密部分,上面一个while已经把明文都解出来了,到了这里还剩下什么?对了,还剩下尾部的填充,应该全是0
285 // 所以这里有检查是否解密了之后是0,如果不是的话那肯定出错了,所以返回null
286 for (padding = 1 ; padding < 8 ; padding ++ )
287 {
288 if (pos < 8 )
289 {
290 if ((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0 )
291 throwException( @" tail is not filled correct. " );
292 pos ++ ;
293 }
294 if (pos == 8 )
295 {
296 m = input;
297 preCrypt = crypt;
298 if ( ! Decrypt8Bytes(input, offset, len))
299 throwException( @" Decrypt8Bytes() failed. " );
300 }
301 }
302 return output;
303 }
304
305 /// <param name="input">
306 /// 需要被解密的密文
307 /// </param>
308 /// <param name="key">
309 /// 密钥
310 /// </param>
311 /// <returns> Message 已解密的消息
312 /// </returns>
313 public byte [] Decrypt0( byte [] input, byte [] key)
314 {
315 return Decrypt(input, 0 , input.Length, key);
316 }
317
318 /// <summary> 加密 </summary>
319 /// <param name="input"> 明文字节数组
320 /// </param>
321 /// <param name="offset"> 开始加密的偏移
322 /// </param>
323 /// <param name="len"> 加密长度
324 /// </param>
325 /// <param name="key"> 密钥
326 /// </param>
327 /// <returns> 密文字节数组
328 /// </returns>
329 public byte [] Encrypt0( byte [] input, int offset, int len, byte [] key)
330 {
331 plain = new byte [ 8 ];
332 prePlain = new byte [ 8 ];
333 pos = 1 ;
334 padding = 0 ;
335 crypt = preCrypt = 0 ;
336 this .key = key;
337 header = true ;
338
339 // 计算头部填充字节数
340 pos = (len + 0x0A ) % 8 ;
341 if (pos != 0 )
342 pos = 8 - pos;
343 // 计算输出的密文长度
344 output = new byte [len + pos + 10 ];
345 // 这里的操作把pos存到了plain的第一个字节里面
346 // 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
347 int t1 = 0x7648354F ;
348
349 plain[ 0 ] = ( byte )((t1 & 0xF8 ) | pos);
350
351 // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
352 for ( int i = 1 ; i <= pos; i ++ )
353 plain[i] = ( byte )(t1 & 0xFF );
354 pos ++ ;
355 // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
356 for ( int i = 0 ; i < 8 ; i ++ )
357 prePlain[i] = ( byte )( 0x0 );
358
359 // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
360 padding = 1 ;
361 while (padding <= 2 )
362 {
363 if (pos < 8 )
364 {
365 plain[pos ++ ] = ( byte )(t1 & 0xFF );
366 padding ++ ;
367 }
368 if (pos == 8 )
369 Encrypt8Bytes();
370 }
371
372 // 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
373 int i2 = offset;
374 while (len > 0 )
375 {
376 if (pos < 8 )
377 {
378 plain[pos ++ ] = input[i2 ++ ];
379 len -- ;
380 }
381 if (pos == 8 )
382 Encrypt8Bytes();
383 }
384
385 // 最后填上0,以保证是8字节的倍数
386 padding = 1 ;
387 while (padding <= 7 )
388 {
389 if (pos < 8 )
390 {
391 plain[pos ++ ] = ( byte )( 0x0 );
392 padding ++ ;
393 }
394 if (pos == 8 )
395 Encrypt8Bytes();
396 }
397
398 return output;
399 }
400
401 /// <param name="input">
402 /// 需要加密的明文
403 /// </param>
404 /// <param name="key">
405 /// 密钥
406 /// </param>
407 /// <returns> Message 密文
408 /// </returns>
409 public byte [] Encrypt0( byte [] input, byte [] key)
410 {
411 return Encrypt(input, 0 , input.Length, key);
412 }
413
414 /// <summary>
415 /// 加密一个8字节块
416 /// </summary>
417 /// <param name="input">
418 /// 明文字节数组
419 /// </param>
420 /// <returns>
421 /// 密文字节数组
422 /// </returns>
423 private byte [] Encipher( byte [] input)
424 {
425 if (key == null )
426 {
427 throwException( @" key is null. " );
428 }
429 // 迭代次数,16次
430 int loop = 0x10 ;
431 // 得到明文和密钥的各个部分,注意c#有无符号类型,所以为了表示一个无符号的整数
432 // 我们用了uint,这个uint的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的uint也都是一样的
433 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
434 uint y = GetUInt(input, 0 , 4 );
435 uint z = GetUInt(input, 4 , 4 );
436 uint a = GetUInt(key, 0 , 4 );
437 uint b = GetUInt(key, 4 , 4 );
438 uint c = GetUInt(key, 8 , 4 );
439 uint d = GetUInt(key, 12 , 4 );
440 // 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
441 // 这个数是TEA算法的delta,实际是就是sqr(5)-1 * 2^31
442 uint sum = 0 ;
443 uint delta = 0x9E3779B9 ;
444 // delta &= unchecked((int) 0xFFFFFFFFL);
445
446 // 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
447 while (loop -- > 0 )
448 {
449 sum += delta;
450 // sum &= unchecked((int) 0xFFFFFFFFL);
451 y += ((z << 4 ) + a) ^ (z + sum) ^ (z >> 5 ) + b;
452 // y &= unchecked((int) 0xFFFFFFFFL);
453 z += ((y << 4 ) + c) ^ (y + sum) ^ (y >> 5 ) + d;
454 // z &= unchecked((int) 0xFFFFFFFFL);
455 }
456
457 // 最后,我们输出密文,因为我用的uint,所以需要强制转换一下变成int
458
459 return ToBytes(y, z);
460 }
461
462 /// <summary>
463 /// 解密从offset开始的8字节密文
464 /// </summary>
465 /// <param name="input">
466 /// 密文字节数组
467 /// </param>
468 /// <param name="offset">
469 /// 密文开始位置
470 /// </param>
471 /// <returns>
472 /// 明文
473 /// </returns>
474 private byte [] Decipher( byte [] input, int offset)
475 {
476 if (key == null )
477 {
478 throwException( @" key is null. " );
479 }
480 // 迭代次数,16次
481 int loop = 0x10 ;
482 // 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
483 // 我们用了uint,这个uint的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的uint也都是一样的
484 // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
485 uint y = GetUInt(input, offset, 4 );
486 uint z = GetUInt(input, offset + 4 , 4 );
487 uint a = GetUInt(key, 0 , 4 );
488 uint b = GetUInt(key, 4 , 4 );
489 uint c = GetUInt(key, 8 , 4 );
490 uint d = GetUInt(key, 12 , 4 );
491 // 算法的一些控制变量,为什么sum在这里也有数了呢,这个sum嘛就是和迭代次数有关系了
492 // 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
493 // 得到什么? Yeah,得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样
494 // 才能解密呀~~
495 uint sum = 0xE3779B90 ;
496 // sum &= unchecked((int) 0xFFFFFFFFL);
497 uint delta = 0x9E3779B9 ;
498 // delta &= unchecked((int) 0xFFFFFFFFL);
499
500 // 迭代开始了, #_#
501 while (loop -- > 0 )
502 {
503 z -= ((y << 4 ) + c) ^ (y + sum) ^ ((y >> 5 ) + d);
504 // z &= unchecked((int) 0xFFFFFFFFL);
505 y -= ((z << 4 ) + a) ^ (z + sum) ^ ((z >> 5 ) + b);
506 // y &= unchecked((int) 0xFFFFFFFFL);
507 sum -= delta;
508 // sum &= unchecked((int) 0xFFFFFFFFL);
509 }
510
511 // 输出明文,注意要转成int
512
513 return ToBytes(y, z);
514 }
515
516 /// <summary>
517 /// 解密
518 /// </summary>
519 /// <param name="input">
520 /// 密文
521 /// </param>
522 /// <returns>
523 /// 明文
524 /// </returns>
525 private byte [] Decipher( byte [] input)
526 {
527 return Decipher(input, 0 );
528 }
529
530 /// <summary>
531 /// 加密8字节
532 /// </summary>
533 private void Encrypt8Bytes()
534 {
535 // 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
536 for (pos = 0 ; pos < 8 ; pos ++ )
537 {
538 if (header)
539 plain[pos] ^= prePlain[pos];
540 else
541 plain[pos] ^= output[preCrypt + pos];
542 }
543 // 这个完成到了我上面说的 f(plain ^ preCrypt)
544 byte [] crypted = Encipher(plain);
545 // 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了
546 Array.Copy(crypted, 0 , output, crypt, 8 );
547
548 // 这个就是完成到了 f(plain ^ preCrypt) ^ prePlain,ok,完成了,下面拷贝一下就行了
549 for (pos = 0 ; pos < 8 ; pos ++ )
550 output[crypt + pos] ^= prePlain[pos];
551 Array.Copy(plain, 0 , prePlain, 0 , 8 );
552
553 // 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
554 preCrypt = crypt;
555 crypt += 8 ;
556 pos = 0 ;
557 header = false ;
558 }
559
560 /// <summary>
561 /// 解密8个字节
562 /// </summary>
563 /// <param name="input">
564 /// 密文字节数组
565 /// </param>
566 /// <param name="offset">
567 /// 从何处开始解密
568 /// </param>
569 /// <param name="len">
570 /// 密文的长度
571 /// </param>
572 /// <returns>
573 /// true表示解密成功
574 /// </returns>
575 private bool Decrypt8Bytes( byte [] input, int offset, int len)
576 {
577 // 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
578 for (pos = 0 ; pos < 8 ; pos ++ )
579 {
580 if (contextStart + pos >= len)
581 return true ;
582 prePlain[pos] ^= input[offset + crypt + pos];
583 }
584
585 // 好,这里执行到了 d(crypt ^ prePlain)
586 prePlain = Decipher(prePlain);
587 if (prePlain == null )
588 return false ;
589
590 // 解密完成,wait,没完成哦,最后一步没做哦?
591 // 这里最后一步放到Decrypt里面去做了,因为解密的步骤毕竟还是不太一样嘛
592 // 调整这些变量的值先
593 contextStart += 8 ;
594 crypt += 8 ;
595 pos = 0 ;
596 return true ;
597 }
598
599 /// <summary>
600 /// 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值。
601 /// 随机因子可以使相同的明文每次加密出来的密文都不一样。
602 /// </summary>
603 /// <returns>
604 /// 随机因子
605 /// </returns>
606 private int Rand()
607 {
608 return xRandom.Next();
609 }
610 static Crypter()
611 {
612 random = xRandom;
613 }
614 }
615
616 /// <summary>
617 /// 加密/解密出错异常。
618 /// </summary>
619 public class CrypterException : Exception
620 {
621 public CrypterException( string message)
622 : base (message)
623 {
624 }
625 }
626 }