[修改]C#版的TEA加密算法,我所发现的比较好的版本。

///////////////////////////////////////////////////////////////////////////
/// 下面不是我写的,我所做的就是把所调到所有的类都集成在一起.    ///
///  我前面有一篇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 &circ; preCrypt) &circ;
  8     /// prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt &circ; prePlain) &circ;
  9     /// preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
 10     /// 填充的字节数与原始明文长度有关,填充的方法是:
 11     /// 
 12     /// <code>
 13     /// 
 14     /// ------- 消息填充算法 ----------- 
 15     /// a = (明文长度 + 10) mod 8
 16     /// if(a 不等于 0) a = 8 - a;
 17     /// b = 随机数 &amp; 0xF8 | a;              这个的作用是把a的值保存了下来
 18     /// plain[0] = b;                       然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
 19     /// plain[1 至 a+2] = 随机数 &amp; 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, 04);
435             uint z =GetUInt(input, 44);
436             uint a =GetUInt(key, 04);
437             uint b =GetUInt(key, 44);
438             uint c =GetUInt(key, 84);
439             uint d =GetUInt(key, 124);
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 + 44);
487             uint a =GetUInt(key, 04);
488             uint b =GetUInt(key, 44);
489             uint c =GetUInt(key, 84);
490             uint d =GetUInt(key, 124);
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, 08);
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 }


 

posted on 2008-03-07 14:54  ExeLive  阅读(2760)  评论(1编辑  收藏  举报

导航