MD5算法和C#实现

MD5算法和C#实现

1 MD5简介

MD5是由麻省理工学院(MIT)的Ronald L. Rivest教授发明的。MD5算法可以接受任意长度的输入并且产生一个128位(bit)的“指纹”或者“消息摘要”输出。通常不同的消息不会产生相同的消息摘要,而且通过消息摘要也无法还原出原始信息。从本质上说,MD5算法是一种验证数据完整性的方法,并且它比其它许多方法都要可靠的多。但是MD5并不是一种完美的方法。

2 术语和概念

2.1 (Bit),字节(Byte)和字(Word

       MD5始终把消息当成一个位(bit)字符串来处理。本文中,一个“字”(Word)是32位,而一个“字节”(Byte)是8位。比如,我们把10001100看成一个字节并且MSBMost Significant Bit)在左边,那就是说,最左边的“1是高位。对于“字”来说,和“字节”有所不同。一个字通常是4字节并且LSBLeast Significant Bit)在左边。比如,10001100 11000011 10010001 01001000 可以看成一个字并且LSB10001100,那就是说,最左边的字节是低位。

2.2 符号

       符号“+”表示以2^32为模的加法。假设Word = Word1 + Word2。如果Word>2^32,那么Word = Word & 0XFFFFFFFF。符号“<<<”表示循环移位。如果X<<<s,那么就表示对X循环移动s位。符号“not”表示按位取反。“v”表示按位或,“xor”表示按位异或。

3 MD5算法简介

       MD5算法中,我们必须把原始消息(字符串,文件等)转换成位字符串。MD5算法只接受位作为输入。假设我们对字符串“abc”产生消息摘要。首先,我们将它转换成位字符串如下:

01100001 01100010 01100011

―――――――――――――

‘a’=97   ‘b’=98   ‘c’=99

这个位字符串的长度为24。下面我们需要5个步骤来计算MD5

3.1 补位

       消息必须进行补位,以使其长度在对512取模以后的余数是448。也就是说,(补位后的消息长度)%512 = 448。即使长度已经满足对512取模后余数是448,补位也必须要进行。

       补位是这样进行的:先补一个1,然后再补0,直到长度满足对512取模后余数是448。总而言之,补位是至少补一位,最多补512位。还是以前面的“abc”为例显示补位的过程。

原始信息: 01100001 01100010 01100011

补位第一步:01100001 01100010 01100011 1

                                                                首先补一个“1

补位第二步:01100001 01100010 01100011 10…..0

                                                                 然后补423个“0

我们可以把最后补位完成后的数据用16进制写成下面的样子

61626380 00000000 00000000 00000000

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000

00000000 00000000

现在,数据的长度是448了,我们可以进行下一步操作。

3.2 补长度

       所谓的补长度是将原始数据的长度补到已经进行了补位操作的消息后面。通常用一个64位的数据来表示原始消息的长度。对于数据长度大于2^64这种极其少见的情况,我们只取其低64位。通常把数据长度分成232位的字,并且先补低位的数据,再补高位的数据。在进行了补长度的操作以后,整个消息就变成下面这样了(16进制格式)

61626380 00000000 00000000 00000000

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000

00000000 00000000 18000000 00000000

如果原始的消息长度超过了512,我们需要将它补成512的倍数。然后我们把整个消息分成一个一个512位的数据块,分别处理每一个数据块,从而得到消息摘要。

3.3 初始化消息摘要缓冲区

       一个四个字长的缓冲区(ABCD)用来存储消息摘要。ABCD都是32位寄存器。这些寄存器被初始化为下面的值(16进制格式,低位在前)

A01 23 45 67 (在我们的程序中,我们需要把它写成A=0x67452301

B89 ab cd ef  (在我们的程序中,我们需要把它写成B=0xefcdab89

Cfe dc ba 98  (在我们的程序中,我们需要把它写成C=0x98badcfe

D76 54 32 10 (在我们的程序中,我们需要把它写成D=0x10325476

3.4 处理16个字长的消息块

       首先我们定义4个辅助函数,每个函数都能接受332位的字,并且输出一个32位的字
     
F(X,Y,Z) = XY v not(X) Z

          G(X,Y,Z) = XZ v Y not(Z)

          H(X,Y,Z) = X xor Y xor Z

          I(X,Y,Z) = Y xor (X v not(Z))

我们使用了一个有64个元素的表T[1…..64]。假设i164,那么:

T[i] = [4294967296*abs(sin(i))][x]表示取x的整数部分。

3.5 计算消息摘要

       我们已经补齐了数据并且有了所有的函数,接下来就可以计算消息摘要了。计算的过程如下:
  
//处理每一个16个字的数据块

      // A另存为AAB存为BBC存为CCD存为DD

     AA = A

     BB = B

     CC = C

     DD = D

     // 第一轮

     //  [abcd k s i] 表示:a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s).

     // 进行下面的16步操作

     [ABCD  0  7  1]    [DABC  1 12  2]    [CDAB  2 17  3]    [BCDA  3 22  4]

     [ABCD  4  7  5]    [DABC  5 12  6]    [CDAB  6 17  7]    [BCDA  7 22  8]

     [ABCD  8  7  9]    [DABC  9 12 10]   [CDAB 10 17 11]  [BCDA 11 22 12]

     [ABCD 12  7 13]  [DABC 13 12 14]  [CDAB 14 17 15]  [BCDA 15 22 16]

 

     // 第二轮

     //  [abcd k s i] 表示:a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s).

     // 进行下面的16步操作

     [ABCD  1  5 17]  [DABC  6  9 18]  [CDAB 11 14 19]  [BCDA  0 20 20]

     [ABCD  5  5 21]  [DABC 10  9 22]  [CDAB 15 14 23]  [BCDA  4 20 24]

     [ABCD  9  5 25]  [DABC 14  9 26]  [CDAB  3 14 27]  [BCDA  8 20 28]

     [ABCD 13  5 29]  [DABC  2  9 30]  [CDAB  7 14 31]  [BCDA 12 20 32]

 

     // 第三轮

     //  [abcd k s t]表示:a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s).

     // 进行下面的16步操作

     [ABCD  5  4 33]  [DABC  8 11 34]  [CDAB 11 16 35]  [BCDA 14 23 36]

     [ABCD  1  4 37]  [DABC  4 11 38]  [CDAB  7 16 39]  [BCDA 10 23 40]

     [ABCD 13  4 41]  [DABC  0 11 42]  [CDAB  3 16 43]  [BCDA  6 23 44]

     [ABCD  9  4 45]  [DABC 12 11 46]  [CDAB 15 16 47]  [BCDA  2 23 48]

 

     // 第四轮

     // [abcd k s t] 表示:a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s).

     // 进行下面的16步操作

     [ABCD  0  6 49]  [DABC  7 10 50]  [CDAB 14 15 51]  [BCDA  5 21 52]

     [ABCD 12  6 53]  [DABC  3 10 54]  [CDAB 10 15 55]  [BCDA  1 21 56]

     [ABCD  8  6 57]  [DABC 15 10 58]  [CDAB  6 15 59]  [BCDA 13 21 60]

     [ABCD  4  6 61]  [DABC 11 10 62]  [CDAB  2 15 63]  [BCDA  9 21 64]

 

     //累加寄存器

     A = A + AA

     B = B + BB

     C = C + CC

     D = D + DD

这样就处理完了一个16个字的数据块,按照同样的方法依次处理完所有的数据块以后,就从寄存器ABCD中得到了消息摘要。

4 参考文献

1: MD5 Homepage (unoffical): http://userpages.umbc.edu/~mabzug1/cs/md5/md5.html

2: The website of Professor Ronald L. Rivest: http://theory.lcs.mit.edu/~rivest/homepage.html

3: The MD5 Message-Digest Algorithm: http://www.faqs.org/rfcs/rfc1321.html

4: The MD4 Message-Digest Algorithm: http://www.ietf.org/rfc/rfc1320.txt

5 C#实现代码

public   class  MyMD5
    {
        
private   static  UInt32 A; 
        
private   static  UInt32 B; 
        
private   static  UInt32 C; 
        
private   static  UInt32 D; 

        
private   const   int   S11  =   7
        
private   const   int   S12  =   12
        
private   const   int   S13  =   17
        
private   const   int   S14  =   22
        
private   const   int   S21  =   5
        
private   const   int   S22  =   9
        
private   const   int   S23  =   14
        
private   const   int   S24  =   20
        
private   const   int   S31  =   4
        
private   const   int   S32  =   11
        
private   const   int   S33  =   16
        
private   const   int   S34  =   23
        
private   const   int   S41  =   6
        
private   const   int   S42  =   10
        
private   const   int   S43  =   15
        
private   const   int   S44  =   21


//          F, G, H and I are basic MD5 functions.          
//          F(X,Y,Z) =(X&Y)|((~X)&Z) 
//           G(X,Y,Z) =(X&Z)|(Y&(~Z)) 
//          H(X,Y,Z) =X^Y^Z 
//           I(X,Y,Z)=Y^(X|(~Z)) 
//          (& AND,| OR,~NOT,^XOR)
         
        
private   static  UInt32 F(UInt32 x,UInt32 y,UInt32 z)
        { 
            
return  (x & y) | (( ~ x) & z); 
        } 
        
private   static  UInt32 G(UInt32 x,UInt32 y,UInt32 z)
        { 
            
return  (x & z) | (y & ( ~ z)); 
        } 
        
private   static  UInt32 H(UInt32 x,UInt32 y,UInt32 z)
        { 
            
return  x ^ y ^ z; 
        } 
        
private   static  UInt32 I(UInt32 x,UInt32 y,UInt32 z)
        { 
            
return  y ^ (x | ( ~ z)); 
        } 

//          FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. 
//          Rotation is separate from addition to prevent recomputation.       
         private   static   void  FF( ref  UInt32 a,UInt32 b,UInt32 c,UInt32 d,UInt32 mj, int  s,UInt32 ti)
        { 
            a 
=  a  +  F(b,c,d)  +  mj  +  ti; 
            a 
=  a  <<  s  |  a  >>  ( 32 - s); 
            a 
+=  b; 
        } 
        
private   static   void  GG( ref  UInt32 a,UInt32 b,UInt32 c,UInt32 d,UInt32 mj, int  s,UInt32 ti)
        { 
            a 
=  a  +  G(b,c,d)  +  mj  +  ti; 
            a 
=  a  <<  s  |  a  >>  ( 32 - s); 
            a 
+=  b; 
        } 
        
private   static   void  HH( ref  UInt32 a,UInt32 b,UInt32 c,UInt32 d,UInt32 mj, int  s,UInt32 ti)
        { 
            a 
=  a  +  H(b,c,d)  +  mj  +  ti; 
            a 
=  a  <<  s  |  a  >>  ( 32 - s); 
            a 
+=  b; 
        } 
        
private   static   void  II( ref  UInt32 a,UInt32 b,UInt32 c,UInt32 d,UInt32 mj, int  s,UInt32 ti)
        { 
            a 
=  a  +  I(b,c,d)  +  mj  +  ti; 
            a 
=  a  <<  s  |  a  >>  ( 32 - s); 
            a 
+=  b; 
        } 

        
private   static   void  MD5_Init()
        { 
            A
= 0x67452301 ;   // in memory, this is 0x01234567 
            B = 0xefcdab89 ;    // in memory, this is 0x89abcdef 
            C = 0x98badcfe ;    // in memory, this is 0xfedcba98 
            D = 0x10325476 ;   // in memory, this is 0x76543210 
        } 

        
private   static  UInt32[] MD5_Append( byte [] input)
        { 
            
int  zeros = 0
            
int  ones  = 1
            
int  size = 0
            
int  n  =  input.Length; 
            
int  m  =  n % 64
            
if ( m  <   56  )
            { 
                zeros 
=   55 - m; 
                size
= n - m + 64
            } 
            
else   if  (m == 56 )
            { 
                zeros 
=   63
                ones 
=   1
                size
= n + 8 + 64 ;  
            } 
            
else
            { 
                zeros 
=   63 - m + 56
                size
= n + 64 - m + 64
            } 

            ArrayList bs 
=   new  ArrayList(input); 
            
if (ones == 1 )
            { 
                bs.Add( (
byte ) 0x80  );  //  0x80 = 10000000 
            } 
            
for ( int  i = 0 ;i < zeros;i ++ )
            { 
                bs.Add( (
byte ) 0  ); 
            } 

            UInt64 N 
=  (UInt64) n  *   8
            
byte  h1 = ( byte )(N & 0xFF ); 
            
byte  h2 = ( byte )((N >> 8 ) & 0xFF ); 
            
byte  h3 = ( byte )((N >> 16 ) & 0xFF ); 
            
byte  h4 = ( byte )((N >> 24 ) & 0xFF ); 
            
byte  h5 = ( byte )((N >> 32 ) & 0xFF ); 
            
byte  h6 = ( byte )((N >> 40 ) & 0xFF ); 
            
byte  h7 = ( byte )((N >> 48 ) & 0xFF ); 
            
byte  h8 = ( byte )(N >> 56 ); 
            bs.Add(h1); 
            bs.Add(h2); 
            bs.Add(h3); 
            bs.Add(h4); 
            bs.Add(h5); 
            bs.Add(h6); 
            bs.Add(h7); 
            bs.Add(h8);
            
byte [] ts = ( byte [])bs.ToArray( typeof ( byte )); 

//              Decodes input (byte[]) into output (UInt32[]). Assumes len is 
//               a multiple of 4. 
             
            UInt32[] output 
=   new  UInt32[size / 4 ]; 
            
for (Int64 i = 0 ,j = 0 ;i < size;j ++ ,i += 4 )
            { 
                output[j]
= (UInt32)(ts[i] | ts[i + 1 ] << 8 | ts[i + 2 ] << 16 | ts[i + 3 ] << 24 ); 
            } 
            
return  output; 
        } 
        
private   static  UInt32[] MD5_Trasform(UInt32[] x)
        { 

            UInt32 a,b,c,d; 

            
for ( int  k = 0 ;k < x.Length;k += 16 )
            { 
                a
= A; 
                b
= B; 
                c
= C; 
                d
= D; 
    
                
// Round 1 
                FF ( ref  a, b, c, d, x[k +   0 ], S11,  0xd76aa478 );  //  1  
                FF ( ref  d, a, b, c, x[k +   1 ], S12,  0xe8c7b756 );  //  2  
                FF ( ref  c, d, a, b, x[k +   2 ], S13,  0x242070db );  //  3  
                FF ( ref  b, c, d, a, x[k +   3 ], S14,  0xc1bdceee );  //  4  
                FF ( ref  a, b, c, d, x[k +   4 ], S11,  0xf57c0faf );  //  5  
                FF ( ref  d, a, b, c, x[k +   5 ], S12,  0x4787c62a );  //  6  
                FF ( ref  c, d, a, b, x[k +   6 ], S13,  0xa8304613 );  //  7  
                FF ( ref  b, c, d, a, x[k +   7 ], S14,  0xfd469501 );  //  8  
                FF ( ref  a, b, c, d, x[k +   8 ], S11,  0x698098d8 );  //  9  
                FF ( ref  d, a, b, c, x[k +   9 ], S12,  0x8b44f7af );  //  10  
                FF ( ref  c, d, a, b, x[k + 10 ], S13,  0xffff5bb1 );  //  11  
                FF ( ref  b, c, d, a, x[k + 11 ], S14,  0x895cd7be );  //  12  
                FF ( ref  a, b, c, d, x[k + 12 ], S11,  0x6b901122 );  //  13  
                FF ( ref  d, a, b, c, x[k + 13 ], S12,  0xfd987193 );  //  14  
                FF ( ref  c, d, a, b, x[k + 14 ], S13,  0xa679438e );  //  15  
                FF ( ref  b, c, d, a, x[k + 15 ], S14,  0x49b40821 );  //  16  

                
//  Round 2  
                GG ( ref  a, b, c, d, x[k +   1 ], S21,  0xf61e2562 );  //  17  
                GG ( ref  d, a, b, c, x[k +   6 ], S22,  0xc040b340 );  //  18  
                GG ( ref  c, d, a, b, x[k + 11 ], S23,  0x265e5a51 );  //  19  
                GG ( ref  b, c, d, a, x[k +   0 ], S24,  0xe9b6c7aa );  //  20  
                GG ( ref  a, b, c, d, x[k +   5 ], S21,  0xd62f105d );  //  21  
                GG ( ref  d, a, b, c, x[k + 10 ], S22,   0x2441453 );  //  22  
                GG ( ref  c, d, a, b, x[k + 15 ], S23,  0xd8a1e681 );  //  23  
                GG ( ref  b, c, d, a, x[k +   4 ], S24,  0xe7d3fbc8 );  //  24  
                GG ( ref  a, b, c, d, x[k +   9 ], S21,  0x21e1cde6 );  //  25  
                GG ( ref  d, a, b, c, x[k + 14 ], S22,  0xc33707d6 );  //  26  
                GG ( ref  c, d, a, b, x[k +   3 ], S23,  0xf4d50d87 );  //  27  
                GG ( ref  b, c, d, a, x[k +   8 ], S24,  0x455a14ed );  //  28  
                GG ( ref  a, b, c, d, x[k + 13 ], S21,  0xa9e3e905 );  //  29  
                GG ( ref  d, a, b, c, x[k +   2 ], S22,  0xfcefa3f8 );  //  30  
                GG ( ref  c, d, a, b, x[k +   7 ], S23,  0x676f02d9 );  //  31  
                GG ( ref  b, c, d, a, x[k + 12 ], S24,  0x8d2a4c8a );  //  32  

                
//  Round 3  
                HH ( ref  a, b, c, d, x[k +   5 ], S31,  0xfffa3942 );  //  33  
                HH ( ref  d, a, b, c, x[k +   8 ], S32,  0x8771f681 );  //  34  
                HH ( ref  c, d, a, b, x[k + 11 ], S33,  0x6d9d6122 );  //  35  
                HH ( ref  b, c, d, a, x[k + 14 ], S34,  0xfde5380c );  //  36  
                HH ( ref  a, b, c, d, x[k +   1 ], S31,  0xa4beea44 );  //  37  
                HH ( ref  d, a, b, c, x[k +   4 ], S32,  0x4bdecfa9 );  //  38  
                HH ( ref  c, d, a, b, x[k +   7 ], S33,  0xf6bb4b60 );  //  39  
                HH ( ref  b, c, d, a, x[k + 10 ], S34,  0xbebfbc70 );  //  40  
                HH ( ref  a, b, c, d, x[k + 13 ], S31,  0x289b7ec6 );  //  41  
                HH ( ref  d, a, b, c, x[k +   0 ], S32,  0xeaa127fa );  //  42  
                HH ( ref  c, d, a, b, x[k +   3 ], S33,  0xd4ef3085 );  //  43  
                HH ( ref  b, c, d, a, x[k +   6 ], S34,   0x4881d05 );  //  44  
                HH ( ref  a, b, c, d, x[k +   9 ], S31,  0xd9d4d039 );  //  45  
                HH ( ref  d, a, b, c, x[k + 12 ], S32,  0xe6db99e5 );  //  46  
                HH ( ref  c, d, a, b, x[k + 15 ], S33,  0x1fa27cf8 );  //  47  
                HH ( ref  b, c, d, a, x[k +   2 ], S34,  0xc4ac5665 );  //  48  

                
//  Round 4  
                II ( ref  a, b, c, d, x[k +   0 ], S41,  0xf4292244 );  //  49  
                II ( ref  d, a, b, c, x[k +   7 ], S42,  0x432aff97 );  //  50  
                II ( ref  c, d, a, b, x[k + 14 ], S43,  0xab9423a7 );  //  51  
                II ( ref  b, c, d, a, x[k +   5 ], S44,  0xfc93a039 );  //  52  
                II ( ref  a, b, c, d, x[k + 12 ], S41,  0x655b59c3 );  //  53  
                II ( ref  d, a, b, c, x[k +   3 ], S42,  0x8f0ccc92 );  //  54  
                II ( ref  c, d, a, b, x[k + 10 ], S43,  0xffeff47d );  //  55  
                II ( ref  b, c, d, a, x[k +   1 ], S44,  0x85845dd1 );  //  56  
                II ( ref  a, b, c, d, x[k +   8 ], S41,  0x6fa87e4f );  //  57  
                II ( ref  d, a, b, c, x[k + 15 ], S42,  0xfe2ce6e0 );  //  58  
                II ( ref  c, d, a, b, x[k +   6 ], S43,  0xa3014314 );  //  59  
                II ( ref  b, c, d, a, x[k + 13 ], S44,  0x4e0811a1 );  //  60  
                II ( ref  a, b, c, d, x[k +   4 ], S41,  0xf7537e82 );  //  61  
                II ( ref  d, a, b, c, x[k + 11 ], S42,  0xbd3af235 );  //  62  
                II ( ref  c, d, a, b, x[k +   2 ], S43,  0x2ad7d2bb );  //  63  
                II ( ref  b, c, d, a, x[k +   9 ], S44,  0xeb86d391 );  //  64  

                A
+= a; 
                B
+= b; 
                C
+= c; 
                D
+= d; 
            } 
            
return   new  UInt32[]{A,B,C,D}; 
        } 
        
public   static   byte [] MyMD5Array( byte [] input)
        { 
            MD5_Init(); 
            UInt32[] block 
=  MD5_Append(input); 
            UInt32[] bits 
=  MD5_Trasform(block); 

//              Encodes bits (UInt32[]) into output (byte[]). Assumes len is 
//              a multiple of 4. 
                 
            
byte [] output = new   byte [bits.Length * 4 ]; 
            
for ( int  i = 0 ,j = 0 ;i < bits.Length;i ++ ,j += 4 )
            { 
                output[j] 
=  ( byte )(bits[i] & 0xff ); 
                output[j
+ 1 =  ( byte )((bits[i] >> 8 &   0xff ); 
                output[j
+ 2 =  ( byte )((bits[i] >> 16 &   0xff ); 
                output[j
+ 3 =  ( byte )((bits[i] >> 24 &   0xff ); 
            } 
            
return  output; 
        } 

        
public   static   string  ArrayToHexString( byte [] array, bool  uppercase)
        { 
            
string  hexString = ""
            
string  format = " x2 "
            
if (uppercase)
            { 
                format
= " X2 "
            } 
            
foreach ( byte  b  in  array)
            { 
                hexString 
+=  b.ToString(format); 
            } 
            
return  hexString; 
        } 

        
public   static   string  MyMD5String( string  message)
        { 
            
char [] c  =  message.ToCharArray(); 
            
byte [] b  =   new   byte [c.Length]; 
            
for ( int  i = 0 ;i < c.Length;i ++ )
            { 
                b[i]
= ( byte )c[i]; 
            } 
            
byte [] digest  =  MyMD5Array(b); 
            
return  ArrayToHexString(digest, true ); 
        } 

        
public   static   string  MyMD5File( string  fileName)
        { 
            
byte [] array  =   null ;
            FileStream fs 
=   null ;
            
try
            {
                fs 
=  File.Open(fileName,FileMode.Open,FileAccess.Read); 
                array
= new   byte [fs.Length]; 
                fs.Read(array,
0 ,( int )fs.Length); 
            }
            
catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            
byte [] digest  =  MyMD5Array(array); 
            fs.Close(); 
            
return  ArrayToHexString(digest, true ); 
        } 
        
#region  UnitTest
        
public   static   string  Test( string  message)
        { 
            
return   " \r\nMD5 (\ "" +message+ " \ " ) =  "   +  MyMD5.MyMD5String(message); 
        } 

        
public   static   string  TestSuite()
        {     
            
string  s  =   ""
            s
+= Test( "" ); 
            s
+= Test( " a " ); 
            s
+= Test( " abc " ); 
            s
+= Test( " abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq " );
            s
+= Test( " message digest " ); 
            s
+= Test( " abcdefghijklmnopqrstuvwxyz " ); 
            s
+= Test( " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 " ); 
            s
+= Test( " 12345678901234567890123456789012345678901234567890123456789012345678901234567890 " ); 
            
return  s;     
        } 

        
public   static   void  Main()
        {
            Console.WriteLine(TestSuite());
            Console.ReadLine();
        }
        
#endregion
    }

/******************************************************************************************
 *【Author】:flyingbread
 *【Date】:2006年12月23日
 *【Notice】:
 *1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
 *2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
 *3、本声明为文章一部分,转载和引用必须包括在原文中。
 ******************************************************************************************/

转载于:https://www.cnblogs.com/FlyingBread/archive/2006/12/24/602511.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值