用DELPHI實現文件加密壓縮
作者: e夢緣 (wnhoo)
Mail:wnhoo@163.com
概述:
在這篇文件中,講述對單個文件的數據加密、數據壓縮、自解壓的實現。同樣,也可以實現對多個文件或文件夾的壓縮,只要稍加修改便可實現。
關鍵字:加密壓縮、Zlib、流、資源文件
引言:
在日常中,我們一定使用過WINZip、WINRAR這樣的出名的壓縮軟件,就是我們開發軟件過程中不免要遇到數據加密、數據壓縮的問題!本文中就這一技術問題展開探討,同時感謝各位網友的技巧,在我每次面對問題要解決的時候,是你們辛苦地摸索出來的技巧總是讓我豁然開朗,問題迎刃而解。本篇文章主要是運用DELPH的強大的流處理方面的技巧來實現的數據加密壓縮,並用于實際的軟件程序開發中,將我個人的心得、開發經驗寫出來與大家分享。
1、 系統功能
1)、數據壓縮
使用DELPHI提供的兩個流類(TComPRessionStream和TDecompressionStream)來完成數據的壓縮和解壓縮。
2)、數據加密壓縮
通過Delphi編程中「流」的應用實現數據加密,主要采用Tstream的兩個派生類Tfilestream、Tmemorystream 來完成的;其中數據壓縮部分采用1)的實現方法
3)、雙擊壓縮文件自動關聯解壓
通過更改注冊表的實現擴展名與程序文件的關聯,主要采用Tregistry;並且,API函數SHChangeNotify實現注冊效果的立即呈現。
4)、可生成自解壓文件
自解壓的文件實現數據壓縮1)與數據加密壓縮2)的自動解壓;並且,通過資源文件的使用實現可執行的自解壓文件與數據文件的合並,來完成數據的自解壓實現。
2、 系統實現
2.2、關鍵技術的講述
(一)ZLIB
1)、基類 TCustomZlibStream:是類TCompressionStream和TDecompressionStream 類的基類,它主要有一個屬性: OnProgress,在類進行壓縮或解壓縮的過程中會發生這個的事件 。
格式:Procedure OnProgress (Sender: TObject); dynamic;
2)、壓縮類TCompressionStream:除了繼承了基類的OnProgress 屬性外,又增加了一個屬性:CompressionRate,它的定義如下:
Property CompressionRate: Single read GetCompressionRate;
通過這個屬性,可以得到壓縮比。
它的幾個重要的方法定義如下:
Constructor TCompressionStream.Create (CompressionLevel: TCompressionLevel; Dest: TStream);
其中:TcompressionLevel(壓縮類型),它由如下幾個定義:
1)、 clNone :不進行數據壓縮;
2)、 clFastest:進行快速壓縮,犧牲壓縮效率;
3)、 clDefault:進行正常壓縮;
4)、 clMax: 進行最大化壓縮,犧牲速度;
Dest:目的流,用于存放壓縮過的數據。
Function TCompressionStream.Write (const Buffer; Count: Longint): Longint;
其中:Buffer:需要壓縮的數據;
Count: 需要壓縮的數據的字節數;
函數返回寫入流的字節數。
注意:壓縮類TCompressionStream的數據只能是寫入的,如果試圖從其內部讀取數據,將發生一個"Error "異常。需要壓縮的數據通過方法 Write寫入流中,在寫入的過程中就被壓縮,並保存在由構造函數提供的內存流(TmemoryStream)中,同時觸發 OnProcess 事件。
3)、 解壓縮類 TDecompressionStream :和壓縮類TcompressionStream相反,它的數據是只能讀出的,如果試圖往其內部寫數據,將發生一個"Error "異常。
它的幾個重要方法定義如下:
構造函數:Constructor Create(Source: TStream);
其中:Source 是保存著壓縮數據的流;
Function Read(var Buffer; Count: Longint): Longint;
數據讀出函數,Buffer: 存數據緩沖區;Count: 緩沖區的大小;
函數返回讀出的字節數。數據在讀出的過程中,數據被解壓縮,並觸發 OnProcess 事件。
(二)流
在Delphi中,所有流對象的基類爲TStream類,其中定義了所有流的共同屬性和方法。
TStream類中定義的屬性如下:
1)、Size:此屬性以字節返回流中數據大小。
2)、Position:此屬性控制流中存取指針的位置。
Tstream中定義的虛方法有四個:
1)、Read:此方法實現將數據從流中讀出,返回值爲實際讀出的字節數,它可以小于或等于指定的值。
2)、Write:此方法實現將數據寫入流中,返回值爲實際寫入流中的字節數。
3)、Seek:此方法實現流中讀取指針的移動,返回值爲移動後指針的位置。
函數原形爲:Function Seek(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:此方法的作用是從其它流中拷貝數據流。
函數原形爲:Function CopyFrom(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類來存取文件,首先要建立一個實例。聲明如下:
constructor Create(const Filename: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文件
資源文件名:資源文件的在磁盤上存儲的文件全名
例如:
myzjy exefile zjy.exe
2)、編譯資源文件
在DELPHI的安裝目錄的\Bin下,使用BRCC32.exe編譯資源文件.RC。當然,也可以將BRCC32單獨拷貝到程序文檔目錄使用。
例如:
Brcc32 wnhoo_reg.Rc
3)、資源文件引用
…
implementation
{$R *.dfm}
{$R wnhoo_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_ASync or Snd_Memory or snd_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;
//實現關聯注冊
procedure Tmyzip.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
unit wnhoo_zzz;
interface
uses
Windows,Forms,SysUtils,Classes,zlib,Registry,INIFILES, Dialogs, shlobj;
type
pass=string[20];
type
Tmyzip = class
private
{ private declarations here}
protected
{ protected declarations here }
public
procedure regzzz;
procedure ys_file(infileName, outfileName: string;password:pass;isjm:boolean;ysbz:integer);
function jy_file(infileName: string;password:pass=''):boolean;
procedure zjywj(var filename:string);
constructor Create;
destructor Destroy; override;
{ public declarations here }
published
{ published declarations here }
end;
implementation
constructor Tmyzip.Create;
begin
inherited Create; // 初始化繼承下來的部分
end;
//#####################################################
//原文件加密
procedure jm_File(vfile:string;var Target: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,fmOpenread or fmShareExclusive);
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,fmOpenread or fmShareExclusive);
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;
//**************************************************************
//流壓縮
procedure ys_stream(instream, outStream: TStream;ysbz:integer);
{
instream: 待壓縮的已加密文件流
outStream 壓縮後輸出文件流
ysbz:壓縮標准
}
var
ys: TCompressionStream;
begin
//流指針指向頭部
inStream.Position := 0;
//壓縮標准的選擇
case ysbz of
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;
//*****************************************************************
//流解壓
procedure jy_Stream(instream, outStream: TStream);
{
instream :原壓縮流文件
outStream:解壓後流文件
}
var
jyl: TDeCompressionStream;
buf: array[1..512] of byte;
sjread: integer;
begin
inStream.Position := 0;
jyl := TDeCompressionStream.Create(inStream);
try
repeat
//讀入實際大小
sjRead := jyl.Read(buf, sizeof(buf));
if sjread > 0 then
OutStream.Write(buf, sjRead);
until (sjRead = 0);
finally
jyl.Free;
end;
end;
//**************************************************************
//實現關聯注冊
procedure Tmyzip.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;
//壓縮文件
procedure Tmyzip.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;
//解壓文件
function Tmyzip.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);
if resultvalue then
begin
if inifile.ReadString ('file1','password','')=trim(password) then
resultvalue:=true
else
resultvalue:=false;
end
else
resultvalue:=true;
if resultvalue then
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;
//自解壓創建
procedure tmyzip.zjywj(var filename:string);
var
myRes: TResourceStream;//臨時存放自解壓EXE文件
myfile:tfilestream;//原文件流
xfilename:string;//臨時文件名稱
file_ok:tmemorystream; //生成文件的內存流
filesize:integer; //原文件大小
begin
if FileExists(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;
//#####################################################
destructor Tmyzip.Destroy;
begin
inherited Destroy;
end;
end.
3 、結束語
Delphi的全新可視化編程環境,爲我們提供了一種方便、快捷的Windows應用程序開發工具。對于程序開發人員來講,使用Delphi開發應用軟件,無疑會大大地提高編程效率。在delphi中可以很方便的利用流實現文件處理、動態內存處理、網絡數據處理等多種數據形式,寫起程序也會大大提高效率的。
參考文獻:
1、DELPHI系統幫助
2、馮志強. Delphi 中壓縮流和解壓流的應用
3、陳經韬.談Delphi編程中「流」