// MemIniCrypt
// allows working with fully encrypted ini files
//
// By CodeX, v1.0, 2011-01-17
//
// Features:
// - Allows all encryption methods available in DCPcrypt (by default RC4)
// - Fully Unicode (UTF8) compatible
// - Use with or without encryption
// - Allows optional shared/nested ini access (Consistent=true) or as Xzibit would say:
// "Sup Dawg, we heard you like shared ini access, so we put an extra UpdateFile
// into MemIniCrypt, so you can access the ini while you access the ini." ;)
//
// Additional functions compared to TMemIniFile:
// public Encrypt, Decrypt, IsEncrypted
// global IsCorrectPassword, IsIniStructure
//
// Requires a Unicode version of Delphi
// ANSI versions (older than 2009) probably won't work correctly, but were not tested
//
// Requires DCPcrypt v2
// http://www.cityinthesky.co.uk/cryptography.html
// Inspired by RCmxIni
// http://www.delphipraxis.net/303502-post2.html
unit MemIniCrypt;
interface
uses
Classes, sysUtils, IniFiles, DCPRC4, DCPSHA1;
type
TMemIniCrypt =
class(TMemIniFile)
private
FFileName:
String;
FPassword:
String;
FEncrypted: Boolean;
FConsistent: Boolean;
procedure LoadValues;
protected
public
constructor Create(
const FileName, Password:
String;
Consistent: Boolean = true);
procedure WriteString(
const Section, Ident, Value:
String);
procedure UpdateFile;
override;
procedure Rename(
const FileName:
String; Reload: Boolean);
procedure EraseSection(
const Section:
String);
procedure DeleteKey(
const Section, Ident:
String);
procedure SetStrings(List: TStrings; ConsistentAware: Boolean = true);
destructor Destroy;
override;
function Encrypt(Password:
String): Boolean;
function Decrypt: Boolean;
end;
function MICIsCorrectPassword(
const Filename, Password:
String): Boolean;
function MICIsIniStructure(
var List: TStringList): Boolean;
function MICIsEncrypted(
const Filename:
String): Boolean;
implementation
constructor TMemIniCrypt.Create(
const FileName, Password:
String;
Consistent: Boolean = true);
var
bEncrypted : Boolean;
begin
FFileName := FileName;
FPassword := Password;
FEncrypted := Password <> '
';
FConsistent := Consistent;
//allows shared/nested access, but is significantly slower
bEncrypted := MICIsEncrypted(FileName);
if (
not bEncrypted)
and (FPassword <> '
')
then
Encrypt(FPassword)
else if bEncrypted
and (FPassword = '
')
then
begin
// ToDo: How to handle missing passwords for encrypted files
// (or what to do if PW is not suitable for that ini?)
// Attention! Using a wrong PW or no encryption (blank PW)
// will currently erase all existing information!
end;
if FEncrypted
then
begin
// Clean instancing without any values
inherited Create('
');
//inherited Create(FFileName);
end
else
begin
inherited Create(FFileName, TEncoding.UTF8);
Encoding := TEncoding.UTF8;
end;
// Custom LoadValues
LoadValues;
end;
destructor TMemIniCrypt.Destroy;
begin
if not FConsistent
then //Only save to file if not already done
UpdateFile;
FPassword := '
';
FFilename := '
';
inherited;
end;
procedure TMemIniCrypt.WriteString(
const Section, Ident, Value:
String);
begin
inherited;
// Save to file after each change to allow shared/nested ini access
if FConsistent
then
UpdateFile;
end;
procedure TMemIniCrypt.EraseSection(
const Section:
String);
begin
inherited;
if FConsistent
then
UpdateFile;
end;
procedure TMemIniCrypt.DeleteKey(
const Section, Ident:
String);
begin
inherited;
if FConsistent
then
UpdateFile;
end;
procedure TMemIniCrypt.SetStrings(List: TStrings;
ConsistentAware: Boolean = true);
begin
inherited SetStrings(List);
// ConsistantAware is required to not update the file when used by LoadValues
if FConsistent
and ConsistentAware
then
UpdateFile;
end;
procedure TMemIniCrypt.Rename(
const FileName:
String; Reload: Boolean);
begin
FFileName := FileName;
if Reload
then
LoadValues;
end;
procedure TMemIniCrypt.LoadValues;
var
List: TStringList;
Cipher: TDCP_RC4;
fsIn: TFileStream;
fsOut: TMemoryStream;
begin
if not FEncrypted
then
inherited
else
begin
if (FFileName <> '
')
and FileExists(FFileName)
then
begin
List := TStringList.Create;
Cipher := TDCP_RC4.Create(
nil);
Cipher.InitStr(FPassword, TDCP_SHA1);
fsIn := TFileStream.Create(FFileName, fmOpenRead
or fmShareDenyNone);
fsOut := TMemoryStream.Create();
try
fsIn.Seek(0, soFromBeginning);
Cipher.DecryptStream(fsIn, fsOut, fsIn.Size);
fsOut.Seek(0, soFromBeginning);
List.LoadFromStream(fsOut, TEncoding.UTF8);
SetStrings(List, false);
finally
List.Free;
fsIn.Free;
fsOut.Free;
Cipher.Burn;
Cipher.Free;
end;
end
else
Clear;
end;
end;
procedure TMemIniCrypt.UpdateFile;
var
List: TStringList;
Cipher: TDCP_RC4;
fsIn: TMemoryStream;
fsOut: TFileStream;
begin
if not FEncrypted
then
inherited
else
begin
List := TStringList.Create;
Cipher := TDCP_RC4.Create(
nil);
fsOut := TFileStream.Create(FFileName, fmCreate);
fsIn := TMemoryStream.Create;
try
Cipher.InitStr(FPassword, TDCP_SHA1);
GetStrings(List);
List.SaveToStream(fsIn, TEncoding.UTF8);
fsIn.Seek(Length(TEncoding.UTF8.GetPreamble), soFromBeginning);
Cipher.EncryptStream(fsIn, fsOut, fsIn.Size - Length(TEncoding.UTF8.GetPreamble));
finally
List.Free;
fsIn.Free;
fsOut.Free;
Cipher.Burn;
Cipher.Free;
end;
end;
end;
function TMemIniCrypt.Encrypt(Password:
String): Boolean;
var
Cipher: TDCP_RC4;
fsIn: TFileStream;
fsOut: TMemoryStream;
begin
Result := false;
if length(Password) = 0
then
Exit;
if not((FFileName <> '
')
and FileExists(FFileName))
then
Exit;
if MICIsEncrypted(FFileName)
then
Exit;
Cipher := TDCP_RC4.Create(
nil);
fsOut := TMemoryStream.Create;
try
Cipher.InitStr(FPassword, TDCP_SHA1);
fsIn := TFileStream.Create(FFileName, fmOpenRead
or fmShareDenyNone);
try
fsIn.Seek(0, soFromBeginning);
Cipher.EncryptStream(fsIn, fsOut, fsIn.Size);
finally
fsIn.Free;
end;
fsOut.Seek(0, soFromBeginning);
fsOut.SaveToFile(FFileName);
FPassword := Password;
FEncrypted := true;
Result := true;
finally
Cipher.Burn;
Cipher.Free;
fsOut.Free;
end;
end;
function TMemIniCrypt.Decrypt: Boolean;
var
Cipher: TDCP_RC4;
fsIn: TFileStream;
fsOut: TMemoryStream;
List: TStringList;
i: Integer;
begin
Result := false;
if not((FFileName <> '
')
and FileExists(FFileName))
then
Exit;
if not MICIsEncrypted(FFileName)
then
Exit;
Cipher := TDCP_RC4.Create(
nil);
fsOut := TMemoryStream.Create;
try
Cipher.InitStr(FPassword, TDCP_SHA1);
fsIn := TFileStream.Create(FFileName, fmOpenRead
or fmShareDenyNone);
try
fsIn.Seek(0, soFromBeginning);
Cipher.DecryptStream(fsIn, fsOut, fsIn.Size);
finally
fsIn.Free;
end;
List := TStringList.Create;
try
fsOut.Seek(0, soFromBeginning);
List.LoadFromStream(fsOut);
// Only save if the file was encrypted correctly
if MICIsIniStructure(List)
then
fsOut.SaveToFile(FFileName);
finally
List.Free;
end;
FEncrypted := false;
Result := true;
finally
Cipher.Burn;
Cipher.Free;
fsOut.Free;
end;
end;
function MICIsEncrypted(
const Filename:
String): Boolean;
var
fs: TFileStream;
List: TStringList;
begin
Result := false;
if not((Filename <> '
')
and FileExists(Filename))
then
Exit;
List := TStringList.Create;
try
fs := TFileStream.Create(Filename, fmOpenRead
or fmShareDenyNone);
try
fs.Seek(0, soFromBeginning);
List.LoadFromStream(fs);
Result :=
not MICIsIniStructure(List);
finally
fs.Free;
end;
finally
List.Free;
end;
end;
function MICIsCorrectPassword(
const Filename, Password:
String): Boolean;
var
List: TStringList;
Cipher: TDCP_RC4;
fsIn: TFileStream;
fsOut: TMemoryStream;
begin
Result := false;
if not((Filename <> '
')
and FileExists(Filename))
then
Exit;
List := TStringList.Create;
Cipher := TDCP_RC4.Create(
nil);
fsIn := TFileStream.Create(Filename, fmOpenRead
or fmShareDenyNone);
fsOut := TMemoryStream.Create();
try
fsIn.Seek(0, soFromBeginning);
Cipher.InitStr(Password, TDCP_SHA1);
Cipher.DecryptStream(fsIn, fsOut, fsIn.Size);
fsOut.Seek(0, soFromBeginning);
List.LoadFromStream(fsOut);
Result := MICIsIniStructure(List);
finally
List.Free;
fsIn.Free;
fsOut.Free;
Cipher.Burn;
Cipher.Free;
end;
end;
function MICIsIniStructure(
var List: TStringList): Boolean;
var
i: Integer;
begin
if List.Count > 0
then
begin
Result := false;
for i := 0
to List.Count - 1
do
begin
if copy(List[i], 0, 1) + copy(List[i], Length(List[i]), 1) = '
[]'
then
begin
Result := true;
Break;
end;
end;
end;
end;
end.