Einzelnen Beitrag anzeigen

CodeX

Registriert seit: 30. Okt 2004
471 Beiträge
 
Delphi 12 Athens
 
#1

MemIniCrypt: Vollverschlüsseltes Arbeiten mit ini-Dateien - Kommentare erwünscht!

  Alt 17. Jan 2011, 18:38
Delphi-Version: XE
Auf der Suche nach einer Möglichkeit, die ini mit allen Einstellungen vollständig zu verschlüsseln, habe ich leider keine ideale Lösung gefunden. Alles was ich ausprobiert habe, war entweder nicht vollständig nutzbar, nicht Unicode-kompatibel, konnte die Verschlüsselung nicht deaktivieren oder erlaubte keinen mehrfachen Zugriff auf eine ini (z.B. Prozeduraufruf, der auf die ini zugreift, während sie außerhalb bereits bearbeitet wird). Ich habe mich deshalb daran gemacht, eine eigene Lösung zu entwickeln. Herausgekommen ist die Unit MemIniCrypt. Um diese verwenden zu können wird allerdings noch das freie DCPcrypt v2 benötigt. Die Unit unterstützt damit gleichzeitig auch alle Verschlüsselungsmethoden, die dort vorhanden sind. In meiner Implementierung nehme ich RC4, weil das nach meinen Tests am schnellsten arbeitete.
  • Ich würde mich freuen, wenn ein paar Kommentare zur Unit kommen würden. Speziell konkrete Verbesserungsvorschläge (Geschwindigkeitsoptimierung, Code-Patzer, etc.).
  • Außerdem habe ich noch keine Lösung dafür, wie mit falschen Passwörtern umgegangen werden soll, wenn die MemIniCrypt erzeugt wird. Momentan wird der nicht entschlüsselbare Inhalt verworfen. Dies ist für meinen Einsatzzweck irrelevant, da ich sowieso mit einem festen Schlüssel arbeiten werde, was einfach einfach eine direkte Bearbeitung der ini unmöglich machen soll. Wer meint, dass gehört da noch rein und möchte dies umsetzen, werde ich eine vernünftige Lösung gerne mit übernehmen.
  • Was mir definitiv noch fehlt, ist das Einstreuen von Saltz in die Verschlüsselung. Leider war ich mir nicht sicher, wie das optimalerweise umgesetzt werden sollte. Wenn dies jemand beisteuern möchte, würde ich mich sehr freuen!
Zum Testen habe ich ein kleines Programm erstellt, dass alle Hauptfunktionen verwendet und auch ein paar Zeitmessungen durchführt. Generell ist MemIniCrypt nur minimal langsamer als MemIniFile und selbst bei gesetztem Consistent-Parameter (damit jeder Schreibzugriff sofort ausgeführt wird) immer noch deutlich schneller als eine normale IniFile.

Die Unit und die Testdatei befinden sich im Anhang.



Delphi-Quellcode:
// 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.
Miniaturansicht angehängter Grafiken
screenshot.png  
Angehängte Dateien
Dateityp: pas MemIniCrypt.pas (9,5 KB, 51x aufgerufen)
Dateityp: zip MemIniCryptTest.zip (2,5 KB, 59x aufgerufen)
Nur Delphi schafft es, einem ein Lächeln zu schenken, wenn man sich beim Schreiben von := vertippt und stattdessen ein :) erscheint.

Geändert von CodeX (17. Jan 2011 um 22:50 Uhr)
  Mit Zitat antworten Zitat