加密技术与我们的日常生活息息相关,在信息社会更是凸显重要。本文将主要就MD5算法及密码学算法实现做一些相关探讨。MD5的全称是message-digestalgorithm5(信息-摘要算法)。由于其使用不需要支付任何版权费用,安全性好,所以MD5成为了当今非常流行的优秀的典型Hash加密技术。为了使自己能够对MD5加深理解,本文将利用VC++编程实现MD5加密过程,在软件实现上,提供了友好的界面,能够实现字符串和文件加密。
算法描述
算法输入一个字节串,每个字节8个bit,算法的执行分为以下几个步骤:
第一步,补位。
MD5算法先对输入的数据进行补位,使得数据的长度(以byte为单位)对64求余的结果是56。即数据扩展至LEN=K*64+56个字节,K为整数。
补位方法:补一个1,然后补0至满足上述要求。相当于补一个0x80的字节,再补值为0的字节。这一步总共补充的字节数为0~63个。
第二步,附加数据长度。
用一个64位的整数表示数据的原始长度(以bit为单位),将这个数字的8个字节按低位在前高位在后的顺序附加在补位后的数据后面。此时,数据被填补后的总长度为:LEN=K*64+56+8=(K+1)*64Bytes。注意,64位整数是输入数据的原始长度,而不是填充字节后的长度。
第三步,初始化MD5参数。
有4个32位整数变量(A、B、C、D)用来计算信息摘要,每一个变量被初始化成以下以十六进制数表示的数值,低位字节在前面。
wordA:01234567
wordB:89abcdef
wordC:fedcba98
wordD:76543210
注意,低位字节在前面指的是LittleEndian平台上内存中字节的排列方式,而在程序中书写时,要写成。
A=0x67452301
B=0xefcdab89
C=0x98badcfe
D=0x10325476
第四步,定义4个MD5基本的按位操作函数。
X、Y、Z为32位整数。
F(X,Y,Z)=(XandY)or(not(X)andZ)
G(X,Y,Z)=(XandZ)or(Yandnot(Z))
H(X,Y,Z)=XxorYxorZ
I(X,Y,Z)=Yxor(Xornot(Z))
再定义4个分别用于四轮变换的函数。
设Mj表示消息的第j个子分组(从0到15),s表示循环左移s位,则四种操作为:
FF(a,b,c,d,Mj,s,ti)表示a=b+((a+(F(b,c,d)+Mj+ti)s)
GG(a,b,c,d,Mj,s,ti)表示a=b+((a+(G(b,c,d)+Mj+ti)s)
HH(a,b,c,d,Mj,s,ti)表示a=b+((a+(H(b,c,d)+Mj+ti)s)
II(a,b,c,d,Mj,s,ti)表示a=b+((a+(I(b,c,d)+Mj+ti)s)
第五步,对输入数据作变换。
处理数据,N是总的字节数,以64个字节为一组,每组作一次循环,每次循环进行四轮操作。要变换的64个字节用16个32位的整数数组M[0...15]表示,而数组T[1...64]表示一组常数,T[i]为4294967296*abs(sin(i))的32位整数部分,i的单位是弧度,i的取值从1到64。
设计任务
在设计任务中,加密软件具有以下功能:
A:加密软件具有良好的用户界面;
B:能够对字符串进行加密;
C:能够对文件进行加密;
D:对于文件加密大于10M的文件能够显示进度条;
F:在没有选择加密类型时,必须先选择才能进行加密;
H:界面要简练,输出输入表示要明确,尽量做到通俗易懂。
部分代码说明
本程序主要由ZMD5.cpp和MyMD5Dlg.cpp文件构成,前者是MD5算法的实现,后者用于界面的控制和文件选择。以下是这两个文件的关键代码。
1.ZMD5.cpp关键代码
unsignedintZMD5::ROTATE_LEFT(unsignedintx,unsignedintn) { return(((x)(n))|((x)(32-(n)))); } unsignedintZMD5::F(unsignedintx,unsignedinty,unsignedintz) { return((xy)|((~x)z)); } unsignedintZMD5::G(unsignedintx,unsignedinty,unsignedintz) { return((xz)|(y(~z))); } unsignedintZMD5::H(unsignedintx,unsignedinty,unsignedintz) { returnx^y^z; } unsignedintZMD5::I(unsignedintx,unsignedinty,unsignedintz) { return(y^(x|(~z))); } voidZMD5::FF(unsignedinta,unsignedintb,unsignedintc,unsignedint d,unsignedintx,ints,unsignedintac) { (a)+=F((b),(c),(d))+(x)+(ac); (a)=ROTATE_LEFT((a),(s)); (a)+=(b); } voidZMD5::GG(unsignedinta,unsignedintb,unsignedintc,unsignedint d,unsignedintx,ints,unsignedintac) { (a)+=G((b),(c),(d))+(x)+(ac); (a)=ROTATE_LEFT((a),(s)); (a)+=(b); } voidZMD5::HH(unsignedinta,unsignedintb,unsignedintc,unsignedint d,unsignedintx,ints,unsignedintac) { } (a)+=H((b),(c),(d))+(x)+(ac); (a)=ROTATE_LEFT((a),(s)); (a)+=(b); voidZMD5::II(unsignedinta,unsignedintb,unsignedintc,unsignedint d,unsignedintx,ints,unsignedintac) { (a)+=I((b),(c),(d))+(x)+(ac); (a)=ROTATE_LEFT((a),(s)); (a)+=(b); } voidZMD5::Init() { S11=7;S21=5;S31=4;S41=6; S12=12;S22=9;S32=11;S42=10; S13=17;S23=14;S33=16;S43=15; S14=22;S24=20;S34=23;S44=21; A=0x67452301;//inmemory,thisis0x01234567 B=0xEFCDAB89;//inmemory,thisis0x89ABCDEF C=0x98BADCFE;//inmemory,thisis0xFEDCBA98 D=0x10325476;//inmemory,thisis0x76543210 } voidZMD5::Append(unsignedintMsgLen) { //计算要补位的字节数 intm=MsgLen%64; if(m==0) m_AppendByte=56; elseif(m56) m_AppendByte=56-m; else m_AppendByte=64-m+56; //截取传入长度的高十六位和低十六位 inthWord=(MsgLen0xFFFF0000)16; intlWord=MsgLen0x0000FFFF; //将低十六位和高十六位分别乘以八(1byte=8bit) inthDiv=hWord*8; intlDiv=lWord*8; m_MsgLen[0]=lDiv0xFF; m_MsgLen[1]=(lDiv8)0xFF; m_MsgLen[2]=((lDiv16)0xFF)|(hDiv0xFF); m_MsgLen[3]=(hDiv8)0xFF; m_MsgLen[4]=(hDiv16)0xFF; m_MsgLen[5]=(hDiv24)0xFF; m_MsgLen[6]=0; m_MsgLen[7]=0; } voidZMD5::Transform(unsignedcharBlock[64]) { //将64字节位转换为16个字节 unsignedlongx[16]; for(inti=0,j=0;j64;i++,j+=4) x[i]=Block[j]|Block[j+1]8|Block[j+2]16|Block[j+3]24; //初始化临时寄存器变量 unsignedinta,b,c,d; a=A;b=B;c=C;d=D; //第一轮计算 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[10],S13,0xFFFF5BB1);//11 FF(b,c,d,a,x[11],S14,0x895CD7BE);//12 FF(a,b,c,d,x[12],S11,0x6B901122);//13 FF(d,a,b,c,x[13],S12,0xFD987193);//14 FF(c,d,a,b,x[14],S13,0xA679438E);//15 FF(b,c,d,a,x[15],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[11],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[10],S22,0x2441453);//22 GG(c,d,a,b,x[15],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[14],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[13],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[12],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[11],S33,0x6D9D6122);//35 HH(b,c,d,a,x[14],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[10],S34,0xBEBFBC70);//40 HH(a,b,c,d,x[13],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[12],S32,0xE6DB99E5);//46 HH(c,d,a,b,x[15],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[14],S43,0xAB9423A7);//51 II(b,c,d,a,x[5],S44,0xFC93A039);//52 II(a,b,c,d,x[12],S41,0x655B59C3);//53 II(d,a,b,c,x[3],S42,0x8F0CCC92);//54 II(c,d,a,b,x[10],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[15],S42,0xFE2CE6E0);//58 II(c,d,a,b,x[6],S43,0xA3014314);//59 II(b,c,d,a,x[13],S44,0x4E0811A1);//60 II(a,b,c,d,x[4],S41,0xF7537E82);//61 II(d,a,b,c,x[11],S42,0xBD3AF235);//62 II(c,d,a,b,x[2],S43,0x2AD7D2BB);//63 II(b,c,d,a,x[9],S44,0xEB86D391);//64 //保存当前寄存器结果 A+=a;B+=b;C+=c;D+=d; } stringZMD5::ToHex(boolUpperCase) { stringstrResult; intResultArray[4]={A,B,C,D}; charBuf[33]={0}; for(inti=0;i4;i++) { memset(Buf,0,3); sprintf(Buf,%02x,ResultArray[i]0x00FF); strResult+=Buf; memset(Buf,0,3); sprintf(Buf,%02x,(ResultArray[i]8)0x00FF); strResult+=Buf; memset(Buf,0,3); sprintf(Buf,%02x,(ResultArray[i]16)0x00FF); strResult+=Buf; memset(Buf,0,3); sprintf(Buf,%02x,(ResultArray[i]24)0x00FF); strResult+=Buf; } if(UpperCase)CharUpper((char*)strResult.c_str()); returnstrResult; } stringZMD5::GetMD5OfString(stringInputMessage,boolUpperCase) { //初始化MD5所需常量 Init(); //计算追加长度 Append(InputMessage.length()); //对原始信息进行补位 for(inti=0;im_AppendByte;i++) { if(i==0)InputMessage+=(unsignedchar)0x80; elseInputMessage+=(unsignedchar)0x0; } //将原始信息长度附加在补位后的数据后面 for(inti=0;i8;i++)InputMessage+=m_MsgLen[i]; //位块数组 unsignedcharx[64]={0}; //循环,将原始信息以64字节为一组拆分进行处理 for(inti=0,Index=-1;iInputMessage.length();i++) { x[++Index]=InputMessage[i]; if(Index==63) { Index=-1; Transform(x); } } returnToHex(UpperCase); } stringZMD5::GetMD5OfFile(conststringFileName,boolUpperCase) { //定义读取文件的缓冲区 char*ReadBuf=newchar[FILE_BUFFER_READ+1]; memset(ReadBuf,0,FILE_BUFFER_READ); try { //检查文件是否存在 if((_access(FileName.c_str(),0))==-1)return; //二进制方式读取文件 if(m_pFile=fopen(FileName.c_str(),rb),m_pFile==NULL)return; m_FileOpen=true; //获取文件大小 unsignedlongFileSize=0xFFFF; WIN32_FIND_DATAwin32_find_data; HANDLEhFile; if((hFile=FindFirstFile(FileName.c_str(),win32_find_data))!=INVALID_HANDLE _VALUE) if(hFile==NULL)return; if(FileSize=win32_find_data.nFileSizeLow,FileSize==0xFFFF || FileSize==0)return; FindClose(hFile); //初始化MD5所需常量 Init(); //通过文件长度计算追加长度 Append(FileSize); //位块数组 unsignedcharx[64]={0}; //本次读取字节数 intReadSize=fread(ReadBuf,1,FILE_BUFFER_READ,m_pFile); //读取次数 intReadCount=0; while(ReadSize==FILE_BUFFER_READ) { /*
如果用户开启了另一个线程调用此函数,则允许用户从外部结束此函数。
为安全起见,没有在这个类的内部开启线程,可以最大限度的保证文件安全关闭。
*/ if(!m_FileOpen) { } fclose(m_pFile); return; //将处理进度返回给用户 ReadCount++; OnProcessing((int)(FILE_BUFFER_READ*ReadCount/(FileSize/ 100))); //将原始信息以64字节为一组拆分进行处理 for(inti=0,Index=-1;iFILE_BUFFER_READ;i++) { x[++Index]=ReadBuf[i]; if(Index==63) { Index=-1; Transform(x); } } memset(ReadBuf,0,FILE_BUFFER_READ);//重置缓冲区 ReadSize=fread(ReadBuf,1,FILE_BUFFER_READ,m_pFile); }//endwhile /*处理不能被整除的剩余部分数据,此时要对剩余部分数据进行补位及长原始信息长度追加。如果最后一次读取数据的长度为零,说明文件已被读完,则直接将补位数据及原信息长度送入Transform处理。*/ if(ReadSize==0) { stringstrData; for(inti=0;im_AppendByte;i++) { if(i==0)strData+=(unsignedchar)0x80; elsestrData+=(unsignedchar)0x0; } for(inti=0;i8;i++)strData+=m_MsgLen[i]; for(inti=0,Index=-1;istrData.length();i++) { x[++Index]=strData[i]; if(Index==63) { Index=-1; Transform(x); } } } else//将剩余数据处理完再补位 { for(inti=0,Index=-1;iReadSize+m_AppendByte+8;i++) { //将原始信息以64字节为一组,进行拆分处理 if(iReadSize) x[++Index]=ReadBuf[i]; elseif(i==ReadSize) x[++Index]=(unsignedchar)0x80; elseif(iReadSize+m_AppendByte) x[++Index]=(unsignedchar)0x0; elseif(i==ReadSize+m_AppendByte) x[++Index]=m_MsgLen[0]; elseif(i==ReadSize+m_AppendByte+1) x[++Index]=m_MsgLen[1]; elseif(i==ReadSize+m_AppendByte+2) x[++Index]=m_MsgLen[2]; elseif(i==ReadSize+m_AppendByte+3) x[++Index]=m_MsgLen[3]; elseif(i==ReadSize+m_AppendByte+4) x[++Index]=m_MsgLen[4]; elseif(i==ReadSize+m_AppendByte+5) x[++Index]=m_MsgLen[5]; elseif(i==ReadSize+m_AppendByte+6) x[++Index]=m_MsgLen[6]; elseif(i==ReadSize+m_AppendByte+7) x[++Index]=m_MsgLen[7]; if(Index==63) { Index=-1; Transform(x); } } } OnProcessing(100);//处理进度百分之百 //关闭文件 fclose(m_pFile); m_FileOpen=false;//文件打开状态为false delete[]ReadBuf;//释放动态申请的内存 } catch(...) { if(m_FileOpen) fclose(m_pFile); //关闭文件 m_FileOpen=false;//文件打开状态为false delete[]ReadBuf;//释放动态申请的内存 return; returnToHex(UpperCase); } } voidZMD5::GetMD5OfFile_Terminate() { if(m_FileOpen)m_FileOpen=false; } 2.MyMD5Dlg.cpp关键代码 voidCMyMD5Dlg::OnBtnCalculation() { //取“计算”按钮文字,如果上面的文字为“停止”,则停止当前信息的MD5计算CStringstrButton; intselected; selected=GetCheckedRadioButton(IDC_RADIO_1,IDC_RADIO_2); GetDlgItemText(IDC_BTN_CALCULATION,strButton); if(strButton==停止) { SetDlgItemText(IDC_EDIT_RESULT,); md5.GetMD5OfFile_Terminate(); SetDlgItemText(IDC_BTN_CALCULATION,计算); ((CProgressCtrl*)GetDlgItem(IDC_PROGRESS))-ShowWindow(SW_HIDE); return; } //清空结果文本框 SetDlgItemText(IDC_EDIT_RESULT,); //获取待计算MD5的文件数据 CStringstrInput; GetDlgItemText(IDC_EDIT_SOURCE,strInput); //获取待计算MD5的字符串数据 CStringstrInput2; GetDlgItemText(IDC_EDIT_SOURCE2,strInput2); //如果原信息类型为字符串 if(selected==IDC_RADIO_1) { SetDlgItemText(IDC_EDIT_RESULT, md5.GetMD5OfString((LPSTR)(LPCTSTR)strInput2,true).c_str()); } //如果原信息类型为文件 elseif(selected==IDC_RADIO_2) { //如果文件为空,显示选择文件对话框 if(m_File.empty()) { OnBtnSelect(); return; } //检查文件是否存在 elseif((_access((char*)(LPCTSTR)strInput,0))==-1) { AfxMessageBox(文件不存在!); return; } else { //计算文件大小 unsignedlongFileSize=0xFFFFFFFF; WIN32_FIND_DATAwin32_find_data; HANDLEhFile; if((hFile=FindFirstFile((char*)(LPCTSTR)strInput,win32_find_data))!=INVALI D_HANDLE_VALUE) { FindClose(hFile); if(FileSize=win32_find_data.nFileSizeLow,FileSize==0xFFFFFFFF||FileSize==0) return; } //文件大于等于10M,显示处理进度条 if(FileSize=10485760) { //显示处理进度条 m_Processing.SetPos(0); ((CProgressCtrl*)GetDlgItem(IDC_PROGRESS))-ShowWindow(SW_SHOW); //将“计算”按钮上的文字变成“停止” SetDlgItemText(IDC_BTN_CALCULATION,停止); //开启一个线程用于计算MD5,以免造成假死现像 hThread=CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)m_thunk.CallBack(this, CMyMD5Dlg::ProcessingThread,ZThunk::THISCALL), 0,0,dwThreadId); } //文件小于10M,直接进行MD5计算,不开启线程,因为计算时间很短 else { } SetDlgItemText(IDC_EDIT_RESULT, md5.GetMD5OfFile((LPSTR)(LPCTSTR)strInput,true).c_str()); } } else AfxMessageBox(请选择输入类型,“字符串”或“文件”!); } voidCMyMD5Dlg::OnBtnAbout() { CAboutDlgdlg; dlg.DoModal(); } //处理WM_RETURN(回车)和WM_VK_ESCAPE(取消) BOOLCMyMD5Dlg::PreTranslateMessage(MSG*pMsg) { if(pMsg-message==WM_KEYDOWN) { switch(pMsg-wParam) { caseVK_ESCAPE: returnTRUE; caseVK_RETURN: returnTRUE; default: break; } } returnCDialog::PreTranslateMessage(pMsg); } DWORDCMyMD5Dlg::ProcessingThread(LPVOIDlpParameter) { CStringstrInput; GetDlgItemText(IDC_EDIT_SOURCE,strInput); SetDlgItemText(IDC_EDIT_RESULT, md5.GetMD5OfFile((LPSTR)(LPCTSTR)strInput,true).c_str()); return1; } LRESULTCMyMD5Dlg::OnProcessing(WPARAMwParam,LPARAMlParam) { m_Processing.SetPos((int)wParam); if((int)wParam==100) { } //隐藏CProcessCtrl控件 ((CProgressCtrl*)GetDlgItem(IDC_PROGRESS))-ShowWindow(SW_HIDE); //将“停止”按钮上的文字变成“计算” SetDlgItemText(IDC_BTN_CALCULATION,计算); return1; } //选择字符串加密功能 1voidCMyMD5Dlg::OnRadio_1() { //TODO:Addyourcontrolnotificationhandlercodehere UpdateData(TRUE); GetDlgItem(IDC_OPEN)-EnableWindow(false); GetDlgItem(IDC_EDIT_SOURCE)-EnableWindow(false); GetDlgItem(IDC_EDIT_SOURCE2)-EnableWindow(true); SetDlgItemText(IDC_EDIT_SOURCE2,); } //选择文件加密功能 voidCMyMD5Dlg::OnRadio_2() { //TODO:Addyourcontrolnotificationhandlercodehere UpdateData(TRUE); GetDlgItem(IDC_OPEN)-EnableWindow(true); GetDlgItem(IDC_EDIT_SOURCE)-EnableWindow(true); GetDlgItem(IDC_EDIT_SOURCE2)-EnableWindow(false); SetDlgItemText(IDC_EDIT_SOURCE,); } //文件打开按钮 voidCMyMD5Dlg::OnOpen() { //TODO:Addyourcontrolnotificationhandlercodehere ZFileDialogfileDlg; dequestringdqSelectFile=fileDlg.GetOpenFileName(FALSE,所有文件 (*.*)\0*.*\0\0); if(dqSelectFile.size()!=0) { m_File=dqSelectFile[0]; SetDlgItemText(IDC_EDIT_SOURCE,m_File.c_str()); } }
系统运行说明
本文件不需要安装,直接运行文件夹里面的MyMD5.exe文件,就会打开如图1所示的界面,新手可以先看说明,如果对本程序已经了解,可以直接运行程序。接下来要做的就是选择输入的类型,当选择文件加密并且大小大于10M的时候,会出现如图2所示的界面(其他两种:如字符串和文件小于10M的请自己体验),最终的计算结果如图3所示。
系统测试
1.正确性测试
1)字符串计算
输入字符串:123456789
计算结果为:25F9E794323B453885F5181F1B624D0B
解密结果:123456789
证明本程序能够正确的计算字符串的摘要。
2)文件计算
在记事本中输入字符串“123456789”,保存为:“1.txt”。
计算该文件结果为:25F9E794323B453885F5181F1B624D0B。
2.性能测试
本程序能较快的完成摘要计算功能,对100M大小的文件,计算时间大约为3秒钟,对系统资源占用较小。