Thema: Delphi DEC 5.1 wie benutzen?

Einzelnen Beitrag anzeigen

Benutzerbild von negaH
negaH

Registriert seit: 25. Jun 2003
Ort: Thüringen
2.950 Beiträge
 
#15

Re: DEC 5.1 wie benutzen?

  Alt 3. Feb 2008, 17:48
Aus gegebenen Anlaß

Luckie's Unit ist ein Beispiel dafür wie man die Funktionalität vom DEC in eine Klasse kapseln kann.
DEC selber ist nur eine Zusammenstellung von vorgefertigten kryptographischen Verfahren. Mehr nicht, aber auch nicht weniger, da ich hoffe das die Qualität des DECs gerade im Source und seinen Schnittstellen sehr hoch ist.
DEC muß also nicht so wie üblich wie eine Komponente installiert werden, sondern es reicht einfach die notwendigen Units einzubinden und den Pfad auf die Source oder kompilierten Units oder Packages zu legen.

Bei der Entwicklung einer kryptographischen Bibliothek hat der Programmierer ganz besondere Anforderungen, im Gegensatz zu normalen Bibliotheken. Einige dieser Anforderungen widersprechen sich, zb. möglichst viel zu unterstützen und damit eine hohe Komplexität im Gegensatz zur kryptrographischen Sicherheit und damit möglichst anwendungssicheren und somit einfach gestrickten und denoch flexiblen Schnittstellen. Nun DEC versucht eben nicht alles zu können sondern beschränkt sich auf eine klare und sichere Schnittstelle. Daraus ergibt sich das ein Nutzer vom DEC noch einiges drumherum basteln muß und nichts im DEC quasi readymade ist.

Soweit zum Vorwort Kommen wir nun zur konkreten Aufgabe eine Datei zu verschlüsseln.

Der einfache aber unflexible und nicht ganz kryptographisch sichere Weg

Delphi-Quellcode:
procedure EncryptFile(const ASourceFileName, ADestFileName, APassword: String);
begin
  with TCipher_Rijndael.Create do
  try
    Init(APassword);
    EncodeFile(ASourceFileName, ADestFileName);
  finally
    Free;
  end;
end;
fertig. Bei diesem Source fällt folgendes auf:

- Einfachheit
- Unflexibel da der verwendete Cipheralgo. usw. nicht konfigurierbar ist
- Verfahrensunsicher da zb. nicht klar ist was passieren würde wenn ASourceFileName und ADestFileName identisch sind
- kryptographisch nicht so besonderst sicher da das APassword direkt im Cipher.Init benutzt wird und somit einige effiziente Angriffe möglich sind

Also müssen bzw. können wir das verbessern, fragt sich nur womit man am besten anfängt. Wir wollen folgendes abändern

1.) Konfigurierbarkeit
Das ist eine Forderung nach kryptographischer Sicherheit denn wenn in der Zukunft bekannt werden sollte das AES-Rijndael als Verfahren geknackt worden sein sollte dann müssen wir möglichst schnell unseren Source abändern. Sowas macht man zB. über globale Konstanten.

2.) krypto. Sicherheit inkrementieren
Wir wissen das es heute schon effiziente Angriffe auf direkt benutzte Paswörter gibt, zb. Rainbow Tabellen. Obiger Source ist dagegen anfällig also müssen wir das abändern. Nun taucht das erste wirkliche Problem auf. Wie ändern wir das ab wenn es dafür keine anerkannten Standards gibt ? Klar, wir müssen einen prohibitären Weg einschlagen, besonders bei der Frage der Datenformate usw. Wir können aber unsere benutzten Algorithmen dabei an empfohlenen Vorgehensweisen ausrichten. Diese Diskrepanz entsteht immer wenn man anfängt pure krypto. Verfahren in krypto. Protokollen anzuwenden. Es gibt PKCS, PGP, IEEE, NIST, AES, ASN1 usw. usw. Standards und alle machen es anders in ihren Datenformaten. Allen liegen zwar die gleichen mathmatisch kryptographsich orientierten Forderungen zugrunde aber was hinten rauskommt ist absolut unterschiedlich. DEC hatte sich mal vorgenommen die wichtigsten dieser Standards auch umzusetzen aber wie jeder sich denken kann ist das ne Menge Holz an Arbeit. Im Besonderen wenn man bedenkt das dies meistens kommerziell orientierte Standards sind und man sich defakto damit in seiner Bibliothek dem Goodwill dieser Konzerne aussetzt. Ergo, die Entscheidung ist die das der Endbenutzer des DECs sich selber entscheiden muß. Das heist aber auch das in der korrekten Benutzung vom DEC auch der Benutzer dieser Bibliothek Ahnung von Kryptographie haben muß. Naja meiner Meinung nach sollte jeder der Kryptographie benutzt auch das notwendige Wissen besitzen, denn nur das ist echte Sicherheit.
Zurück zum Thema: wir erhöhen die Sicherheit des APassword enorm wenn wir dazu eine randomisierte KDF benutzen. KDF bedeutet Key Derivation Function und übersetzt Schlüsselableitungsfunktion (ein schönes deutsches Wort das es exakt auf den Punkt bringt )
Eine KDF erzeugt also auf irgendeine sichere Weise aus einem Schlüssel=APassword einen anderen Schlüssel mit kryptographisch besseren Eigenschaften. Später mehr dazu.

3.) Flexibilität
Wir wissen das man in der VCL eine Datei ver-entschlüsselt über TStream Objekte. Also wäre es sinnvoll gleich damit eine Funktion zu schreiben und dann darauf aufsetztend die Ver/Entschlüsselung von Dateien. Von Haus aus kann das ja das DEC schon aber wir wollen ja auch ansonsten noch Drumherum mehr.

Also fangen wir an mit der TStream basierten Funktion und der Konfigurierbarkeit:

Delphi-Quellcode:
const
  ACipherClass: TDECCipherClass = TCipher_Rijndael;
  ACipherMode: TDECCipherMode = cmCTSx;

procedure Encrypt(ASource, ADest: TStream; const APassword: String);
begin
  Assert(ASource <> ADest, 'Encrypt(), ASource must be distinct to ADest');

  with ValidCipher(ACipherClass).Create do
  try
    Mode := ACipherMode;
    Init(APassword);
    EncodeStream(ASource, ADest, -1);
  finally
    Free;
  end;
end;
Als erstes nutzen wir Assert() um zu prüfen ob die Objekte ADest und ASource unterschiedlich sind. Sowas prüft DEC intern nicht ab da es durchaus möglich sein könnte das es eine Streamklasse geben könnte die inplaced und überlappend auf dem gleichen Datenbereich arbeitet. Ich kenne zwar sowas nicht aber möglich wäre es. In unserem Beispiel möchten wir aber sicherstellen das man nur mit zwei unterschiedlichen Streamobjekten arbeiten kann. Deshalb eine Assertion da es ja ein Programmierfehler ist wenn er auftritt.

Als nächstes erzeugen wir wieder eine Cipherinstanz, aber diesesmal überprüfen wir mit ValidCipher(ACipherClass) ob die übergebene Klassenreferenz eine gültige ist. Man könnte also ACipherClass auf nil initialisieren und ValidCipher() würde die mit DECCipher.SetDefaultCipherClass() global installierte Cipherklasse zurückgeben. Dh. DEC enthält quasi schon eine globale Variable um eine Cipherklasse, Hashklasse und DECFormat Klasse zu registrieren. Wir nutzen dieses Feature hier aber nur indirekt.
Dann initialisieren wir das Cipherobjekt, erstmal den Feedbackmodus und dann das Passwort. Und danach wird der Stream verschlüsselt.

Wir können also in Zukunft sehr schnell unsere Procedure konfigurieren. Man könnte auch alle notwendigen Parameter der Procedure Encrypt() übergeben aber das hat weniger Vorteile als man denkt. Normalerweise entscheidet sich der Programmierer für einen Algorithmus pro Algorithmentyp. Er überlässt also eben nicht die Wahl der Qual dem Anwedner seiner selbst geschaffenen Schnittstellen sondern legt alles fest. Das erhöht dann die Anwendungssicherheit und wir möchten ja das Ziel errichen das wir eine Readymade Schnittstelle haben.

Nun möchten wir aber ua. die Passwordsicherheit inkrementieren indem wir eine KDF benutzen.

Delphi-Quellcode:
const
  ACipherClass: TDECCipherClass = TCipher_Rijndael;
  ACipherMode: TDECCipherMode = cmCTSx;
  AKDFClass: TDECHashClass = THash_SHA1;
  AKDFIndex: Cardinal = 1;
  AKDFSaltSize: Byte = 16;

procedure Encrypt(ASource, ADest: TStream; const APassword: String); overload;
var
  Salt,SessionKey: Binary;
begin
  Assert(ASource <> ADest, 'Encrypt(), ASource must be distinct to ADest');

  with ValidCipher(ACipherClass).Create do
  try
    Salt := RandomBinary(AKDFSaltSize);
    SessionKey := ValidHash(AKDFClass).KDFx(APassword, Salt, Context.MaxKeySize, TFormat_Copy, AKDFIndex);

    Mode := ACipherMode;
    Init(SessionKey);
  
    ADest.WriteBuffer(Salt[1], Length(Salt));

    EncodeStream(ASource, ADest, -1);
  finally
    Free;

    ProtectBinary(SessionKey);
    ProtectBinary(Salt);
  end;
end;
Wir erzeugen einen Zufallssalt "Salt" der 16 Bytes groß ist. Das ist also ein 16 Bytes großer Speicherbereich der mit Zufallsdaten befüllt wird. Nun wird mit Hilfe einer KDF aus diesem Salt und dem APassword ein Cipher.Context.MaxKeySize langer Wert berechnet. Dieser Wert ist auf Grund des Salt pseudozufällig und hat exakt die Größe an Bytes die nötig ist um die Sicherheit des Cipher's auszunutzen. Zb. beim AES Rijndael gibt es eine 128, 196 und 256 Bit Verschlüsselungsstärke. In unserem Beispiel nutzen wir also immer die 256 Bit Verschlüsselung des AES aus.
Auf Grund der Hashfunktion, das ist eine kryptographisch sichere Einwegfunktion, kann man nicht so ohne weiteres vom so erzeugten Sessionkey auf das Passwort zurückrechnen. Aber viel wichtiger ist eben die Eigenschaft das wenn wir die exakt gleichen Daten im Stream mit dem exakt gleichen Passwort erneut verschlüsseln wir jedesmal einen anderen Salt benutzen und somit auch jedesmal einen anderen Sessionkey erzeugen. Somit wird in ADest immer ein anderes Produkt der Verschlüsselung entstehen. Wir haben quasi über die Schlüsselableitung per zufälligem Salt einen psuedozufälligen Sessionkey erzeugt und somit eine pseudozufällige Verschlüsselung der Daten. Zu jedem der möglichen Passwörter wird es also 2^128, da 16 Bytes Salt = 128 Bit sind, verschiedene Salts und somit minimal 2^128 mögliche Sessionkeys zu einem spezifischen Passwort.
Eine Wörterbuchattacke erzeugt offline zu jedem der möglichen Passwörter die benutzten Sessionkeys. Wenn aber die KDF bei ihrer Berechnung einen 128 Bit Salt benutzt so heist dies das man nun statt nur einem Wörterbuch nun 2^128-1 mal mehr Wörterbücher erzeugen müsste. Das ist praktisch gesehen bisher unmöglich. Ergo: über diese Methode haben wir zusätzlichen kryptographsichen Schutz eingebaut und die Gesamtsicherheit des Systemes erhöht.
Der AKDFIndex ist eine Zahl mit der man die Berechnung des Sessionkeys applikationsbezogen serialisieren kann. Mit einem anderen AKDFIndex wird man also auch andere Sessionkeys produzieren.

Da der Salt zufällig gewählt wurde müssen wir diesen nun auch für die notwenige Entschlüsslung abspeichern. Also schreiben wir diesen Salt in unseren Destinationstream. Das führt zu einer Expansion der Datenmenge, dh. in ADest sind später mehr Bytes gespeichert als in ASource, hier konkret 16 Bytes == AKDFSaltSize.

Am Ende der Procedure zerstören wir den Inhalt von Salt und Sessionkey im Speicher. Das erhöht ein bischen die Anwendungssicherheit und schützt vor offline Spionagesoftware die zb. das SwapFile oder die Harddisk des Betriebsystemes auslesen kann.

Nun bauen wir drumherum die Schnittstelle um Dateien zu verschlüsseln.

Delphi-Quellcode:
procedure Encrypt(const ASourceFileName, ADestFileName, APassword: String); overload;
var
  ASource, ADest: TStream;
  ATempFileName: String;
begin
  ATempFileName := ChangeFileExt(ADestFileName, '.$$$');
  try
    ASource := TFileStream.Create(ASourceFileName, fmOpenRead or fmShareDenyNone);
    try
      ADest := TFileStream.Creare(ATempFileName, fmCreate);
      try
        Encrypt(ASource, ADest, APassword);
      finally
        ADest.Free;
      end;
    finally
      ASource.Free;
    end;
    DeleteFile(ADestFileName);
    RenameFile(ATempFileName, ADestFileName);
  except
    DeleteFile(ATempFileName);
    raise;
  end;
end;

Es hat sich bewährt bei der Verschlüsselung von Dateien erstmal die Originaldatei in eine temporäre Datei zu verschlüsseln und im Anschluß die temporär angelegte Datei einfach umzubennen. So erlangt man wiederum eine höhere Anwendungssicherheit da nun zb. bei einer Exception keine Datei unabsichtlich zerstört wird. Man kann auch ASourceFileName identisch zu ADestFileName festlegen.

Die Procedure DeleteFile() sollte noch durch eine Procedure wie SecureDeleteFile() ersetzt werden. Diese Procedure macht das gleiche wie DeleteFile() nur mit dem Unterschied das sie vor dem Löschen noch mehrmals den Inhalt der Datei überschreibt. Diesen Vorgang nennt man Wipe. Allerdings ist es auf den meisten Betriebsystemen utopisch anzunehmen das man damit wirklich alle Daten der Dateien gelöscht hätte.

Delphi-Quellcode:
procedure SecureDeleteFile(const AFileName: String);
var
  Stream: TStream;
begin
  if FileExists(AFileName) then
  begin
    Stream := TFileStream.Create(AFileName, fmOpenReadWrite or fmShareDeny);
    try
      ProtectStream(Stream);
    finally
      Stream.Free;
    end;
    DeleteFile(AFileName);
  end;
end;
Nun möchten wir noch die Flexibilität im fertigen GUI verbessern. Dazu wäre es hilfreich wenn wir zb. eine Progressbar im GUI hätten damit der Anwender sieht wie's vorwärtsgeht. Auch möchten wir zb. das der Anwender die Verschlüsselung abbrechen kann.
Dazu stellt DEC schon eine relativ komfortable Möglichkeit zur Verfügung, das Interface IDECProgress.
Alle Methoden von DEC Klassen die zb. mit Hilfe von TStream's arbeiten unterstützen dieses Interface. Wir ändern erstmal unsere beiden Encrypt() Procedure dingehend ab.

Delphi-Quellcode:
procedure Encrypt(ASource, ADest: TStream; const APassword: String; const AProgress: IDECProgress = nil); overload;
var
  Salt,SessionKey: Binary;
begin
  Assert(ASource <> ADest, 'Encrypt(), ASource must be distinct to ADest');

  with ValidCipher(ACipherClass).Create do
  try
    Salt := RandomBinary(AKDFSaltSize);
    SessionKey := ValidHash(AKDFClass).KDFx(APassword, Salt, Context.MaxKeySize, TFormat_Copy, AKDFIndex);

    Mode := ACipherMode;
    Init(SessionKey);
  
    ADest.WriteBuffer(Salt[1], Length(Salt));

    EncodeStream(ASource, ADest, -1, AProgress);
  finally
    Free;

    ProtectBinary(SessionKey);
    ProtectBinary(Salt);
  end;
end;

procedure Encrypt(const ASourceFileName, ADestFileName, APassword: String; const AProgress: IDECProgress = nil); overload;
var
  ASource, ADest: TStream;
  ATempFileName: String;
begin
  ATempFileName := ChangeFileExt(ADestFileName, '.$$$');
  try
    ASource := TFileStream.Create(ASourceFileName, fmOpenRead or fmShareDenyNone);
    try
      ADest := TFileStream.Creare(ATempFileName, fmCreate);
      try
        Encrypt(ASource, ADest, APassword, AProgress);
      finally
        ADest.Free;
      end;
    finally
      ASource.Free;
    end;
    DeleteFile(ADestFileName);
    RenameFile(ATempFileName, ADestFileName);
  except
    DeleteFile(ATempFileName);
    raise;
  end;
end;

Fertig. Nun wenden wir das mal in einem TForm an.

Delphi-Quellcode:
type
  TForm1 = class(TForm, IDECProgress)
    Button1: TButton;
    Progressbar1: TProgressbar;

    procedure Button1Click(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    FInAction: Integer;
    FNextTick: Cardinal;
    procedure Process(const Min,Max,Pos: Int64); stdcall;
  end;

procedure TForm1.Process(const Min,Max,Pos: Int64); stdcall;
begin
  if GetTickCount >= FNextTick then
  begin
    FNextTick := GetTickCount + 100;
    Application.ProcessMessages;
    ProgressBar1.Position := Round((Max - Min) / 100 * (Pos - Min));
    ProgressBar1.Update;
    if FInAction > 1 then
      Abort;
  end;
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := FInAction = 0;
  if not CanClose and (MessageBox('Wollen Sie die aktuelle Aktion abbrechen ?') = mrYes) then
    Inc(FInAction);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if FInAction = 0 then
  try
    FInAction := 1;
    Button1.Caption := 'Abort';
    // Button2.Enabled := False; // hier alle Bedienelemente deaktivieren die nicht bedienbar sein dürfen
    FNextTick := 0;
    try
      Encrypt(GetSourceFileName, GetDestFileName, GetPassword, Self);
    except
      on E: EAbort do ShowMessge('Abbruch durch Benutzer')
       else raise;
    end;
  finally
    FinAction := 0;
    Button1.Caption := 'Encrypt';
    // Button2.Enabled := True;
  end else
    Inc(FInAction);
end;
Das ist mal ein stark abstrahiertes Beispiel. Auf dem TForm1 sitzt ein Button1 mit Caption := 'Encrypt'.
Die Funktionen GetSourceFileName, GetDestFileName und GetPassword müssen von euch noch implementiert werden.

Drückt der Anwender nun Button1 das erste mal so ist FInAction == 0, wir verzweigen also in den try finally Block oben im Source. Nun setzen wir FinAction auf 1 und zeigen somit an das wir gerade ver-entschlüsseln. Zusätzlich setzen wir die .Caption von Button1 auf 'Abort' und fangen an eine Datei zu verschlüsseln. Dabei übergeben wir Self als IDECProgress Interface, was dazu führt das die Methode TForm1.Process() periodisch duch die internen DEC Funktionen aufgerufen wird. In TForm1.Process() rufen wir Application.ProcessMessages; auf und somit friert das GUI der Anwendung nicht mehr ein. Danach updaten wir die Progressbar1 und prüfen ob FInAction > 1 ist. Wenn das der Fall ist muß der Anwender in der Zischenzeit erneut den Button1 gedrückt haben oder er hat versucht das TForm1 zu schließen. Denn nur in diesem beiden Methoden inkrementieren wir FInAction falls es > 0 ist. Wir lösen dann einfach per Abort; eine stille Exception aus. Diese EAbort Exception unterbricht dann die internen DEC Verschlüsselungsmethoden, da ja aus diesen heraus erst die Methode TForm1.Process() aufgerufen wurde. Natürlich fangen wir diese Exception in unserer zu äusserst liegenden Methode, hier .Button1Click(), gezielt ab.

Diese Methode hat den Vorteil das das GUI nicht mehr einfrieren kann, das der Benutzer einen Fortschrittsbalken sieht, das er die Aktion sauber abbrechen kann und wir benötigen keine TThread's, keine Synchronisation usw. Der Nachteil besteht darin das der Programmierer sehr sehr genau darauf achten muß welche Bedienelemente er aktiviert bzw. deaktiviert. Meistens wird man alle Steuerelemente deaktivieren in .Button1Click() und nur das TForm1 selber und den Button1 der zum Abbruch dient aktiviert lassen. Das ist wichtig !

Nochwas zu FNextTick. Es kann nun vorkommen das TForm1.Process() sehr sehr schnell nacheinander aufgerufen wird. Das führt dann dazu das durch den Aufruf von Application.ProcessMessages; und das Updatiung der ProgressBar1 die Gesamtperformance stark reduziert. Wir verhidnern dies indem wir über FNextTick und GetTickCount() nur alle minimal 100 Milisekunden diese Aktionen durchführen, also ca. 10 mal pro Sekunde. Das ist für einen Menschen ausreichend, sowohl in der Visualisierung des Fortschrittsbalken wie auch in der
Interaktion des Benutzers über die Steuerelemente.

Möchte man ohne diese Möglichkeit der Progressbar arbeiten so ruft man Encrypt() einfach ohne den letzten Parameter Self auf. Durch unseren Defaultparameter ist dieser dann == nil und intern wird ohne dieses Callback-Interface gearbeitet.

Man muß auch nicht das IDECProgress Interface direkt im TForm1 implementieren und könnte eine eigene Klasse oder Komponente dafür schreiben. Allerdings erachte ich das bei diesem geringen Aufwand und dem direkten logischen Zusammenhang zwischen IDECProgress zum GUI einer Applikation für weniger geschickt, bzw. oversized. Man muß halt immer abwägen. Jede Kapselung solcher Funktionen zb. in eigene Klassen/Komponenten stellt eine Erhöhung der Komplexität dar und macht meistns reingarnichts wirklich besser. Somit reuziert sich die Gesamtsicherheit da man mehr Fehler reinbaut, der Anwender keinen Vorteil erlangt usw. Die rein prozedurale Lösung ist noch die geschickteste in diesem Fall.

Nun nochmal alles als Unit zusammengefasst:

Delphi-Quellcode:
unit MyCrypt;

interface

uses Classes, DECUtil;

procedure Encrypt(ASource, ADest: TStream; const APassword: String; const AProgress: IDECProgress = nil); overload;
procedure Decrypt(ASource, ADest: TStream; const APassword: String; const AProgress: IDECProgress = nil); overload;

procedure Encrypt(const ASourceFileName, ADestFileName, APassword: String; const AProgress: IDECProgress = nil); overload;
procedure Decrypt(const ASourceFileName, ADestFileName, APassword: String; const AProgress: IDECProgress = nil); overload;

procedure SecureDeleteFile(const AFileName: String);

implementation

uses Windows, DECRandom, DECCipher, DECHash, DECFmt;

const
  ACipherClass: TDECCipherClass = TCipher_Rijndael;
  ACipherMode: TDECCipherMode = cmCTSx;
  AKDFClass: TDECHashClass = THash_SHA1;
  AKDFIndex: Cardinal = 1;
  AKDFSaltSize: Byte = 16;

procedure Encrypt(ASource, ADest: TStream; const APassword: String; const AProgress: IDECProgress = nil); overload;
var
  Salt,SessionKey: Binary;
begin
  Assert(ASource <> ADest, 'Encrypt(), ASource must be distinct to ADest');

  with ValidCipher(ACipherClass).Create do
  try
    Salt := RandomBinary(AKDFSaltSize);
    SessionKey := ValidHash(AKDFClass).KDFx(APassword, Salt, Context.MaxKeySize, TFormat_Copy, AKDFIndex);

    Mode := ACipherMode;
    Init(SessionKey);
  
    ADest.WriteBuffer(Salt[1], Length(Salt));

    EncodeStream(ASource, ADest, -1, AProgress);
  finally
    Free;

    ProtectBinary(SessionKey);
    ProtectBinary(Salt);
  end;
end;

procedure Decrypt(ASource, ADest: TStream; const APassword: String; const AProgress: IDECProgress = nil); overload;
var
  Salt,SessionKey: Binary;
begin
  Assert(ASource <> ADest, 'Decrypt(), ASource must be distinct to ADest');

  with ValidCipher(ACipherClass).Create do
  try
    SetLength(Salt, AKDFSaltSize);
    ASource.ReadBuffer(Salt[1], Length(Salt));
 
    SessionKey := ValidHash(AKDFClass).KDFx(APassword, Salt, Context.MaxKeySize, TFormat_Copy, AKDFIndex);

    Mode := ACipherMode;
    Init(SessionKey);
  
    DecodeStream(ASource, ADest, -1, AProgress);
  finally
    Free;

    ProtectBinary(SessionKey);
    ProtectBinary(Salt);
  end;
end;

procedure Encrypt(const ASourceFileName, ADestFileName, APassword: String; const AProgress: IDECProgress = nil); overload;
var
  ASource, ADest: TStream;
  ATempFileName: String;
begin
  ATempFileName := ChangeFileExt(ADestFileName, '.$$$');
  try
    ASource := TFileStream.Create(ASourceFileName, fmOpenRead or fmShareDenyNone);
    try
      ADest := TFileStream.Creare(ATempFileName, fmCreate);
      try
        Encrypt(ASource, ADest, APassword, AProgress);
      finally
        ADest.Free;
      end;
    finally
      ASource.Free;
    end;
    SecureDeleteFile(ADestFileName);
    RenameFile(ATempFileName, ADestFileName);
  except
    SecureDeleteFile(ATempFileName);
    raise;
  end;
end;

procedure Decrypt(const ASourceFileName, ADestFileName, APassword: String; const AProgress: IDECProgress = nil); overload;
var
  ASource, ADest: TStream;
  ATempFileName: String;
begin
  ATempFileName := ChangeFileExt(ADestFileName, '.$$$');
  try
    ASource := TFileStream.Create(ASourceFileName, fmOpenRead or fmShareDenyNone);
    try
      ADest := TFileStream.Creare(ATempFileName, fmCreate);
      try
        Decrypt(ASource, ADest, APassword, AProgress);
      finally
        ADest.Free;
      end;
    finally
      ASource.Free;
    end;
    SecureDeleteFile(ADestFileName);
    RenameFile(ATempFileName, ADestFileName);
  except
    SecureDeleteFile(ATempFileName);
    raise;
  end;
end;

procedure SecureDeleteFile(const AFileName: String);
var
  Stream: TStream;
begin
  if FileExists(AFileName) then
  begin
    Stream := TFileStream.Create(AFileName, fmOpenReadWrite or fmShareDeny);
    try
      ProtectStream(Stream);
    finally
      Stream.Free;
    end;
    DeleteFile(AFileName);
  end;
end;

initialization
  RandomSeed;
finalization
end.
Wir binden zusätzlich noch die Unit DECRandom mit ein um einen kryptographisch sicheren Zufallsgenerator zu benutzen. Dieser wird mit RandomSeed; so wie Borland's Randomize; beim Starten der Applikation zufällig initialisiert. Diese Initialisierung erfolgt per default mit der aktuellen Systemzeit und PerformanceCounter(). Dies ist eine Archillesferse des Ganzen und wenn man es sehr sicher haben möchte so muß man dies ändern. Im Allgmeinen ist es aber so das die Gesamtsicherheit erstmal immer vom gewählten Passwort abhängt und dies ist bei menschlich gewählten Passwörtern eh viel geringer als die Sicherheit der angewendeten Algorithmen.

Die Decyrpt() Procedures sind das Pendant zu ihren Encrypt() Procedures.

Man kann nun diese Beispiele immer mehr ausbauen. Zb. könnte man in den Streamfunktionen zusätzlich noch Daten über den verwendeten CipherMode, Cipherklasse, KDF Funktionstyp, Länge des verwendeten Salt usw. usw. als Header abspeichern und natürlich entsprechned auch wieder laden in der Decrypt() Procedure. Damit wird das System immer flexibler aber eben auch eventuell störanfälliger oder sogar kryptrographisch unsicherer. Man könnte auch am Ende des verschlüsselten Streams eine prüfsumme der Daten abspeichern damit man bei der Entschlüsselung die Daten und das Passwort auf Richtigkeit überprüfen kann. Für all dies finden sich in der DP aber schon Beispiele.

Gruß Hagen

PS: alle Sourcen habe ich mal eben so eingehackt und sind leider noch nicht real getestet. Sie dienen also erstmal nur als Beispiele.
  Mit Zitat antworten Zitat