用DELPHI實現文件加密壓縮
作者:e夢緣(wnhoo)
Mail:[email protected]
風花雪月e夢情緣
點擊下載此詳細說明文件
概述:
在這篇文件中,講述對單個文件的數據加密、數據壓縮、自解壓的實現。同樣,也可以實現對多個文件或文件夾的壓縮,只要稍加修改便可實現。
關鍵字:加密壓縮、Zlib、流、資源文件
引言:
在日常中,我們一定使用過WINZip、WINRAR這樣的出名的壓縮軟件,就是我們開發軟件過程中不免要遇到數據加密、數據壓縮的問題!本文中就這一技術問題展開探討,同時感謝各位網友的技巧,在我每次面對問題要解決的時候,是你們辛苦地摸索出來的技巧總是讓我豁然開朗,問題迎刃而解。本篇文章主要是運用DELPH的強大的流處理方面的技巧來實現的數據加密壓縮,並用於實際的軟件程序開發中,將我個人的心得、開發經驗寫出來與大家分享。
1、系統功能
1)、數據壓縮
使用DELPHI提供的兩個流類(TComPRessionStream和TDecompressionStream)來完成數據的壓縮和解壓縮。
2)、數據加密壓縮
通過Delphi編程中“流”的應用實現數據加密,主要採用Tstream的兩個派生類Tfilestream、Tmemorystream來完成的;其中數據壓縮部分採用1)的實現方法
3)、雙擊壓縮文件自動關聯解壓
通過更改註冊表的實現擴展名與程序文件的關聯,主要採用Tregistry;並且,API函數SHChangeNotify實現註冊效果的立即呈現。
4)、可生成自解壓文件
自解壓的文件實現數據壓縮1)與數據加密壓縮2)的自動解壓;並且,通過資源文件的使用實現可執行的自解壓文件與數據文件的合併,來完成數據的自解壓實現。
2、系統實現
2.1、工作原理
2.2、關鍵技術的講述
(一)ZLIB
1)、基類TCustomZlibStream:是類TCompressionStream和TDecompressionStream類的基類,它主要有一個屬性:OnProgress,在類進行壓縮或解壓縮的過程中會發生這個的事件。
格式:ProcedureOnProgress(Sender:TObject);dynamic;
2)、壓縮類TCompressionStream:除了繼承了基類的OnProgress屬性外,又增加了一個屬性:CompressionRate,它的定義如下:
PropertyCompressionRate:SinglereadGetCompressionRate;
通過這個屬性,可以得到壓縮比。
它的幾個重要的方法定義如下:
ConstructorTCompressionStream.Create(CompressionLevel:TCompressionLevel;Dest:TStream);
其中:TcompressionLevel(壓縮類型),它由如下幾個定義:
1)、clNone:不進行數據壓縮;
2)、clFastest:進行快速壓縮,犧牲壓縮效率;
3)、clDefault:進行正常壓縮;
4)、clMax:進行最大化壓縮,犧牲速度;
Dest:目的流,用於存放壓縮過的數據。
FunctionTCompressionStream.Write(constBuffer;Count:Longint):Longint;
其中:Buffer:需要壓縮的數據;
Count:需要壓縮的數據的字節數;
函數返回寫入流的字節數。
注意:壓縮類TCompressionStream的數據只能是寫入的,如果試圖從其內部讀取數據,將發生一個"Error"異常。需要壓縮的數據通過方法Write寫入流中,在寫入的過程中就被壓縮,並保存在由構造函數提供的內存流(TmemoryStream)中,同時觸發OnProcess事件。
3)、解壓縮類TDecompressionStream:和壓縮類TcompressionStream相反,它的數據是只能讀出的,如果試圖往其內部寫數據,將發生一個"Error"異常。
它的幾個重要方法定義如下:
構造函數:ConstructorCreate(Source:TStream);
其中:Source是保存著壓縮數據的流;
FunctionRead(varBuffer;Count:Longint):Longint;
數據讀出函數,Buffer:存數據緩衝區;Count:緩衝區的大小;
函數返回讀出的字節數。數據在讀出的過程中,數據被解壓縮,並觸發OnProcess事件。
(二)流
在Delphi中,所有流對象的基類為TStream類,其中定義了所有流的共同屬性和方法。
TStream類中定義的屬性如下:
1)、Size:此屬性以字節返回流中數據大小。
2)、Position:此屬性控制流中存取指針的位置。
Tstream中定義的虛方法有四個:
1)、Read:此方法實現將數據從流中讀出,返回值為實際讀出的字節數,它可以小於或等於指定的值。
2)、Write:此方法實現將數據寫入流中,返回值為實際寫入流中的字節數。
3)、Seek:此方法實現流中讀取指針的移動,返回值為移動後指針的位置。
函數原形為:FunctionSeek(Offset:Longint;Origint:Word):Longint;virtual;abstract;
參數Offset為偏移字節數,參數Origint指出Offset的實際意義,其可能的取值如下:
soFromBeginning:Offset為指針距離數據開始的位置。此時Offset必須大於或者等於零。
soFromCurrent:Offset為移動後指針與當前指針的相對位置。
soFromEnd:Offset為移動後指針距離數據結束的位置。此時Offset必須小於或者等於零。
4)、Setsize:此方法實現改變數據的大小。
另外,TStream類中還定義了幾個靜態方法:
1)、ReadBuffer:此方法的作用是從流中當前位置讀取數據,跟上面的Read相同。
注意:當讀取的數據字節數與需要讀取的字節數不相同時,將產生EReadError異常。
2)、WriteBuffer:此方法的作用是在當前位置向流寫入數據,跟上面的Write相同。
注意:當寫入的數據字節數與需要寫入的字節數不相同時,將產生EWriteError異常。
3)、CopyFrom:此方法的作用是從其它流中拷貝數據流。
函數原形為:FunctionCopyFrom(Source:TStream;Count:Longint):Longint;
參數Source為提供數據的流,Count為拷貝的數據字節數。當Count大於0時,CopyFrom從Source參數的當前位置拷貝Count個字節的數據;當Count等於0時,CopyFrom設置Source參數的Position屬性為0,然後拷貝Source的所有數據;
Tstream常見派生類:
TFileStream(文件流的存取)
TStringStream(處理內存中的字符串類型數據)
TmemoryStream(對於工作的內存區域數據處理)
TBlobStream(BLOB類型字段的數據處理)
TwinSocketStream(socket的讀寫處理)
ToleStream(COM接口的數據處理)
TresourceStream(資源文件流的處理)
其中最常用的是TFileStream類。使用TFileStream類來存取文件,首先要建立一個實例。聲明如下:
constructorCreate(constFilename:string;Mode:Word);
Filename為文件名(包括路徑)
Mode為打開文件的方式,它包括文件的打開模式和共享模式,其可能的取值和意義如下:
打開模式:
fmCreate:用指定的文件名建立文件,如果文件已經存在則打開它。
fmOpenRead:以只讀方式打開指定文件
fmOpenWrite:以只寫方式打開指定文件
fmOpenReadWrite:以寫寫方式打開指定文件
共享模式:
fmShareCompat:共享模式與FCBs兼容
fmShareExclusive:不允許別的程序以任何方式打開該文件
fmShareDenyWrite:不允許別的程序以寫方式打開該文件
fmShareDenyRead:不允許別的程序以讀方式打開該文件
fmShareDenyNone:別的程序可以以任何方式打開該文件
(三)資源文件
1)、創建資源文件
首先創建一個.Rc的純文本文件。
格式:資源標識符關鍵字資源文件名
資源標識符:程序中調用資源時的特殊標號;
關鍵字:標識資源文件類型;
Wave:資源文件是聲音文件;
RCDATA:JPEG文件;
AVI:AVI動畫;
ICON:圖標文件;
BITMAP:位圖文件;
CURSOR:光標文件;
EXEFILE:EXE文件
資源文件名:資源文件的在磁盤上存儲的文件全名
例如:
myzjyexefilezjy.exe
2)、編譯資源文件
在DELPHI的安裝目錄的/Bin下,使用BRCC32.exe編譯資源文件.RC。當然,也可以將BRCC32單獨拷貝到程序文檔目錄使用。
例如:
Brcc32wnhoo_reg.Rc
3)、資源文件引用
…
implementation
{$R*.dfm}
{$Rwnhoo_reg.Res}
…
4)、調用資源文件
(1)存取資源文件中的位圖(Bitmap)
Image.Picture.Bitmap.Handle:=LoadBitmap(hInstance,'資源標識符');
注:如果位圖沒有裝載成功,程序仍舊執行,但是Image將不再顯示圖片。你可以根據LoadBitmap函數的返回值判斷是否裝載成功,如果裝載成功返回值是非0,如果裝載失敗返回值是0。
另外一個存取顯示位圖的方法如下
Image.Picture.Bitmap.LoadFromResourceName(hInstance,'資源標識符');
(2)存取資源文件中的光標
Screen.Cursors[]是一個光標數組,使用光標文件我們可以將定制的光標加入到這個屬性中。因為默認的光標在數組中索引值是0,所以除非想取代默認光標,最好將定制的光標索引值設為1。
Screen.Cursors[1]:=LoadCursor(hInstance,'資源標識符');
Image.Cursor:=1;
(3)存取資源文件中的圖標
將圖標放在資源文件中,可以實現動態改變應用程序圖標。
application.Icon.Handle:=LoadIcon(hInstance,'資源標識符');
(4)存取資源文件中的AVI
Animate.ResName:='MyAvi';//資源標識符號
Animate.Active:=True;
(5)存取資源文件中的JPEG
把jpeg單元加入到uses單元中。
var
Fjpg:TJpegImage;
FStream:TResourceStream;
begin
Fjpg:=TJpegImage.Create;
//TresourceStream使用
FStream:=TResourceStream.Create(Hinstance,'資源標識符',資源類型);
FJpg.LoadFromStream(FStream);
Image.Picture.Bitmap.Assign(FJpg);
(6)存取資源文件中的Wave
把MMSystem加入uses單元中
PlaySound(pchar('mywav'),Hinstance,Snd_ASyncorSnd_Memoryorsnd_Resource);
(四)INI文件操作
(1)INI文件的結構:
;這是關於INI文件的註釋部分
[節點]
關鍵字=值
...
INI文件允許有多個節點,每個節點又允許有多個關鍵字,“=”後面是該關鍵字的值(類型有三種:字符串、整型數值和布爾值。其中字符串存貯在INI文件中時沒有引號,布爾真值用1表示,布爾假值用0表示)。註釋以分號“;”開頭。
(2)INI文件的操作
1、在Interface的Uses節增加IniFiles;
2、在Var變量定義部分增加一行:inifile:Tinifile;然後,就可以對變量myinifile進行創建、打開、讀取、寫入等操作了。
3、打開INI文件:inifile:=Tinifile.create('tmp.ini');
4、讀取關鍵字的值:
a:=inifile.Readstring('節點','關鍵字',缺省值);//string類型
b:=inifile.Readinteger('節點','關鍵字',缺省值);//integer類型
c:=inifile.Readbool('節點','關鍵字',缺省值);//boolean類型
其中[缺省值]為該INI文件不存在該關鍵字時返回的缺省值。
5、寫入INI文件:
inifile.writestring('節點','關鍵字',變量或字符串值);
inifile.writeinteger('節點','關鍵字',變量或整型值);
inifile.writebool('節點','關鍵字',變量或True或False);
當這個INI文件的節點不存在時,上面的語句還會自動創建該INI文件。
6、刪除關鍵字:
inifile.DeleteKey('節點','關鍵字');//關鍵字刪除
inifile.EraseSection('節點');//節點刪除
7、節點操作:
inifile.readsection('節點',TStrings變量);//可將指定小節中的所有關鍵字名讀取至一個字符串列表變量中;
inifile.readsections(TStrings變量);//可將INI文件中所有小節名讀取至一個字符串列表變量中去。
inifile.readsectionvalues('節點',TStrings變量);//可將INI文件中指定小節的所有行(包括關鍵字、=、值)讀取至一個字符串列表變量中去。
8、釋放:inifile.distory;或inifile.free;
(五)文件關聯
uses
registry,shlobj;
//實現關聯註冊
procedureTmyzip.regzzz;
var
reg:TRegistry;
begin
reg:=TRegistry.Create;
reg.RootKey:=HKEY_CLASSES_ROOT;
reg.OpenKey('.zzz',true);
reg.WriteString('','myzip');
reg.CloseKey;
reg.OpenKey('myzip/shell/open/command',true);
//用於打開.zzz文件的可執行程序
reg.WriteString('','"'+application.ExeName+'""%1"');
reg.CloseKey;
reg.OpenKey('myzip/DefaultIcon',true);
//取當前可執行程序的圖標為.zzz文件的圖標
reg.WriteString('',''+application.ExeName+',0');
reg.Free;
//立即刷新
SHChangeNotify(SHCNE_ASSOCCHANGED,SHCNF_IDLIST,nil,nil);
end;
2.3、加密壓縮的實現
1、生成INI臨時加密文件
用於加密的INI的臨時文件格式:
[FILE1]//節點,在軟件中使用FILE1..N可以實現多文件加密
FILENAME=壓縮文件名
PASSWORD=解壓密碼
FILESIZE=文件大小
FILEDATE=創建日期
ISJM=解壓是否需要密碼
如果是實現多文件、文件夾的信息存儲,可以將密碼關鍵字存在一個總的節點下。本文中僅是實現對單個文件的加密,所以只要上述格式就可以了。
2、將數據文件與用於加密的INI文件的合併,這可以採用文件流的形式實現。
加密後文件結構圖:
圖(1)
圖(2)
上面兩種形式,可以根據實際採用。本文采用圖(1)的結構。
3、對於加密後的數據,採用ZLIB技術實現壓縮存儲,生成新壓縮形式的文件。
2.4、文件關聯的實現見2.2(五)
2.5、自解壓的實現
1.建立一個專門用來自解壓的可執行程序文件
2.將1中建立的文件,生成資源文件
3.將資源文件放到本文中這個壓縮工具的程序中一起編譯。
4.通過將資源文件與壓縮文件的合併,生成自解壓文件。
自解壓文件結構圖:
5.自解壓實現:通過將自身文件中的加密壓縮數據的分解,然後對分解的加密壓縮數據再一次解壓並分解出真正的數據文件。
2.6系統程序設計
這是關於這個軟件實現的核心部分全部代碼,在這裡詳細講述這個軟件所有的技術細節。
//wnhoo_zzz.pas
unitwnhoo_zzz;
interface
uses
Windows,Forms,SysUtils,Classes,zlib,Registry,INIFILES,Dialogs,shlobj;
type
pass=string[20];
type
Tmyzip=class
private
{privatedeclarationshere}
protected
{protecteddeclarationshere}
public
procedureregzzz;
procedureys_file(infileName,outfileName:string;password:pass;isjm:boolean;ysbz:integer);
functionjy_file(infileName:string;password:pass=''):boolean;
procedurezjywj(varfilename:string);
constructorCreate;
destructorDestroy;override;
{publicdeclarationshere}
published
{publisheddeclarationshere}
end;
implementation
constructorTmyzip.Create;
begin
inheritedCreate;//初始化繼承下來的部分
end;
//################################################ #####
//原文件加密
procedurejm_File(vfile:string;varTarget:TMemoryStream;password:pass;isjm:boolean);
{
vfile:加密文件
target:加密後輸出目標流》》》
password:密碼
isjm:是否加密
-------------------------------------------------- -----------
加密後文件SIZE=原文件SIZE+[INI加密壓縮信息文件]的SIZE+存儲[INI加密壓縮信息文件]的大小數據類型的SIZE
-------------------------------------------------- -------------
}
var
tmpstream,inistream:TFileStream;
FileSize:integer;
inifile:TINIFILE;
filename:string;
begin
//打開需要[加密壓縮文件]
tmpstream:=TFileStream.Create(vFile,fmOpenreadorfmShareExclusive);
try
//向[臨時加密壓縮文件流]尾部寫入[原文件流]
Target.Seek(0,soFromEnd);
Target.CopyFrom(tmpstream,0);
//取得文件路徑,生成[INI加密壓縮信息文件]
filename:=ExtractFilePath(paramstr(0))+'tmp.in_';
inifile:=TInifile.Create(filename);
inifile.WriteString('file1','filename',ExtractFileName(vFile));
inifile.WriteString('file1','password',password);
inifile.WriteInteger('file1','filesize',Target.Size);
inifile.WriteDateTime('file1','fileDate',now());
inifile.WriteBool('file1','isjm',isjm);
inifile.Free;
//讀入[INI加密壓縮信息文件流]
inistream:=TFileStream.Create(filename,fmOpenreadorfmShareExclusive);
try
//繼續在[臨時加密壓縮文件流]尾部加入[INI加密壓縮信息文件]
inistream.Position:=0;
Target.Seek(0,sofromend);
Target.CopyFrom(inistream,0);
//計算當前[INI加密壓縮信息文件]的大小
FileSize:=inistream.Size;
//繼續在[臨時加密文件尾部]加入[INI加密壓縮信息文件]的SIZE信息
Target.WriteBuffer(FileSize,sizeof(FileSize));
finally
inistream.Free;
deletefile(filename);
end;
finally
tmpstream.Free;
end;
end;
//************************************************ **************
//流壓縮
procedureys_stream(instream,outStream:TStream;ysbz:integer);
{
instream:待壓縮的已加密文件流
outStream壓縮後輸出文件流
ysbz:壓縮標準
}
var
ys:TCompressionStream;
begin
//流指針指向頭部
inStream.Position:=0;
//壓縮標準的選擇
caseysbzof
1:ys:=TCompressionStream.Create(clnone,OutStream);//不壓縮
2:ys:=TCompressionStream.Create(clFastest,OutStream);//快速壓縮
3:ys:=TCompressionStream.Create(cldefault,OutStream);//標準壓縮
4:ys:=TCompressionStream.Create(clmax,OutStream);//最大壓縮
else
ys:=TCompressionStream.Create(clFastest,OutStream);
end;
try
//壓縮流
ys.CopyFrom(inStream,0);
finally
ys.Free;
end;
end;
//************************************************ *****************
//流解壓
procedurejy_Stream(instream,outStream:TStream);
{
instream:原壓縮流文件
outStream:解壓後流文件
}
var
jyl:TDeCompressionStream;
buf:array[1..512]ofbyte;
sjread:integer;
begin
inStream.Position:=0;
jyl:=TDeCompressionStream.Create(inStream);
try
repeat
//讀入實際大小
sjRead:=jyl.Read(buf,sizeof(buf));
ifsjread>0then
OutStream.Write(buf,sjRead);
until(sjRead=0);
finally
jyl.Free;
end;
end;
//************************************************ **************
//實現關聯註冊
procedureTmyzip.regzzz;
var
reg:TRegistry;
begin
reg:=TRegistry.Create;
reg.RootKey:=HKEY_CLASSES_ROOT;
reg.OpenKey('.zzz',true);
reg.WriteString('','myzip');
reg.CloseKey;
reg.OpenKey('myzip/shell/open/command',true);
//用於打開.zzz文件的可執行程序
reg.WriteString('','"'+application.ExeName+'""%1"');
reg.CloseKey;
reg.OpenKey('myzip/DefaultIcon',true);
//取當前可執行程序的圖標為.zzz文件的圖標
reg.WriteString('',''+application.ExeName+',0');
reg.Free;
//立即刷新
SHChangeNotify(SHCNE_ASSOCCHANGED,SHCNF_IDLIST,nil,nil);
end;
//壓縮文件
procedureTmyzip.ys_file(infileName,outfileName:string;password:pass;isjm:boolean;ysbz:integer);
{
infileName://需要壓縮加密的文件
outfileName://壓縮加密後產生的文件
password://解壓密碼
ysbz://壓縮標準
}
var
instream:TMemoryStream;//文件加密後的臨時流
outStream:TFileStream;//壓縮輸出文件流
begin
//創建[文件加密後的臨時流]
instream:=TMemoryStream.Create;
//文件加密
jm_file(infileName,instream,password,isjm);
//創建壓縮輸出文件流
outStream:=TFileStream.create(outFIleName,fmCreate);
try
//[文件加密後的臨時流]壓縮
ys_stream(instream,OutStream,ysbz);
finally
OutStream.free;
instream.Free;
end;
end;
//解壓文件
functionTmyzip.jy_file(infileName:string;password:pass=''):boolean;
var
inStream,inistream,filestream_ok:TFileStream;
{
instream://解壓文件名稱
inistream://INI臨時文件流
filestream_ok://解壓OK的文件
}
outStream:tmemorystream;//臨時內存流
inifile:TINIFILE;//臨時INI文件
FileSize:integer;//密碼文件的SIZE
resultvalue:boolean;//返回值
begin
try
inStream:=TFileStream.create(inFIleName,fmOpenRead);
try
outStream:=tmemorystream.create;
try
jy_stream(insTream,OutStream);
//生成臨時INI文件
inistream:=TFileStream.create(ExtractFilePath(paramstr(0))+'tmp.in_',fmCreate);
try
//指向存儲解碼信息的INTEGER型變量位置
OutStream.Seek(-sizeof(FileSize),sofromend);
//讀入變量信息
OutStream.ReadBuffer(FileSize,sizeof(FileSize));
//指向解碼信息位置
OutStream.Seek(-(FileSize+sizeof(FileSize)),sofromend);
//將解碼信息讀入INI流中
inistream.CopyFrom(OutStream,FileSize);
//釋放INI文件流
inistream.Free;
//讀入INI文件信息
inifile:=TINIFILE.Create(ExtractFilePath(paramstr(0))+'tmp.in_');
resultvalue:=inifile.ReadBool('file1','isjm',false);
ifresultvaluethen
begin
ifinifile.ReadString('file1','password','')=trim(password)then
resultvalue:=true
else
resultvalue:=false;
end
else
resultvalue:=true;
ifresultvaluethen
begin
filestream_ok:=TFileStream.create(ExtractFilePath(paramstr(1))+inifile.ReadString('file1','filename','wnhoo.zzz'),fmCreate);
try
OutStream.Position:=0;
filestream_ok.CopyFrom(OutStream,inifile.ReadInteger('file1','filesize',0));
finally
filestream_ok.Free;
end;
end;
inifile.Free;
finally
//刪除臨時INI文件
deletefile(ExtractFilePath(paramstr(0))+'tmp.in_');
end;
//
finally
OutStream.free;
end;
finally
inStream.free;
end;
except
resultvalue:=false;
end;
result:=resultvalue;
end;
//自解壓創建
proceduretmyzip.zjywj(varfilename:string);
var
myRes:TResourceStream;//臨時存放自解壓EXE文件
myfile:tfilestream;//原文件流
xfilename:string;//臨時文件名稱
file_ok:tmemorystream;//生成文件的內存流
filesize:integer;//原文件大小
begin
ifFileExists(filename)then
begin
//創建內存流
file_ok:=tmemorystream.Create;
//釋放資源文件--自解壓EXE文件
myRes:=TResourceStream.Create(Hinstance,'myzjy',Pchar('exefile'));
//將原文件讀入內存
myfile:=tfilestream.Create(filename,fmOpenRead);
try
myres.Position:=0;
file_ok.CopyFrom(myres,0);
file_ok.Seek(0,sofromend);
myfile.Position:=0;
file_ok.CopyFrom(myfile,0);
file_ok.Seek(0,sofromend);
filesize:=myfile.Size;
file_ok.WriteBuffer(filesize,sizeof(filesize));
file_ok.Position:=0;
xfilename:=ChangeFileExt(filename,'.exe');
file_ok.SaveToFile(xfilename);
finally
myfile.Free;
myres.Free;
file_ok.Free;
end;
DeleteFile(filename);
filename:=xfilename;
end;
end;
//################################################ #####
destructorTmyzip.Destroy;
begin
inheritedDestroy;
end;
end.
3、結束語
Delphi的全新可視化編程環境,為我們提供了一種方便、快捷的Windows應用程序開發工具。對於程序開發人員來講,使用Delphi開發應用軟件,無疑會大大地提高編程效率。在delphi中可以很方便的利用流實現文件處理、動態內存處理、網絡數據處理等多種數據形式,寫起程序也會大大提高效率的。
參考文獻:
1、DELPHI系統幫助
2、馮志強.Delphi中壓縮流和解壓流的應用
3、陳經韜.談Delphi編程中“流”