摘 要:本文深入讨论了Delphi环境下流的工作原理,通过重载ToleStream流的读(Read)、写(Write)函数,并结合OLE复合文档实现数据加密,有效地增强单 机数据库系统的安全性。
关键词:流; 函数重载; OLE复合文档 ;数据加密
一、引言
目前,很多C/S(Client/Server)分布式应用系统为了改善系统的工作效率和提高系统的伸缩性,都把SQL服务器上的一些基本数据分发到各个客户机上。这一过程有两种选择:一是在客户机上安装SQL Server桌面版,二是把SQL服务器上的数据转换为Paradox 数据表或Access数据库等形式。从系统“瘦身”角度看,更多程序员选择后者,但此时,SQL服务器的安全保密功能对客户机上的数据库便完全失效,须由程序员重新进行数据安全加密。传统的方法是往转换后的数据表添加口令,然后在应用系统中使用TSession对象自动打开带有口令的数据表。事实上,这一方法有着严重的不足:因为Paradox 7数据表存在着万能口令“jIGGAe”或“cupcdvum”(用户在Database Desktop中,用万能口令就能打开所有添加口令的数据表);而深受程序员喜爱的Access数据库的口令破解程序也在互联网上广为传播。为了更有效地增强单机数据库系统的安全性,本文深入讨论了Delphi环境下流的工作原理,并在ToleStream类的基础上派生一个子类,通过重载其读(Read)、写(Write)函数,并结合OLE复合文档,实现数据加密。
二、原理机制
2.1 Delphi流的工作原理
在Delphi中,“流”是指数据从一个对象到另一个对象的流动。简单来说,就是建立在面向对象基础上的一种抽象的处理数据的工具。当应用程序与外界环境进行信息交换时,存在着两个对象:一个是应用程序的对象,另一个是文件对象,目前,Windows操作系统已把键盘、屏幕、打印机和通信端口等扩充为文件进行处理。若在程序中建立一个流对象,并指定这个流对象与某个文件对象建立连接,那么在程序中操作了流对象后,文件系统将对相应的文件对象产生作用。
Dephi提供了一个抽象的数据类型TStream来支持对流式数据的操作,流式数据通常是来自文件、数据库、内存对象、OLE对象等,几种常用流的继承关系如图1所示。TStream中定义了所有流的共同属性和方法,现把一些主要的属性和方法做如下介绍:
(1)Size:此属性以字节返回流中数据大小;
(2)Position:此属性控制流中存取指针的位置;
(3)Read:此方法实现将数据从流中读出。函数原形为:
Function Read(var Buffer;Count:Longint):Longint;virtual;abstract;参数Buffer为数据读出时放置的缓冲区,Count为需要读出的数据的字节数,该方法返回值为实际读出的字节数,它可以小于或等于Count中指定的值。
(4)Write:此方法实现将数据写入流中。函数原形为:
Function Write(var Buffer;Count:Longint):Longint;virtual;abstract;参数Buffer为将要写入流中的数据的缓冲区,Count为数据的长度字节数,该方法返回值为实际写入流中的字节数。
TObject | TStream | | | TcustomMemoryStream ToleStream THandleStream | | TMemoryStream TFileStream 图1:Delphi的几种常用流的继承关系。
2.2 数据库的流操作
Delphi提供了TclientDataSet控件来支持数据库的流操作,该控件继承自TDataSet,其数据存储文件格式扩展名为.cds或.xml,是基于文件型数据存储和操作的控件。TclientDataSett控件封装了对数据进行操作处理的接口和功能,其本身并不依赖BDE,ODBC,ADO等数据库驱动程序,所以是开发单机“瘦”数据库应用程序的理想控件。
TclientDataSet控件的SaveToStream()和LoadFromStream()函数可完成数据库与流的读写转换操作。具体的函数原形和参数说明如下:
(1)函数原形:procedure SaveToStream(Stream: TStream; Format: TDataPacketFormat = dfBinary);功能是把TclientDataSet控件所连接的数据写到Stream中。参数Stream是一个抽象流,其具体对象可以是TmemoryStream、ToleStream或TfileStream等,可见,TclientDataSet控件就是通过SaveToStream函数把数据表的内容输出到内存流、OLE接口流或文件流等,从而完成与其它对象交换信息;参数Format是指流的格式,有三种格式:dfBinary(二进制), dfXML(可扩展标记语言), dfXMLUTF8(UTF8可扩展标记语言)。
(2)函数原形: LoadFromStream(Stream: TStream); 功能是把Stream流的内容读入到TclientDataSet控件中,特别指出:这里的Stream应是一个数据表格式的流,否则数据加载失败。
2.3 数据加密方案
TclientDataSet控件通过SaveToStream函数把数据表的信息写入到某一个流中,实质是调用该流的write函数来完成写入功能的。根据这一特点,我们设计如下的数据加密方案:在ToleStream类的基础上派生一个子类TPasswordstream,然后重载该子类的Read、Write函数,使数据表的每个字节在写入或读出TPasswordstream流时,分别与同一字符串的某位相应字符进行相加或相减,最终选用OLE复合文档来保存加密后的流数据,OLE复合文档的原理与编程可参见文献[4]。由于数据表的信息在写入流之前已作了一定规律的变换,所以保存在OLE复合文档中流数据已失去数据表格式,读出该流数据时应有相应的解密过程,否则加载失败。
三、代码实现
3.1新建单元Unit2,从ToleStream基类派生TPasswordstream类,并编写加密、解密过程。
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, AxCtrls;
type
TPasswordstream=class(TOlestream) //以Tolestream为基类,
//派生Tpasswordstream类
private
fkeyStr:string; //加密字符串
public
function read(var buffer;count:longint): longint;override; //重载read函数
function write(const buffer;count:longint): longint;override; //重载write
//函数
property keyStr:string read fkeyStr write fkeyStr ;
end;
implementation
function TPasswordstream.write(const buffer; count:longint):longint;
var
Pbu,Pmy,mykey:pchar;
i,enc:integer;
begin
getmem(pmy,count); //为pmy分配内存
mykey:=pchar(keyStr); //将keyStr 转换为pchar指针
try
pbu:=pchar(@buffer); //将buffer转换为pchar指针
for i:=0 to count-1 do
//将buffer的每个字符与keyStr字符串相应的一位字符相加,结果放入pmy指向的//内存区
begin
enc:=(ord(pbu[i])+ord(mykey
[(i mod length(keyStr))])) mod 256;
Pmy[i]:=char(enc);
end;
result:=inherited write(Pmy^,count);
//将pmy指向的内存内容写入文件
finally
freemem(Pmy,count); //释放pmy内存
end;
end;
function TPasswordstream.read(var buffer;count:longint):
longint;
var
Pbu,Pmy,mykey:pchar;
i,mycount,enc:integer;
begin
getmem(Pmy,count);// 为pmy分配内存
mykey:=pchar(keyStr); //将keyStr 转换为pchar指针
try
mycount:=inherited read(Pmy^,count);
//将文件内容读入pmy指向的内存区域
Pbu:=Pchar(@buffer); //将buffer转换为pchar指针
for i:=0 to mycount-1 do
//将buffer的每个字符与keyStr字符串相应的一位字符相减,结果放入pmy指向的内//存区
begin
enc:=(ord(Pmy[i])-ord(mykey
[(i mod length(keyStr))])) mod 256;
Pbu[i]:=chr(enc);
end;
finally
freemem(Pmy,count); //释放pmy内存
end;
result:=mycount;
end;
end.
3.2在单元Unit1中,使用Tpasswordstream对象,并结合OLE复合文档,完成数据加密、解密过程。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, DB, DBClient, Grids, DBGrids,ActiveX,AxCtrls,
DBTables;
type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
ClientDataSet1: TClientDataSet;
ClientDataSet2: TClientDataSet;
DBGrid2: TDBGrid;
DataSource2: TDataSource;
Table1: TTable;
procedure BitBtn1Click(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses unit2; //引用unit2单元
{$R *.dfm}
procedure TForm1.BitBtn1Click(Sender: TObject);
var
Hre:HResult;
RootStorage,SubStorage:IStorage ;
Istr1:IStream;
OleStream1:TPasswordstream;
begin
Hre:=StgCreateDocfile('MyOleDoc.ole',STGM_CREATE or STGM_READWRITE or STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,RootStorage); //建立一个名为
// MyOleDoc.ole复合文档
if not SUCCEEDED(Hre) then Application.Terminate;
Hre:=RootStorage.CreateStorage('Database',STGM_CREATE or STGM_READWRITE or STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,0,SubStorage); //建立一个名为
// Database的存储
if not SUCCEEDED(Hre) then Application.Terminate;
Hre:=SubStorage.CreateStream('employee',STGM_CREATE or STGM_READWRITE or STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,0,Istr1); //建立一个名为employee
//的流
if not SUCCEEDED(Hre) then Application.Terminate;
ClientDataSet1.Active:=True;
OleStream1:=TPasswordstream.Create(Istr1);//建立Tpasswordstream对象
OleStream1.keystr:='FoShan';//设置加密字符串为"FoShan"
ClientDataSet1.SaveToStream(OleStream1); //调用Tpasswordstream对象的write
//函数把加密后的employee数据表写入到MyOleDoc.ole 复合文档的 Database 存储//下,并以 名为//employee的流进行保存
OleStream1.Free;
end;
procedure TForm1.BitBtn2Click(Sender: TObject);
var
Hre:HResult;
RootStorage,SubStorage:IStorage ;
Istr1:IStream;
OleStream1:TPasswordstream;
begin
ClientDataSet2.Active:=False;
Hre:=StgOpenStorage('MyOleDoc.ole',nil, STGM_READWRITE or STGM_DIRECT or STGM_SHARE_EXCLUSIVE,nil,0,RootStorage); //打开一个名为MyOleDoc.ole复合文档
if not SUCCEEDED(Hre) then Application.Terminate;
Hre:=RootStorage.OpenStorage('Database',nil,STGM_READWRITE or STGM_DIRECT or STGM_SHARE_EXCLUSIVE,nil,0,SubStorage); //打开一个名为Database的存储
if not SUCCEEDED(Hre) then Application.Terminate;;
Hre:=SubStorage.OpenStream('employee',nil,STGM_READWRITE or STGM_DIRECT or STGM_SHARE_EXCLUSIVE,0,Istr1); //打开一个名为employee的流
OleStream1:=TOleStream.Create(Istr1);
if not SUCCEEDED(Hre) then Application.Terminate;
OleStream1:=TPasswordstream.Create(Istr1); //建立Tpasswordstream对象
OleStream1.keystr:='FoShan'; //设置解密字符串为"FoShan"
ClientDataSet2.LoadFromStream(OleStream1); //调用Tpasswordstream对象的read
//函数解密
OleStream1.Free;
ClientDataSet2.Active:=True;
end;
end.
四、结束语
本文深入讨论了Delphi环境下数据表以加密流的方式保存到OLE复合文档的全过程,和传统的口令加密方法对比,该方法让用户真正拥有自主的数据安全机制。若要破解该加密方法,不仅要遍历OLE复合文档中的存储和流的名称,而且还要破解加密后的流数据,可见,数据破解的难度是极高的,
参考文献
1.[美]Macro Cantu著 《Delphi高级开发指南》电子工业出版社 1998年1月
2.[美]Eric Harmon著 《Delphi COM深入编程》机械工业出版社 2000年10月
3.郑莉 主编 《C++语言程序设计》 清华大学出版社 2001年7月
4.黄雄波 丘陵《基于Delphi下的OLE结构化存储原理与应用》电脑编程技巧与维护 2003.?(代发表)
5. [美]Charlie Calvert著 《Delphi 4编程技术内幕》机械工业出版社 1999年6 月
|