MD5的介绍,算法以及实现
MD5的介绍,算法以及实现(第三次修正)Wrote By 娃娃/
前言:
一次偶然的机会和破解界的朋友 -- Stkman大哥谈起了MD5算法,正好在这期间正在研究密码学,顺便问了几个问题,都得到了解答,使得我可以写出这篇文章。
MD5简介:
MD5是英文“Message-Digest Algorithm Five”的简写,在90年代初由MIT的计算机科学实验室和RSA Data Security Inc发明,经历了最初的MD2、MD3和MD4的发展演化而来。“Message-Digest”泛指字节串(Message)的Hash变换,就是把一个任意长度的字节串变换成一定长的大整数。请注意我使用了“字节串”而不是“字符串”这个词,是因为这种变换只与字节的值有关,与字符集或编码方式无关,也就是很多朋友问MD5可以机密中文字符的原理。
MD5将任意长度的“字节串”变换成一个128bit的大整数,并且它是一个不可逆的字符串变换算法,换句话说就是:既便是MD5算法的源代码满天飞,使得任何人都可以了解MD5的详尽算法描述,但是却也绝对没有任何人可以将一个经由MD5算法加密过的字符串变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。
MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现两者的不同,以便提早发现文件已经被改动过了。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
MD5还可以被广泛用于加密和解密技术上,在很多操作系统中,用户的密码是以MD5值(或类似的其它算法)的方式保存的, 用户在进行登陆验证的时候,系统是把用户输入的密码计算成MD5值,然后再去和系统中保存的初始密码的MD5值进行比较,而系统实际上是并不“知道”用户的密码是什么的。一些黑客破获这种密码的方法是一种被称为“跑字典”的方法,也就是所谓的暴力破解法或穷举法。得到字典的方法基本有两种,一种是日常搜集到的“弱智密码”用做密码的字符串列表,另一种是用排列组合方法生成的,先用MD5程序计算出这些字典项的MD5值,然后再用目标的MD5值在这个字典中检索。
即使假设密码的最大长度为8,同时密码只能是大写或小写字母和数字,那么一共可以有“26+26+10”也就是62个有效的密码字符,那么排列组合出的字典的项数则是P(62,1)+P(62,2)….+P(62,8),既便如此那也已经是一个天文数字了,存储这个字典就需要TB级的磁盘组,而且这种方法还有一个前提,就是能获得目标账户的密码MD5值的情况下才可以。
MD5算法在破解中的应用:
在软件的加密保护中,MD5是一个经常会被用到的加密算法,但是由于MD5算法为不可逆算法,所以所有的软件都只能使用MD5算法作为一个加密的中间步骤,比如对用户名做一个MD5变换,结果再进行一个可逆的加密变换,变换结果为注册码。而做注册机时也必须先用MD5进行加密转换,然后再用第二个算法的逆算法进行演变,得出原始的用户名。具体来说:我们设用户名为StrName,注册码为StrRegNO,那么如果哪一位共享软件的作者采用如下算法作为他自己软件的注册算法:‘If MD5(StrName)=MD5(strRegNO) then "register successful"Else "register unsuccessful " ’那么对于Cracker来说只要可以看出软件采用了MD5算法就可以写出注册机了,但是如果作者采用如下算法:‘If MD5(StrRegNO)=MD5(StrName) Then "register successful"Else "register unsuccessful " ’的话,那么我想连共享软件作者自己也必须通过穷举法才可以求出注册用户的序列号了。所以对于破解者来说只要可以看出软件的注册算法采用的是MD5就足够了。
MD5代码的特点也非常的明显,跟踪时很容易发现。如果软件采用MD5算法,在数据初始化的时候必然会用到以下四个常数:
A = 0x01234567
B = 0x89abcdef
C = 0xfedcba98
D = 0x76543210
若常数不等,则可能是变形的MD5算法或者根本就不是MD5算法。在内存中显示为:
01 23 45 67 89 ab cd ef fe dc ......32 10 一共16个字节
--------------------------------------------
MD5的算法描述:
第一步:增加填充
增加padding使得数据长度(bit为单位)模512为448。如果数据长度正好是模512为448,增加512个填充bit,也就是说填充的个数为1-512。第一个bit为1,其余全部为0。
第二步:补足长度
将数据长度转换为64bit的数值,如果长度超过64bit所能表示的数据长度的范围,值保留最后64bit,增加到前面填充的数据后面,使得最后的数据为512bit的整数倍。也就是32bit的16倍的整数倍。在RFC1321中,32bit称为一个word。
第三步:初始化变量:
用到4个变量,分别为A、B、C、D,均为32bit长。初始化为:
A: 01 23 45 67
B: 89 ab cd ef
C: fe dc ba 98
D: 76 54 32 10
第四步:数据处理:
首先定义4个辅助函数:
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))
其中:XY表示按位与,X v Y表示按位或,not(X)表示按位取反。xor表示按位异或。
函数中的X、Y、Z均为32bit。
定义一个需要用到的数组:T(i),i取值1-64,T(i)等于abs(sin(i))的4294967296倍的整数部分,i为弧度。
假设前三步处理后的数据长度为32*16*Nbit
第五步:输出:
最后得到的ABCD为输出结果,共128bit。A为低位,D为高位。
MD5算法在编程中的实现:
下面来看看如何在C,Delphi和VB中实现MD5算法
C语言举例:
--------------------------------------------
*/
#ifndef PROTOTYPES
#define PROTOTYPES 0
#endif
typedef unsigned char *POINTER;
typedef unsigned short int UINT2;
typedef unsigned long int UINT4;
#if PROTOTYPES
#define PROTO_LIST(list) list
#else
#define PROTO_LIST(list) ()
#endif
---------- MD5.h----------------------------
typedef struct {
UINT4 state;
UINT4 count;
unsigned char buffer;
} MD5_CTX;
void MD5Init PROTO_LIST ((MD5_CTX *));
void MD5Update PROTO_LIST
((MD5_CTX *, unsigned char *, unsigned int));
void MD5Final PROTO_LIST ((unsigned char , MD5_CTX *));
※※※※※※※※※MD5C.C※※※※※※※※※※※※※※※※※※※※※※※※
#include "global.h"
#include "md5.h"
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21
static void MD5Transform PROTO_LIST ((UINT4 , unsigned char ));
static void Encode PROTO_LIST
((unsigned char *, UINT4 *, unsigned int));
static void Decode PROTO_LIST
((UINT4 *, unsigned char *, unsigned int));
static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int));
static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int));
static unsigned char PADDING = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/* 定义F G H I 为四个基数
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
#define FF(a, b, c, d, x, s, ac) { \
(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define GG(a, b, c, d, x, s, ac) { \
(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define HH(a, b, c, d, x, s, ac) { \
(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define II(a, b, c, d, x, s, ac) { \
(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
/*开始进行MD5计算
void MD5Init (context)
MD5_CTX *context;
{
context->count = context->count = 0;
/* 在这里定义四个常数,也就是我们刚才讲到的四个特征数.
context->state = 0x67452301;
context->state = 0xefcdab89;
context->state = 0x98badcfe;
context->state = 0x10325476;
}
void MD5Update (context, input, inputLen)
MD5_CTX *context;
unsigned char *input;
unsigned int inputLen;
{
unsigned int i, index, partLen;
index = (unsigned int)((context->count >> 3) & 0x3F);
if ((context->count += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3))
context->count++;
context->count += ((UINT4)inputLen >> 29);
partLen = 64 - index;
if (inputLen >= partLen) {
MD5_memcpy
((POINTER)&context->buffer, (POINTER)input, partLen);
MD5Transform (context->state, context->buffer);
for (i = partLen; i + 63 < inputLen; i += 64)
MD5Transform (context->state, &input);
index = 0;
}
else
i = 0;
MD5_memcpy
((POINTER)&context->buffer, (POINTER)&input,
inputLen-i);
}
void MD5Final (digest, context)
unsigned char digest;
MD5_CTX *context;
{
unsigned char bits;
unsigned int index, padLen;
Encode (bits, context->count, 8);
index = (unsigned int)((context->count >> 3) & 0x3f);
padLen = (index < 56) ? (56 - index) : (120 - index);
MD5Update (context, PADDING, padLen);
MD5Update (context, bits, 8);
Encode (digest, context->state, 16);
MD5_memset ((POINTER)context, 0, sizeof (*context));
}
static void MD5Transform (state, block)
UINT4 state;
unsigned char block;
{
UINT4 a = state, b = state, c = state, d = state, x;
Decode (x, block, 64);
/* 第一轮循环 */
FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
FF (c, d, a, b, x, S13, 0xffff5bb1); /* 11 */
FF (b, c, d, a, x, S14, 0x895cd7be); /* 12 */
FF (a, b, c, d, x, S11, 0x6b901122); /* 13 */
FF (d, a, b, c, x, S12, 0xfd987193); /* 14 */
FF (c, d, a, b, x, S13, 0xa679438e); /* 15 */
FF (b, c, d, a, x, S14, 0x49b40821); /* 16 */
/* 第二轮循环 */
GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
GG (c, d, a, b, x, S23, 0x265e5a51); /* 19 */
GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
GG (d, a, b, c, x, S22,0x2441453); /* 22 */
GG (c, d, a, b, x, S23, 0xd8a1e681); /* 23 */
GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
GG (d, a, b, c, x, S22, 0xc33707d6); /* 26 */
GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
GG (a, b, c, d, x, S21, 0xa9e3e905); /* 29 */
GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
GG (b, c, d, a, x, S24, 0x8d2a4c8a); /* 32 */
/* 第三轮循环 */
HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
HH (c, d, a, b, x, S33, 0x6d9d6122); /* 35 */
HH (b, c, d, a, x, S34, 0xfde5380c); /* 36 */
HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
HH (b, c, d, a, x, S34, 0xbebfbc70); /* 40 */
HH (a, b, c, d, x, S31, 0x289b7ec6); /* 41 */
HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
HH (b, c, d, a, x[ 6], S34,0x4881d05); /* 44 */
HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
HH (d, a, b, c, x, S32, 0xe6db99e5); /* 46 */
HH (c, d, a, b, x, S33, 0x1fa27cf8); /* 47 */
HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
/* 第四轮循环 */
II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
II (c, d, a, b, x, S43, 0xab9423a7); /* 51 */
II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
II (a, b, c, d, x, S41, 0x655b59c3); /* 53 */
II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
II (c, d, a, b, x, S43, 0xffeff47d); /* 55 */
II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
II (d, a, b, c, x, S42, 0xfe2ce6e0); /* 58 */
II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
II (b, c, d, a, x, S44, 0x4e0811a1); /* 60 */
II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
II (d, a, b, c, x, S42, 0xbd3af235); /* 62 */
II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
state += a;
state += b;
state += c;
state += d;
MD5_memset ((POINTER)x, 0, sizeof (x));
}
static void Encode (output, input, len)
unsigned char *output;
UINT4 *input;
unsigned int len;
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4) {
output = (unsigned char)(input & 0xff);
output = (unsigned char)((input >> 8) & 0xff);
output = (unsigned char)((input >> 16) & 0xff);
output = (unsigned char)((input >> 24) & 0xff);
}
}
static void Decode (output, input, len)
UINT4 *output;
unsigned char *input;
unsigned int len;
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4)
output = ((UINT4)input) | (((UINT4)input) << 8) |
(((UINT4)input) << 16) | (((UINT4)input) << 24);
}
static void MD5_memcpy (output, input, len)
POINTER output;
POINTER input;
unsigned int len;
{
unsigned int i;
for (i = 0; i < len; i++) output = input;
}
static void MD5_memset (output, value, len)
POINTER output;
int value;
unsigned int len;
{
unsigned int i;
for (i = 0; i < len; i++)
((char *)output) = (char)value;
}
----------------C代码结束----------
在VB中实现MD5算法
------------------------------------------
模块源码如下:
Private Const OFFSET_4 = 4294967296#
Private Const MAXINT_4 = 2147483647
Private State(4) As Long
Private ByteCounter As Long
Private ByteBuffer(63) As Byte
Private Const S11 = 7
Private Const S12 = 12
Private Const S13 = 17
Private Const S14 = 22
Private Const S21 = 5
Private Const S22 = 9
Private Const S23 = 14
Private Const S24 = 20
Private Const S31 = 4
Private Const S32 = 11
Private Const S33 = 16
Private Const S34 = 23
Private Const S41 = 6
Private Const S42 = 10
Private Const S43 = 15
Private Const S44 = 21
Property Get RegisterA() As String
RegisterA = State(1)
End Property
Property Get RegisterB() As String
RegisterB = State(2)
End Property
Property Get RegisterC() As String
RegisterC = State(3)
End Property
Property Get RegisterD() As String
RegisterD = State(4)
End Property
Public Function Md5_String_Calc(SourceString As String) As String
MD5Init
MD5Update LenB(StrConv(SourceString, vbFromUnicode)), StringToArray(SourceString)
MD5Final
Md5_String_Calc = GetValues
End Function
Public Function Md5_File_Calc(InFile As String) As String
On Error GoTo errorhandler
GoSub begin
errorhandler
DigestFileToHexStr = ""
Exit Function
begin:
Dim FileO As Integer
FileO = FreeFile
Call FileLen(InFile)
Open InFile For Binary Access Read As #FileO
MD5Init
Do While Not EOF(FileO)
Get #FileO, , ByteBuffer
If Loc(FileO) < LOF(FileO) Then
ByteCounter = ByteCounter + 64
MD5Transform ByteBuffer
End If
Loop
ByteCounter = ByteCounter + (LOF(FileO) Mod 64)
Close #FileO
MD5Final
Md5_File_Calc = GetValues
End Function
Private Function StringToArray(InString As String) As Byte()
Dim I As Integer, bytBuffer() As Byte
ReDim bytBuffer(LenB(StrConv(InString, vbFromUnicode)))
bytBuffer = StrConv(InString, vbFromUnicode)
StringToArray = bytBuffer
End Function
Public Function GetValues() As String
GetValues = LongToString(State(1)) & LongToString(State(2)) & LongToString(State(3)) & LongToString(State(4))
End Function
Private Function LongToString(Num As Long) As String
Dim A As Byte, B As Byte, C As Byte, D As Byte
A = Num And &HFF&
If A < 16 Then LongToString = "0" & Hex(A) Else LongToString = Hex(A)
B = (Num And &HFF00&) \ 256
If B < 16 Then LongToString = LongToString & "0" & Hex(B) Else LongToString = LongToString & Hex(B)
C = (Num And &HFF0000) \ 65536
If C < 16 Then LongToString = LongToString & "0" & Hex(C) Else LongToString = LongToString & Hex(C)
If Num < 0 Then D = ((Num And &H7F000000) \ 16777216) Or &H80& Else D = (Num And &HFF000000) \ 16777216
If D < 16 Then LongToString = LongToString & "0" & Hex(D) Else LongToString = LongToString & Hex(D)
End Function
Public Sub MD5Init()
ByteCounter = 0
State(1) = UnsignedToLong(1732584193#)
State(2) = UnsignedToLong(4023233417#)
State(3) = UnsignedToLong(2562383102#)
State(4) = UnsignedToLong(271733878#)
End Sub
Public Sub MD5Final()
Dim dblBits As Double, padding(72) As Byte, lngBytesBuffered As Long
padding(0) = &H80
dblBits = ByteCounter * 8
lngBytesBuffered = ByteCounter Mod 64
If lngBytesBuffered <= 56 Then MD5Update 56 - lngBytesBuffered, padding Else MD5Update 120 - ByteCounter, padding
padding(0) = UnsignedToLong(dblBits) And &HFF&
padding(1) = UnsignedToLong(dblBits) \ 256 And &HFF&
padding(2) = UnsignedToLong(dblBits) \ 65536 And &HFF&
padding(3) = UnsignedToLong(dblBits) \ 16777216 And &HFF&
padding(4) = 0
padding(5) = 0
padding(6) = 0
padding(7) = 0
MD5Update 8, padding
End Sub
Public Sub MD5Update(InputLen As Long, InputBuffer() As Byte)
Dim II As Integer, I As Integer, J As Integer, K As Integer, lngBufferedBytes As Long, lngBufferRemaining As Long, lngRem As Long
lngBufferedBytes = ByteCounter Mod 64
lngBufferRemaining = 64 - lngBufferedBytes
ByteCounter = ByteCounter + InputLen
If InputLen >= lngBufferRemaining Then
For II = 0 To lngBufferRemaining - 1
ByteBuffer(lngBufferedBytes + II) = InputBuffer(II)
Next II
MD5Transform ByteBuffer
lngRem = (InputLen) Mod 64
For I = lngBufferRemaining To InputLen - II - lngRem Step 64
For J = 0 To 63
ByteBuffer(J) = InputBuffer(I + J)
Next J
MD5Transform ByteBuffer
Next I
lngBufferedBytes = 0
Else
I = 0
End If
For K = 0 To InputLen - I - 1
ByteBuffer(lngBufferedBytes + K) = InputBuffer(I + K)
Next K
End Sub
Private Sub MD5Transform(Buffer() As Byte)
Dim X(16) As Long, A As Long, B As Long, C As Long, D As Long
A = State(1)
B = State(2)
C = State(3)
D = State(4)
Decode 64, X, Buffer
FF A, B, C, D, X(0), S11, -680876936
FF D, A, B, C, X(1), S12, -389564586
FF C, D, A, B, X(2), S13, 606105819
FF B, C, D, A, X(3), S14, -1044525330
FF A, B, C, D, X(4), S11, -176418897
FF D, A, B, C, X(5), S12, 1200080426
FF C, D, A, B, X(6), S13, -1473231341
FF B, C, D, A, X(7), S14, -45705983
FF A, B, C, D, X(8), S11, 1770035416
FF D, A, B, C, X(9), S12, -1958414417
FF C, D, A, B, X(10), S13, -42063
FF B, C, D, A, X(11), S14, -1990404162
FF A, B, C, D, X(12), S11, 1804603682
FF D, A, B, C, X(13), S12, -40341101
FF C, D, A, B, X(14), S13, -1502002290
FF B, C, D, A, X(15), S14, 1236535329
GG A, B, C, D, X(1), S21, -165796510
GG D, A, B, C, X(6), S22, -1069501632
GG C, D, A, B, X(11), S23, 643717713
GG B, C, D, A, X(0), S24, -373897302
GG A, B, C, D, X(5), S21, -701558691
GG D, A, B, C, X(10), S22, 38016083
GG C, D, A, B, X(15), S23, -660478335
GG B, C, D, A, X(4), S24, -405537848
GG A, B, C, D, X(9), S21, 568446438
GG D, A, B, C, X(14), S22, -1019803690
GG C, D, A, B, X(3), S23, -187363961
GG B, C, D, A, X(8), S24, 1163531501
GG A, B, C, D, X(13), S21, -1444681467
GG D, A, B, C, X(2), S22, -51403784
GG C, D, A, B, X(7), S23, 1735328473
GG B, C, D, A, X(12), S24, -1926607734
HH A, B, C, D, X(5), S31, -378558
HH D, A, B, C, X(8), S32, -2022574463
HH C, D, A, B, X(11), S33, 1839030562
HH B, C, D, A, X(14), S34, -35309556
HH A, B, C, D, X(1), S31, -1530992060
HH D, A, B, C, X(4), S32, 1272893353
HH C, D, A, B, X(7), S33, -155497632
HH B, C, D, A, X(10), S34, -1094730640
HH A, B, C, D, X(13), S31, 681279174
HH D, A, B, C, X(0), S32, -358537222
HH C, D, A, B, X(3), S33, -722521979
HH B, C, D, A, X(6), S34, 76029189
HH A, B, C, D, X(9), S31, -640364487
HH D, A, B, C, X(12), S32, -421815835
HH C, D, A, B, X(15), S33, 530742520
HH B, C, D, A, X(2), S34, -995338651
II A, B, C, D, X(0), S41, -198630844
II D, A, B, C, X(7), S42, 1126891415
II C, D, A, B, X(14), S43, -1416354905
II B, C, D, A, X(5), S44, -57434055
II A, B, C, D, X(12), S41, 1700485571
II D, A, B, C, X(3), S42, -1894986606
II C, D, A, B, X(10), S43, -1051523
II B, C, D, A, X(1), S44, -2054922799
II A, B, C, D, X(8), S41, 1873313359
II D, A, B, C, X(15), S42, -30611744
II C, D, A, B, X(6), S43, -1560198380
II B, C, D, A, X(13), S44, 1309151649
II A, B, C, D, X(4), S41, -145523070
II D, A, B, C, X(11), S42, -1120210379
II C, D, A, B, X(2), S43, 718787259
II B, C, D, A, X(9), S44, -343485551
State(1) = LongOverflowAdd(State(1), A)
State(2) = LongOverflowAdd(State(2), B)
State(3) = LongOverflowAdd(State(3), C)
State(4) = LongOverflowAdd(State(4), D)
End Sub
Private Sub Decode(Length As Integer, OutputBuffer() As Long, InputBuffer() As Byte)
Dim intDblIndex As Integer, intByteIndex As Integer, dblSum As Double
For intByteIndex = 0 To Length - 1 Step 4
dblSum = InputBuffer(intByteIndex) + InputBuffer(intByteIndex + 1) * 256# + InputBuffer(intByteIndex + 2) * 65536# + InputBuffer(intByteIndex + 3) * 16777216#
OutputBuffer(intDblIndex) = UnsignedToLong(dblSum)
intDblIndex = intDblIndex + 1
Next intByteIndex
End Sub
Private Function FF(A As Long, B As Long, C As Long, D As Long, X As Long, S As Long, ac As Long) As Long
A = LongOverflowAdd4(A, (B And C) Or (Not (B) And D), X, ac)
A = LongLeftRotate(A, S)
A = LongOverflowAdd(A, B)
End Function
Private Function GG(A As Long, B As Long, C As Long, D As Long, X As Long, S As Long, ac As Long) As Long
A = LongOverflowAdd4(A, (B And D) Or (C And Not (D)), X, ac)
A = LongLeftRotate(A, S)
A = LongOverflowAdd(A, B)
End Function
Private Function HH(A As Long, B As Long, C As Long, D As Long, X As Long, S As Long, ac As Long) As Long
A = LongOverflowAdd4(A, B Xor C Xor D, X, ac)
A = LongLeftRotate(A, S)
A = LongOverflowAdd(A, B)
End Function
Private Function II(A As Long, B As Long, C As Long, D As Long, X As Long, S As Long, ac As Long) As Long
A = LongOverflowAdd4(A, C Xor (B Or Not (D)), X, ac)
A = LongLeftRotate(A, S)
A = LongOverflowAdd(A, B)
End Function
Function LongLeftRotate(value As Long, Bits As Long) As Long
Dim lngSign As Long, lngI As Long
Bits = Bits Mod 32
If Bits = 0 Then LongLeftRotate = value: Exit Function
For lngI = 1 To Bits
lngSign = value And &HC0000000
value = (value And &H3FFFFFFF) * 2
value = value Or ((lngSign < 0) And 1) Or (CBool(lngSign And &H40000000) And &H80000000)
Next
LongLeftRotate = value
End Function
Private Function LongOverflowAdd(Val1 As Long, Val2 As Long) As Long
Dim lngHighWord As Long, lngLowWord As Long, lngOverflow As Long
lngLowWord = (Val1 And &HFFFF&) + (Val2 And &HFFFF&)
lngOverflow = lngLowWord \ 65536
lngHighWord = (((Val1 And &HFFFF0000) \ 65536) + ((Val2 And &HFFFF0000) \ 65536) + lngOverflow) And &HFFFF&
LongOverflowAdd = UnsignedToLong((lngHighWord * 65536#) + (lngLowWord And &HFFFF&))
End Function
Private Function LongOverflowAdd4(Val1 As Long, Val2 As Long, val3 As Long, val4 As Long) As Long
Dim lngHighWord As Long, lngLowWord As Long, lngOverflow As Long
lngLowWord = (Val1 And &HFFFF&) + (Val2 And &HFFFF&) + (val3 And &HFFFF&) + (val4 And &HFFFF&)
lngOverflow = lngLowWord \ 65536
lngHighWord = (((Val1 And &HFFFF0000) \ 65536) + ((Val2 And &HFFFF0000) \ 65536) + ((val3 And &HFFFF0000) \ 65536) + ((val4 And &HFFFF0000) \ 65536) + lngOverflow) And &HFFFF&
LongOverflowAdd4 = UnsignedToLong((lngHighWord * 65536#) + (lngLowWord And &HFFFF&))
End Function
Private Function UnsignedToLong(value As Double) As Long
If value < 0 Or value >= OFFSET_4 Then Error 6
If value <= MAXINT_4 Then UnsignedToLong = value Else UnsignedToLong = value - OFFSET_4
End Function
Private Function LongToUnsigned(value As Long) As Double
If value < 0 Then LongToUnsigned = value + OFFSET_4 Else LongToUnsigned = value
End Function
模块代码结束
在窗体中调用md5_string_calc() 实现计算
------------------VB代码结束--------------------------
Delphi代码举例:
-----------------------MD5.PAS--------------------------------
unit MD5unit;
interface
uses Cryptcon, SysUtils, Classes, Controls;
Type
ULONG32 = record
LoWord16: WORD;
HiWord16: WORD;
end;
PULONG32 = ^ULONG32;
PLong = ^LongInt;
hashDigest = record
A: Longint;
B: Longint;
C: Longint;
D: Longint;
end;{hashArray}
PTR_Hash = ^hashDigest;
TMD5 = class(TComponent)
Private
{ Private declarations }
FType : TSourceType; {Source type, whether its a file or ByteArray, or
a Pascal String}
FInputFilePath: String; {Full Path to Input File}
FInputArray: PByte; {Point to input array}
FInputString: String; {Input String}
FOutputDigest: PTR_Hash; {output MD5 Digest}
FSourceLength: LongInt; {input length in BYTES}
FActiveBlock: Array of LongInt; {the 64Byte block being transformed}
FA, FB, FC, FD, FAA, FBB, FCC, FDD: LongInt;
{FA..FDD are used during Step 4, the transform.I made them part of the
Object to cut down on time used to pass variables.}
FpA, FpB, FpC, FpD: PLong;
{FIXME! do we need these, or just use the '@' operator?}
{Put in for readability}
{FF, GG, HH, II are used in Step 4, the transform}
Procedure FF(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
Procedure GG(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
Procedure HH(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
Procedure II(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
protected
{ Protected declarations }
public
{ Public declarations }
{Initialize is used in Step 3, this fills FA..FD with init. values
and points FpA..FpD to FA..FD}
Procedure MD5_Initialize;
{this is where all the magic happens}
Procedure MD5_Transform;
Procedure MD5_Finish;
Procedure MD5_Hash_Bytes;
{Procedure MD5_Hash_String;(Pascal Style strings???)}
Procedure MD5_Hash_File;
{This procedure sends the data 64Bytes at a time to MD5_Transform}
Procedure MD5_Hash;
Property pInputArray: PByte read FInputArray write FInputArray;
Property pOutputArray: PTR_Hash read FOutputDigest write FOutputDigest;{!!See FOutputArray}
Published
Property InputType: TSourceType read FType write FType;
Property InputFilePath: String read FInputFilePath write FInputFilePath;
Property InputString: String read FInputString write FInputString;
Property InputLength: LongInt read FSourceLength write FSourceLength;
end;{TMD5}
procedure Register;{register the component to the Delphi toolbar}
Const
{Constants for MD5Transform routine.}
S11 = 7;
S12 = 12;
S13 = 17;
S14 = 22;
S21 = 5;
S22 = 9;
S23 = 14;
S24 = 20;
S31 = 4;
S32 = 11;
S33 = 16;
S34 = 23;
S41 = 6;
S42 = 10;
S43 = 15;
S44 = 21;
implementation
Function ROL(A: Longint; Amount: BYTE): Longint; Assembler;
asm
mov cl, Amount
rol eax, cl
end;
procedure Register;
{Registers the Component to the toobar, on the tab named 'Crypto'}
{Now all a Delphi programmer needs to do is drag n drop to have
Blowfish encryption}
begin
RegisterComponents('Crypto', );
end;
Procedure TMD5.MD5_Initialize;
var
a, b, c, d: LongInt;
begin
a := $67452301; b:=$efcdab89; c:=$98badcfe; d:=$10325476;
Move(a, FA, 4); FpA := @FA;
Move(b, FB, 4); FpB := @FB;
Move(c, FC, 4); FpC := @FC;
Move(d, FD, 4); FpD := @FD;
end;{MD5_Initialize}
Procedure TMD5.FF(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
{Purpose:Round 1 of the Transform.
Equivalent to a = b + ((a + F(b,c,d) + x + ac) <<< s)
Where F(b,c,d) = b And c Or Not(b) And d
}
var
Fret: LongInt;
begin
Fret := ((PLong(b)^) And (PLong(c)^)) Or ((Not(PLong(b)^)) And (PLong(d)^));
PLong(a)^ := PLong(a)^ + Fret + PLong(x)^ + ac;
{NOW DO THE ROTATE LEFT}
LongInt(a^):= ROL(LongInt(a^), s);
{LongInt(a^):= ( LongInt(a^) SHL s) Or (LongInt(a^) SHR (32-(s)) );}
Inc(PLong(a)^, PLong(b)^);
end;{FF}
Procedure TMD5.GG(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
{Purpose:Round 2 of the Transform.
Equivalent to a = b + ((a + G(b,c,d) + x + ac) <<< s)
Where G(b,c,d) = b And d Or c Not d
}
var
Gret: LongInt;
begin
Gret := (PLong(b)^ And PLong(d)^) Or ( PLong(c)^ And (Not PLong(d)^));
PLong(a)^ := PLong(a)^ + Gret + PLong(x)^ + ac;
LongInt(a^):= ROL(LongInt(a^), s);
{LongInt(a^):= ( LongInt(a^) SHL s) Or (LongInt(a^) SHR (32-(s)) );}
Inc(PLong(a)^, PLong(b)^);
end;{GG}
Procedure TMD5.HH(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
{Purpose:Round 3 of the Transform.
Equivalent to a = b + ((a + H(b,c,d) + x + ac) <<< s)
Where H(b,c,d) = b Xor c Xor d
}
var
Hret: LongInt;
begin
Hret := PLong(b)^ Xor PLong(c)^ Xor PLong(d)^;
PLong(a)^ := PLong(a)^ + Hret + PLong(x)^ + ac;
LongInt(a^):= ROL(LongInt(a^), s);
{LongInt(a^):= ( LongInt(a^) SHL s) Or (LongInt(a^) SHR (32-(s)) );}
PLong(a)^ := PLong(b)^ + PLong(a)^;
end;{HH}
Procedure TMD5.II(a, b, c, d, x: Pointer; s: BYTE; ac: Longint);
{Purpose:Round 4 of the Transform.
Equivalent to a = b + ((a + I(b,c,d) + x + ac) <<< s)
Where I(b,c,d) = C Xor (b Or Not(d))
}
var
Iret: LongInt;
begin
Iret := (PLong(c)^ Xor (PLong(b)^ Or (Not PLong(d)^)));
PLong(a)^ := PLong(a)^ + Iret + PLong(x)^ + ac;
LongInt(a^):= ROL(PLong(a)^, s );
{ LongInt(a^):= ( LongInt(a^) SHL s) Or (LongInt(a^) SHR (32-(s)) );}
PLong(a)^ := PLong(b)^ + PLong(a)^;
end;{II}
Procedure TMD5.MD5_Transform;
{Purpose:Perform Step 4 of the algorithm.This is where all the important
stuff happens.This performs the rounds on a 64Byte Block.This
procedure should be called in a loop until all input data has been
transformed.
}
begin
FAA := FA;
FBB := FB;
FCC := FC;
FDD := FD;
{ Round 1 }
FF (FpA, FpB, FpC, FpD, @FActiveBlock[ 0], S11, $d76aa478); { 1 }
FF (FpD, FpA, FpB, FpC, @FActiveBlock[ 1], S12, $e8c7b756); { 2 }
FF (FpC, FpD, FpA, FpB, @FActiveBlock[ 2], S13, $242070db); { 3 }
FF (FpB, FpC, FpD, FpA, @FActiveBlock[ 3], S14, $c1bdceee); { 4 }
FF (FpA, FpB, FpC, FpD, @FActiveBlock[ 4], S11, $f57c0faf); { 5 }
FF (FpD, FpA, FpB, FpC, @FActiveBlock[ 5], S12, $4787c62a); { 6 }
FF (FpC, FpD, FpA, FpB, @FActiveBlock[ 6], S13, $a8304613); { 7 }
FF (FpB, FpC, FpD, FpA, @FActiveBlock[ 7], S14, $fd469501); { 8 }
FF (FpA, FpB, FpC, FpD, @FActiveBlock[ 8], S11, $698098d8); { 9 }
FF (FpD, FpA, FpB, FpC, @FActiveBlock[ 9], S12, $8b44f7af); { 10 }
FF (FpC, FpD, FpA, FpB, @FActiveBlock, S13, $ffff5bb1); { 11 }
FF (FpB, FpC, FpD, FpA, @FActiveBlock, S14, $895cd7be); { 12 }
FF (FpA, FpB, FpC, FpD, @FActiveBlock, S11, $6b901122); { 13 }
FF (FpD, FpA, FpB, FpC, @FActiveBlock, S12, $fd987193); { 14 }
FF (FpC, FpD, FpA, FpB, @FActiveBlock, S13, $a679438e); { 15 }
FF (FpB, FpC, FpD, FpA, @FActiveBlock, S14, $49b40821); { 16 }
{ Round 2 }
GG (FpA, FpB, FpC, FpD, @FActiveBlock[ 1], S21, $f61e2562); { 17 }
GG (FpD, FpA, FpB, FpC, @FActiveBlock[ 6], S22, $c040b340); { 18 }
GG (FpC, FpD, FpA, FpB, @FActiveBlock, S23, $265e5a51); { 19 }
GG (FpB, FpC, FpD, FpA, @FActiveBlock[ 0], S24, $e9b6c7aa); { 20 }
GG (FpA, FpB, FpC, FpD, @FActiveBlock[ 5], S21, $d62f105d); { 21 }
GG (FpD, FpA, FpB, FpC, @FActiveBlock, S22,$2441453); { 22 }
GG (FpC, FpD, FpA, FpB, @FActiveBlock, S23, $d8a1e681); { 23 }
GG (FpB, FpC, FpD, FpA, @FActiveBlock[ 4], S24, $e7d3fbc8); { 24 }
GG (FpA, FpB, FpC, FpD, @FActiveBlock[ 9], S21, $21e1cde6); { 25 }
GG (FpD, FpA, FpB, FpC, @FActiveBlock, S22, $c33707d6); { 26 }
GG (FpC, FpD, FpA, FpB, @FActiveBlock[ 3], S23, $f4d50d87); { 27 }
GG (FpB, FpC, FpD, FpA, @FActiveBlock[ 8], S24, $455a14ed); { 28 }
GG (FpA, FpB, FpC, FpD, @FActiveBlock, S21, $a9e3e905); { 29 }
GG (FpD, FpA, FpB, FpC, @FActiveBlock[ 2], S22, $fcefa3f8); { 30 }
GG (FpC, FpD, FpA, FpB, @FActiveBlock[ 7], S23, $676f02d9); { 31 }
GG (FpB, FpC, FpD, FpA, @FActiveBlock, S24, $8d2a4c8a); { 32 }
{ Round 3 }
HH (FpA, FpB, FpC, FpD, @FActiveBlock[ 5], S31, $fffa3942); { 33 }
HH (FpD, FpA, FpB, FpC, @FActiveBlock[ 8], S32, $8771f681); { 34 }
HH (FpC, FpD, FpA, FpB, @FActiveBlock, S33, $6d9d6122); { 35 }
HH (FpB, FpC, FpD, FpA, @FActiveBlock, S34, $fde5380c); { 36 }
HH (FpA, FpB, FpC, FpD, @FActiveBlock[ 1], S31, $a4beea44); { 37 }
HH (FpD, FpA, FpB, FpC, @FActiveBlock[ 4], S32, $4bdecfa9); { 38 }
HH (FpC, FpD, FpA, FpB, @FActiveBlock[ 7], S33, $f6bb4b60); { 39 }
HH (FpB, FpC, FpD, FpA, @FActiveBlock, S34, $bebfbc70); { 40 }
HH (FpA, FpB, FpC, FpD, @FActiveBlock, S31, $289b7ec6); { 41 }
HH (FpD, FpA, FpB, FpC, @FActiveBlock[ 0], S32, $eaa127fa); { 42 }
HH (FpC, FpD, FpA, FpB, @FActiveBlock[ 3], S33, $d4ef3085); { 43 }
HH (FpB, FpC, FpD, FpA, @FActiveBlock[ 6], S34,$4881d05); { 44 }
HH (FpA, FpB, FpC, FpD, @FActiveBlock[ 9], S31, $d9d4d039); { 45 }
HH (FpD, FpA, FpB, FpC, @FActiveBlock, S32, $e6db99e5); { 46 }
HH (FpC, FpD, FpA, FpB, @FActiveBlock, S33, $1fa27cf8); { 47 }
HH (FpB, FpC, FpD, FpA, @FActiveBlock[ 2], S34, $c4ac5665); { 48 }
{ Round 4 }
II (FpA, FpB, FpC, FpD, @FActiveBlock[ 0], S41, $f4292244); { 49 }
II (FpD, FpA, FpB, FpC, @FActiveBlock[ 7], S42, $432aff97); { 50 }
II (FpC, FpD, FpA, FpB, @FActiveBlock, S43, $ab9423a7); { 51 }
II (FpB, FpC, FpD, FpA, @FActiveBlock[ 5], S44, $fc93a039); { 52 }
II (FpA, FpB, FpC, FpD, @FActiveBlock, S41, $655b59c3); { 53 }
II (FpD, FpA, FpB, FpC, @FActiveBlock[ 3], S42, $8f0ccc92); { 54 }
II (FpC, FpD, FpA, FpB, @FActiveBlock, S43, $ffeff47d); { 55 }
II (FpB, FpC, FpD, FpA, @FActiveBlock[ 1], S44, $85845dd1); { 56 }
II (FpA, FpB, FpC, FpD, @FActiveBlock[ 8], S41, $6fa87e4f); { 57 }
II (FpD, FpA, FpB, FpC, @FActiveBlock, S42, $fe2ce6e0); { 58 }
II (FpC, FpD, FpA, FpB, @FActiveBlock[ 6], S43, $a3014314); { 59 }
II (FpB, FpC, FpD, FpA, @FActiveBlock, S44, $4e0811a1); { 60 }
II (FpA, FpB, FpC, FpD, @FActiveBlock[ 4], S41, $f7537e82); { 61 }
II (FpD, FpA, FpB, FpC, @FActiveBlock, S42, $bd3af235); { 62 }
II (FpC, FpD, FpA, FpB, @FActiveBlock[ 2], S43, $2ad7d2bb); { 63 }
II (FpB, FpC, FpD, FpA, @FActiveBlock[ 9], S44, $eb86d391); { 64 }
Inc(FA, FAA);
Inc(FB, FBB);
Inc(FC, FCC);
Inc(FD, FDD);
{ Zeroize sensitive information}
FillChar(FActiveBlock, SizeOf(FActiveBlock), #0);
end;{TMD5.MD5_Transform}
Procedure TMD5.MD5_Hash;
var
pStr: PChar;
begin
MD5_Initialize;
case FType of
SourceFile:
begin
MD5_Hash_File;
end;{SourceFile}
SourceByteArray:
begin
MD5_Hash_Bytes;
end;{SourceByteArray}
SourceString:
begin
{Convert Pascal String to Byte Array}
pStr := StrAlloc(Length(FInputString) + 1);
try {protect dyanmic memory allocation}
StrPCopy(pStr, FInputString);
FSourceLength := Length(FInputString);
FInputArray := Pointer(pStr);
MD5_Hash_Bytes;
finally
StrDispose(pStr);
end;
end;{SourceString}
end;{case}
MD5_Finish;
end;{TMD5.MD5_Hash}
Procedure TMD5.MD5_Hash_Bytes;
var
Buffer: array of Byte;
Count64: Comp;
index: longInt;
begin
Move(FInputArray^, Buffer, FSourceLength);
Count64 := FSourceLength * 8; {Save the Length(in bits) before padding}
Buffer := $80; {Must always pad with at least a '1'}
inc(FSourceLength);
while (FSourceLength mod 64)<>56 do begin
Buffer := 0;
Inc(FSourceLength);
end;
Move(Count64,Buffer,SizeOf(Count64){This better be 64bits});
index := 0;
Inc(FSourceLength, 8);
repeat
Move(Buffer, FActiveBlock, 64);
{Flip bytes here on Mac??}
MD5_Transform;
Inc(Index,64);
until Index = FSourceLength;
end;{TMD5.Hash_Bytes}
Procedure TMD5.MD5_Hash_File;
var
Buffer:array of BYTE;
InputFile: File;
Count64: Comp;
DoneFile : Boolean;
Index: LongInt;
NumRead: integer ;
begin
DoneFile := False;
AssignFile(InputFile, FInputFilePath);
Reset(InputFile, 1);
Count64 := 0;
repeat
BlockRead(InputFile,Buffer,4096,NumRead);
Count64 := Count64 + NumRead;
if NumRead<>4096 {reached end of file}
then begin
Buffer:= $80;
Inc(NumRead);
while (NumRead mod 64)<>56
do begin
Buffer[ NumRead ] := 0;
Inc(NumRead);
end;
Count64 := Count64 * 8;
Move(Count64,Buffer,8);
Inc(NumRead,8);
DoneFile := True;
end;
Index := 0;
repeat
Move(Buffer, FActiveBlock, 64);
{Flip bytes here on a Mac(I think)}
MD5_Transform;
Inc(Index,64);
until Index = NumRead;
until DoneFile;
CloseFile(InputFile);
end;{TMD5.MD5_Hash_File}
Procedure TMD5.MD5_Finish;
begin
FOutputDigest^.A := LongInt(FpA^);
FOutputDigest^.B := LongInt(FpB^);
FOutputDigest^.C := LongInt(FpC^);
FOutputDigest^.D := LongInt(FpD^);
end;
end.
-------------------------Delphi代码结束------------------------
结束语:
洋洋洒洒的一大篇可是我查找了不少资料才写出来,大概可以当成我们学校的 Computer Assignment 了 ^_^ 感谢大家能耐心的看完,最后给大家出到题吧,大家可以试试自己的运气啊,就是:“51E5D4BD3323A02CCCDD0472AE2DC20B”这组数是我通过MD5算法加密一组字符串后产生的结果,大家猜猜看我加密的初始字符串是什么? 提示一下 -- 原始字符串加上空格一共有20位。
^_^Bye! 看不懂!!支持一下!!!! 哎我只看到一堆乱码(E文不懂小学没毕业) 已经被山东的教授破解了啊 完全看不明白..... 慢慢学吧.没有达到这个地步啊. 虽然看不懂,还是收藏下来,以后好好看 还是以后慢慢看吧~~关注。。。收藏中。。。 分析一下,呵呵
页:
[1]
2