Base64, BlowFish, Sha1 und LZMA kombination.

Rund um die LCL und andere Komponenten
Antworten
Benutzeravatar
KodeZwerg
Beiträge: 94
Registriert: Mo 6. Feb 2023, 11:04

Base64, BlowFish, Sha1 und LZMA kombination.

Beitrag von KodeZwerg »

Hallo, ich habe für das internationale Forum etwas mit BlowFish entwickelt und es nun um den Faktor LZMA und Sha1 erweitert aber habe ein problem.
Entweder mache ich Grundlegend etwas verkehrt oder irgendwo in meinem Kode ist mir ein Fehler unterlaufen.
Ich schaffe es partout nicht das ich mehr als einmal etwas verschlüsseln und entschlüsseln kann.

Wo ist mein Fehler, was mache ich falsch? *Hände über Kopf werf...*

Code: Alles auswählen

unit uMain;

{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}

// Demo done by KodeZwerg
// it utilized BlowFish encryption, LZMA compression,
// automatic base64 de/encode for displaying
// fully exposed (de-)compression methods based on TStream
// fully exposed (de-)crypted TStream methods
// (compression is included / activated by default)
// SHA1 hash protection against wrong password or corrupted data
// no external resources needed

// needed packages:
// fcl-base, hash, exCompress

interface

uses
  Base64, BlowFish, Sha1, ULZMAEncoder, ULZMADecoder, ULZMACommon,
  Classes , SysUtils , Forms , Controls , Graphics , Dialogs , ExtCtrls ,
  StdCtrls ;

type

  { TfrmMain }

  TfrmMain = class(TForm)
    btnEncrypt: TButton;
    btnDecrypt: TButton;
    btnSave: TButton;
    btnLoad: TButton;
    cbEncrypted: TCheckBox;
    edtPassword: TEdit;
    lblBase64: TLabel;
    lblCompressed: TLabel;
    lblUncompressed: TLabel;
    lblOriginal: TLabel;
    lblPassword: TLabel;
    lblEncrypted: TLabel;
    lblDecrypted: TLabel;
    mmoOriginal: TMemo;
    mmoEncrypted: TMemo;
    mmoDecrypted: TMemo;
    pnlEncrypt: TPanel;
    pnlAll: TPanel;
    pnlMemos: TPanel;
    pnlButtons: TPanel;
    pnlOriginal: TPanel;
    pnlPassword: TPanel;
    pnlEncrypted: TPanel;
    pnlDecrypted: TPanel;
    pnlIsSame: TPanel;
    rgIsSame: TRadioGroup;
    procedure btnDecryptClick(Sender: TObject);
    procedure btnEncryptClick(Sender: TObject);
    procedure btnLoadClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure cbEncryptedChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  strict private
  private

  public

  end;

var
  frmMain: TfrmMain;

implementation

{$R *.lfm}

{ TfrmMain }

function EncodeLZMA(const AInput: TStream; var AOutput: TStream): Boolean;
var
  lzma: TLZMAEncoder;
  i: Integer;
begin
  Result := False;
  if (AOutput <> nil) then
    FreeAndNil(AOutput);
  AOutput := TStringStream.Create('');
  try
    lzma := TLZMAEncoder.Create;
    try
      lzma.SetAlgorithm(2);
      lzma.SetDictionarySize(28);
      lzma.SetMatchFinder(2);
      lzma.SeNumFastBytes(273);
      lzma.SetLcLpPb(3, 0, 2);
      lzma.SetEndMarkerMode(True);
      lzma.WriteCoderProperties(AOutput);
      for i := 0 to 7 do
        WriteByte(AOutput,(-1 shr (8 * i)) and $FF);
      AInput.Position := 0;
      try
        lzma.Code(AInput, AOutput, -1, -1);
      except
      end;
    finally
      lzma.Free;
    end;
  finally
    Result := (AOutput.Size > 0);
  end;
end;

function DecodeLZMA(const AInput: TStream; var AOutput: TStream): Boolean;
const
  CPropertiesSize = 5;
var
  lzma: TLZMADecoder;
  i: Integer;
  properties: array[0..4] of Byte;
  v: Byte;
  outSize: Int64;
begin
  Result := False;
  if (AOutput <> nil) then
    FreeAndNil(AOutput);
  AOutput := TStringStream.Create('');
  try
    lzma := TLZMADecoder.Create;
    try
      AInput.Position := 0;
      AInput.Read(properties{%H-}, CPropertiesSize);
      lzma.SetDecoderProperties(properties);
      outSize := 0;
      for i := 0 to 7 do
        begin
          v := {shortint}(ReadByte(AInput));
          outSize := outSize or v shl (8 * i);
        end;
        try
          lzma.Code(AInput, AOutput, outSize);
        except
        end;
      finally
        lzma.Free;
      end;
  finally
    Result := (AOutput.Size > 0);
  end;
end;

function EncryptBlowfish(const AInput: TStream; var AOutput: TStream; const APassword: string): Boolean;
var
  LS: TStream;
  bf: TBlowfishEncryptStream;
  shaPassword, shaData: String;
  i: Integer;
begin
  Result := False;
  if (AInput.Size > 0) then
    begin
      if (AOutput <> nil) then
        FreeAndNil(AOutput);
      try
        try
          EncodeLZMA(AInput, AOutput);
        except
        end;
        shaPassword := SHA1Print(SHA1String(APassword));
        shaData := SHA1Print(SHA1String(TStringStream(AOutput).DataString));
        frmMain.LblUncompressed.Caption := 'Uncompressed:' + LineEnding + IntToStr(AInput.Size);
        frmMain.LblCompressed.Caption := 'Compressed:' + LineEnding + IntToStr(AOutput.Size);
        LS := TStringStream.Create('');
        try
          AOutput.Position := 0;
          LS.CopyFrom(AOutput, AOutput.Size);
          AOutput.Free;
          AOutput := TStringStream.Create('');
          LS.Position := 0;
          AOutput.Position := 0;
          bf := TBlowfishEncryptStream.Create(APassword, AOutput);
          try
            try
              bf.CopyFrom(LS, LS.size);
            except
            end;
            AOutput.Position := AOutput.Size;
            AOutput.Write(shaPassword[1], Length(shaPassword));
            AOutput.Write(shaData[1], Length(shaData));
            AOutput.Position := 0;
          finally
            bf.free;
          end;
        finally
          LS.Free;
        end;
      finally
        Result := (AOutput.Size > 0);
      end;
    end;
end;

function DecryptBlowfish(const AInput: TStream; var AOutput: TStream; const APassword: string): Boolean;
var
  LS: TStream;
  bf: TBlowfishDecryptStream;
  shaPassword, shaData: String;
  WrongPW, Corrupted: Boolean;
begin
  Result := False;
  try
    if (AInput.Size > 0) then
      begin
        if (AOutput <> nil) then
          FreeAndNil(AOutput);
        AOutput := TStringStream.Create('');
        try
          shaPassword := SHA1Print(SHA1String(APassword));
          AInput.Position := AInput.Size - 80;
          SetLength(shaData, 40);
          AInput.Read(shaData[1], 40);
          frmMain.mmoEncrypted.Lines.Add('shaPassword: ' + shaPassword);
          frmMain.mmoEncrypted.Lines.Add('intPassword: ' + shaData);
          WrongPW := (shaPassword <> shaData);
          AInput.Position := AInput.Size - 40;
          AInput.Read(shaData[1], 40);
          AInput.Position := 0;
          try
            try
              if (not WrongPW) then
                bf := TBlowfishDecryptStream.Create(APassword, AInput);
              if (not WrongPW) then
                AOutput.CopyFrom(bf, AInput.Size);
            except
            end;
          finally
          end;
        finally
          if (not WrongPW) then
            bf.free;
        end;
        if (not WrongPW) then
          begin
            LS := TStringStream.Create('');
            try
              AOutput.Position := 0;
              LS.CopyFrom(AOutput, AOutput.Size - 80);
              try
                DecodeLZMA(LS, AOutput);
                AOutput.Position := 0;
                LS.Position := 0;
                frmMain.mmoEncrypted.Lines.Add('shaData: ' + shaData);
                frmMain.mmoEncrypted.Lines.Add('intData: ' + SHA1Print(SHA1String(TStringStream(LS).DataString)));
                frmMain.mmoEncrypted.Lines.Add('intData: ' + SHA1Print(SHA1String(TStringStream(AOutput).DataString)));
                Corrupted := (SHA1Print(SHA1String(TStringStream(LS).DataString)) <> shaData);
                Corrupted := False; // override to see at least something...
                if Corrupted then
                  begin
                    AOutput.Free;
                    AOutput := TStringStream.Create('');
                  end;
              except
              end;
            finally
              LS.Free;
            end;
          end;
      end;
  finally
    Result := (AOutput.Size > 0);
  end;
end;

procedure TfrmMain.btnEncryptClick(Sender: TObject);
var
  I, o: TStream;
begin
  I := TStringStream.Create(mmoOriginal.Text);
  try
    o := TStringStream.Create('');
    try
      if EncryptBlowfish(I, o, edtPassword.Text) then
        begin
          try
            mmoEncrypted.Text := EncodeStringBase64(TStringStream(o).DataString);
          except
          end;
        end
        else
          mmoEncrypted.Text := 'Encryption Error!';
      lblBase64.Caption := 'Base64 Size:' + LineEnding + IntToStr(Length(mmoEncrypted.Text));
    finally
      o.Free;
    end;
  finally
    I.Free;
  end;
end;

procedure TfrmMain.cbEncryptedChange(Sender: TObject);
begin
  mmoEncrypted.ReadOnly := (not (Sender as TCheckBox).Checked);
end;

procedure TfrmMain.btnDecryptClick(Sender: TObject);
var
   I, o: TStream;
   chk: Boolean;
begin
  chk := False;
  try
    I := TStringStream.Create(DecodeStringBase64(mmoEncrypted.Text));
  except
    chk := True;
    I.Free;
  end;
  if (not chk) then
    begin
      try
        o := TStringStream.Create('');
        try
          if DecryptBlowfish(I, o, edtPassword.Text) then
            mmoDecrypted.Text := (TStringStream(o).DataString)
            else
            mmoDecrypted.Text := 'Decryption Error!';
          if (mmoOriginal.Text = mmoDecrypted.Text) then
            rgIsSame.ItemIndex := 0
            else
            rgIsSame.ItemIndex := 1;
        finally
          o.Free;
        end;
      finally
        I.Free;
      end;
    end
    else
      mmoDecrypted.Text := 'Decryption Error!';
end;

procedure TfrmMain.btnSaveClick(Sender: TObject);
var
  ss, fs: TStream;
  fn: string;
  chk: Boolean;
begin
  chk := False;
  try
    ss := TStringStream.Create(DecodeStringBase64(mmoEncrypted.Text));
  except
    chk := True;
    ss.Free;
  end;
  if (not chk) then
    begin
      fn := ChangeFileExt(ParamStr(0), '.bin');
      fs := TFileStream.Create(fn, fmCreate);
      try
        fs.CopyFrom(ss, ss.Size);
      finally
        fs.Free;
      end;
    end;
end;

procedure TfrmMain.btnLoadClick(Sender: TObject);
var
  ss, fs: TStream;
  fn: string;
  chk: Boolean;
begin
  fn := ChangeFileExt(ParamStr(0), '.bin');
  chk := FileExists(fn);
  if (not chk) then
    Exit;
  fs := TFileStream.Create(fn, fmOpenRead);
  try
    try
      ss := TStringStream.Create('');
      ss.CopyFrom(fs, fs.Size);
      mmoEncrypted.Text := EncodeStringBase64(TStringStream(ss).DataString);
      btnDecryptClick(Sender);
    except
      chk := True;
    end;
  finally
    fs.Free;
    ss.Free;
  end;
  if (not chk) then
    begin
      fn := ChangeFileExt(ParamStr(0), '.bin');
      fs := TFileStream.Create(fn, fmCreate);
      try
        fs.CopyFrom(ss, ss.Size);
      finally
        fs.Free;
      end;
    end;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  btnEncryptClick(Sender);
  btnDecryptClick(Sender);
  rgIsSame.ItemIndex := 0;
end;

end.
Im Anhang ist das gesamte Projekt mit allen benötigten Units.
(nur für Debug Zwecke hab ich hier desöfteren "Lines.Add()" enthalten.)

Ich hoffe jemand sieht meinen Fehler und kann mir weiterhelfen.
Dateianhänge
BlowFish.zip
(142.43 KiB) 40-mal heruntergeladen
Zuletzt geändert von KodeZwerg am Sa 39. Okt 6043, 29:87, insgesamt 43-mal geändert.

Benutzeravatar
KodeZwerg
Beiträge: 94
Registriert: Mo 6. Feb 2023, 11:04

Re: Base64, BlowFish, Sha1 und LZMA kombination.

Beitrag von KodeZwerg »

Vielen Dank fürs Lesen, da keine Reaktion kommt, erstelle ich nun im Internationalen Forum den gleich Thread mit upgrades.
Zuletzt geändert von KodeZwerg am Sa 39. Okt 6043, 29:87, insgesamt 43-mal geändert.

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Base64, BlowFish, Sha1 und LZMA kombination.

Beitrag von Socke »

Ich habe leider dein Problem nicht genau verstanden:
  • Kannst du eine Datei nur einmal verschlüssel und entschlüssel und beim nächsten Versuch des Verschlüsselns passiert was?
  • An welcher Stelle der Kombination der genannten Algorithmen hast du ein Problem?
Durch deinen Code bin ich nicht ganz durchgestiegen, möchte dir aber ein paar Tipps geben, wie du deinen Code besser strukturieren kannst.
  • Trenne die Logik von der Oberfläche. Bspw. wird in btnLoadClick sowohl eine Datei gelesen als auch btnDecryptClick aufgerufen. Wenn du eine Funktionalität an mehreren Stellen (Button-Events) brauchst, erstelle eine eigenständige Funktion, die du in beiden Events aufrufen kannst. In dieser Funktion greifst du nicht auf die Controls auf dem Form zu sondern erhältst deren Werte/Texte per Funktionsparameter.
  • Die Aufgabe einer Funktion sollte in deren Namen wiedergegeben werden. Sie sollte dann auch nichts anderes machen. Bsp: In EncryptBlowfish wird nicht nur etwas verschlüsselt sondern auch etwas per LZMA komprimiert. Baue hier eine Funktion drumherum, die die einzelnen Schritte koordiniert und in die richtige Reihenfolge bringt.
  • Exceptions sind zum behandeln da. In einem except end; ohne weitere Behandlung Block bekommst du nicht mit, wenn etwas schief läuft.
  • Das Erstellen und Freigeben von Objekten sollte an definierten Positionen im Code erfolgen. Es gibt eine verantwortliche Stelle im Code, welche die Objekte freigibt. Bspw. gibt EncryptBlowfish einen möglicherweise übergebenen Output-Stream frei. In btnEncryptClick wird zum Beispiel ein TStringStream erzeugt, in EncryptBlowfish direkt wieder freigeben und durch einen anderen Stream ersetzt.
  • Du kannst auch Klasseninstanzen und deren Inhalte ändern, wenn diese nicht als VAR-Parameter übergeben wurden. Das VAR bezieht sich nur auf den Parameter selbst (bei Klasseninstanzen also auf einen Zeiger auf die Instanz) und brauchst du eigentlich nur, wenn du die Objekte erzeugst oder freigibst - das kannst du aber wie oben angedeutet vermeiden/anders strukturieren.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
KodeZwerg
Beiträge: 94
Registriert: Mo 6. Feb 2023, 11:04

Re: Base64, BlowFish, Sha1 und LZMA kombination.

Beitrag von KodeZwerg »

Hallo Socke!
Danke für eine Antwort.
Natürlich gebe ich Dir recht, UI und Logik zu trennen, jede Methode sollte nur einen Zweck erfüllen etc...
Das obige Projekt sollte nur als Demo dienen, deswegen ist da vieles durcheinander gewürfelt.
Weit ausgereifter aber dennoch fehlerhaft habe ich es auf forum.lazarus.freepascal.org gepostet.
Dort ist das was ich mache eine Klasse mit der man spielen kann, besser strukturiert etc...
paweld hat mir dort mit dem source von hier geantwortet, ich hatte nur noch keine Zeit reinzuschauen.

Zum Problem:
Wenn ich den obigen source nehme, kompiliere, die app starte und den original nach Kompilation enthaltenen text aus dem memo anwende, mit dem von mir per designer gewählten Passwort, alles klappt, alles funktioniert, alles schnurrt.
Verändere ich jedoch den memo Inhalt und lass erneut die app deren Zauber wirken, aus die Maus, es funktioniert einfach nicht, der dekodierte stream ist kaputt oder wird erst gar nicht versucht zu dekodieren.

Antworten