Change the Delphi code below to it prepends the first 2 bytes of CRC32 of the SourceText string and appends the last 2 bytes of the CRC32


unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, ORM_Utils, Vcl.StdCtrls;

type
  TMainForm = class(TForm)
    Memo1: TMemo;
    ORM_Test1: TButton;
    ORM_Test2: TButton;
    procedure ORM_Test1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

    const
      SignaturePost = 'S2'; // 2-byte append signature
    // CRC16 function declaration
    function CRC16CCITT(const Data: TBytes): Word;
    // Helper Function: Compare two TBytes arrays
    function TupleEqual(const A, B: TBytes): Boolean;

implementation

uses
  DECHash, DECCipherBase, DECHashBase, DECCiphers, DECUtil, DECFormat,
  DECRandom, DECHashAuthentication, DECCipherModes, DECCipherFormats,
  System.NetEncoding;

{$R *.dfm}

{ TMainForm }

// CRC16-CCITT Implementation in Delphi
function CRC16CCITT(const Data: TBytes): Word;
const
  POLYNOMIAL = $1021;
var
  i, bit: Integer;
  crc: Word;
begin
  crc := $FFFF; // Initial value
  for i := 0 to Length(Data) - 1 do
  begin
    crc := crc xor (Data[i] shl 8);
    for bit := 0 to 7 do
    begin
      if (crc and $8000) <> 0 then
        crc := (crc shl 1) xor POLYNOMIAL
      else
        crc := crc shl 1;
      crc := crc and $FFFF; // Ensure CRC remains 16-bit
    end;
  end;
  Result := crc;
end;

// Helper Function: Compare two TBytes arrays
function TupleEqual(const A, B: TBytes): Boolean;
var
  I: Integer;
begin
  if Length(A) <> Length(B) then
    Exit(False);
  for I := 0 to Length(A) - 1 do
    if A[I] <> B[I] then
      Exit(False);
  Result := True;
end;

// HexToBytes and BytesToHex Functions
function HexToBytes(const Hex: string): TBytes;
var
  I: Integer;
  ByteValue: Byte;
  Temp: string;
  ByteInt: Integer;
begin
  if Length(Hex) mod 2 <> 0 then
    raise Exception.Create('Invalid hex string length');

  SetLength(Result, Length(Hex) div 2);

  for I := 0 to Length(Result) - 1 do
  begin
    Temp := Copy(Hex, 2 * I + 1, 2);
    if not TryStrToInt('$' + Temp, ByteInt) then
      raise Exception.CreateFmt('Invalid hex character at position %d', [2 * I + 1]);
    if (ByteInt < 0) or (ByteInt > 255) then
      raise Exception.CreateFmt('Hex value out of byte range at position %d', [2 * I + 1]);
    ByteValue := Byte(ByteInt);
    Result[I] := ByteValue;
  end;
end;

function BytesToHex(const Bytes: TBytes): string;
const
  HexChars: array[0..15] of Char = '0123456789ABCDEF';
var
  I: Integer;
begin
  SetLength(Result, Length(Bytes) * 2);
  for I := 0 to Length(Bytes) - 1 do
  begin
    Result[I * 2 + 1] := HexChars[Bytes[I] shr 4];
    Result[I * 2 + 2] := HexChars[Bytes[I] and $0F];
  end;
end;

// Updated Base64ToBytes Function
function Base64ToBytes(const Base64: string): TBytes;
var
  Encoder: TBase64Encoding;
begin
  Encoder := TBase64Encoding.Create(0); // '0' specifies no line breaks
  try
    Result := Encoder.DecodeStringToBytes(Base64);
  finally
    Encoder.Free;
  end;
end;

// Updated BytesToBase64 Function
function BytesToBase64(const Bytes: TBytes): string;
var
  Encoder: TBase64Encoding;
begin
  Encoder := TBase64Encoding.Create(0); // '0' specifies no line breaks
  try
    Result := Encoder.EncodeBytesToString(Bytes);
  finally
    Encoder.Free;
  end;
end;

// Helper Function: Convert RawByteString to TBytes
function RawByteStringToBytes(const Raw: RawByteString): TBytes;
begin
  SetLength(Result, Length(Raw));
  if Length(Raw) > 0 then
    Move(Raw[1], Result[0], Length(Raw));
end;

// Helper Function: Convert TBytes to RawByteString
function BytesToRawByteString(const Bytes: TBytes): RawByteString;
begin
  SetLength(Result, Length(Bytes));
  if Length(Bytes) > 0 then
    Move(Bytes[0], Result[1], Length(Bytes));
end;

// Encrypt Function with CRC16 and SignaturePost
function EncryptString(const SourceText, Password: string): string;
var
  Cipher: TCipher_Rijndael;
  CipherKeyBytes, KeyKDF, IVBytes, PlainBytes, Ciphertext, SeedBytes, CombinedOutput: TBytes;
  CRC16: Word;
  SignaturePostBytes: TBytes;
  EncryptedBase64: string;
begin
  Cipher := TCipher_Rijndael.Create;
  try
    Cipher.Mode := cmCBCx;

    // Convert Password to bytes
    CipherKeyBytes := TEncoding.UTF8.GetBytes(Password);

    // Generate a 16-byte random seed
    SeedBytes := RandomBytes(16);

    // Derive the encryption key using KDF
    KeyKDF := THash_Whirlpool0.KDFx(
      CipherKeyBytes[0], Length(CipherKeyBytes),
      SeedBytes[0], Length(SeedBytes), 8
    );

    // Generate a 16-byte random IV
    IVBytes := RandomBytes(16);

    // Initialize the cipher with the derived key and IV
    Cipher.Init(BytesToRawByteString(KeyKDF), BytesToRawByteString(IVBytes), 0);

    // Convert the source text to UTF-8 bytes
    PlainBytes := TEncoding.UTF8.GetBytes(SourceText);

    // Compute CRC16 of plaintext
    CRC16 := CRC16CCITT(PlainBytes);

    // Convert CRC16 to bytes (big endian)
    SetLength(CombinedOutput, 2 + Length(PlainBytes) + 2); // CRC16 + PlainText + SignaturePost
    CombinedOutput[0] := Byte((CRC16 shr 8) and $FF); // High byte
    CombinedOutput[1] := Byte(CRC16 and $FF);        // Low byte

    // Copy PlainBytes
    if Length(PlainBytes) > 0 then
      Move(PlainBytes[0], CombinedOutput[2], Length(PlainBytes));

    // Convert SignaturePost to bytes
    SignaturePostBytes := TEncoding.UTF8.GetBytes(SignaturePost);

    // Append SignaturePost
    if Length(SignaturePostBytes) > 0 then
      Move(SignaturePostBytes[0], CombinedOutput[2 + Length(PlainBytes)], Length(SignaturePostBytes));

    // Encrypt the combined byte array
    Ciphertext := Cipher.EncodeBytes(CombinedOutput);

    // Debug output (optional)
    MainForm.Memo1.Lines.Add('Encryption:');
    MainForm.Memo1.Lines.Add('CRC16: ' + IntToHex(CRC16, 4));
    MainForm.Memo1.Lines.Add('SeedBytes: ' + BytesToHex(SeedBytes));
    MainForm.Memo1.Lines.Add('IVBytes: ' + BytesToHex(IVBytes));
    MainForm.Memo1.Lines.Add('Plaintext: ' + SourceText);
    MainForm.Memo1.Lines.Add('Ciphertext: ' + BytesToHex(Ciphertext));

    // Combine SeedBytes, IVBytes, and Ciphertext into one byte array
    SetLength(CombinedOutput, Length(SeedBytes) + Length(IVBytes) + Length(Ciphertext));
    Move(SeedBytes[0], CombinedOutput[0], Length(SeedBytes));
    Move(IVBytes[0], CombinedOutput[Length(SeedBytes)], Length(IVBytes));
    Move(Ciphertext[0], CombinedOutput[Length(SeedBytes) + Length(IVBytes)], Length(Ciphertext));

    // Encode the combined byte array to Base64
    EncryptedBase64 := BytesToBase64(CombinedOutput);
    Result := EncryptedBase64;

    // Finalize the cipher
    Cipher.Done;
  finally
    Cipher.Free;
  end;
end;

// Decrypt Function with CRC16 and SignaturePost Verification
function DecryptString(const EncryptedBase64: string; const Password: string): string;
var
  Cipher: TCipher_Rijndael;
  CipherKeyBytes, KeyKDF, IVBytes, CombinedBytes, SeedBytes, CipherBytes, DecryptedBytes: TBytes;
  CRC16Extracted: Word;
  CRC16Computed: Word;
  SignaturePostBytes: TBytes;
  PlaintextBytes: TBytes;
  DecryptedText: string;
begin
  Cipher := TCipher_Rijndael.Create;
  try
    // Decode the Base64 encrypted data
    CombinedBytes := Base64ToBytes(EncryptedBase64);

    // Ensure the combined data has at least Seed (16) + IV (16) + CRC16 (2) + SignaturePost (2) bytes
    if Length(CombinedBytes) < (16 + 16 + 2 + 2) then
      raise Exception.Create('Invalid encrypted data: insufficient length');

    // Extract SeedBytes (first 16 bytes)
    SetLength(SeedBytes, 16);
    Move(CombinedBytes[0], SeedBytes[0], 16);

    // Extract IVBytes (next 16 bytes)
    SetLength(IVBytes, 16);
    Move(CombinedBytes[16], IVBytes[0], 16);

    // Extract CipherBytes (remaining bytes excluding Seed and IV)
    SetLength(CipherBytes, Length(CombinedBytes) - 32);
    if Length(CipherBytes) > 0 then
      Move(CombinedBytes[32], CipherBytes[0], Length(CipherBytes));

    // Debug output (optional)
    MainForm.Memo1.Lines.Add('Decryption:');
    MainForm.Memo1.Lines.Add('SeedBytes: ' + BytesToHex(SeedBytes));
    MainForm.Memo1.Lines.Add('IVBytes: ' + BytesToHex(IVBytes));
    MainForm.Memo1.Lines.Add('Ciphertext: ' + BytesToHex(CipherBytes));

    // Derive the key using KDF
    CipherKeyBytes := TEncoding.UTF8.GetBytes(Password);
    KeyKDF := THash_Whirlpool0.KDFx(
      CipherKeyBytes[0], Length(CipherKeyBytes),
      SeedBytes[0], Length(SeedBytes), 8
    );

    // Initialize the cipher in CBC mode with the derived key and IV
    Cipher.Mode := cmCBCx;
    Cipher.Init(BytesToRawByteString(KeyKDF), BytesToRawByteString(IVBytes), 0);

    // Decrypt the CipherBytes to obtain DecryptedBytes ([CRC16][Plaintext][SignaturePost])
    DecryptedBytes := Cipher.DecodeBytes(CipherBytes);

    // Ensure the decrypted data has at least CRC16 (2) + SignaturePost (2) bytes
    if Length(DecryptedBytes) < (2 + 2) then
      raise Exception.Create('Invalid decrypted data: too short.');

    // Extract CRC16 (first 2 bytes)
    CRC16Extracted := (DecryptedBytes[0] shl 8) or DecryptedBytes[1];

    // Extract SignaturePost (last 2 bytes)
    SetLength(SignaturePostBytes, 2);
    Move(DecryptedBytes[Length(DecryptedBytes) - 2], SignaturePostBytes[0], 2);

    // Verify SignaturePost
    if not TupleEqual(SignaturePostBytes, TEncoding.UTF8.GetBytes(SignaturePost)) then
      raise Exception.Create('Invalid password or corrupted data: Signature mismatch.');

    // Extract Plaintext (middle part)
    if Length(DecryptedBytes) > 4 then
    begin
      SetLength(PlaintextBytes, Length(DecryptedBytes) - 4);
      Move(DecryptedBytes[2], PlaintextBytes[0], Length(PlaintextBytes));
    end
    else
      SetLength(PlaintextBytes, 0); // Handle empty plaintext

    // Compute CRC16 of the extracted plaintext
    CRC16Computed := CRC16CCITT(PlaintextBytes);

    // Verify CRC16
    if CRC16Extracted <> CRC16Computed then
      raise Exception.Create('Invalid password or corrupted data: Checksum mismatch.');

    // Convert PlaintextBytes to a UTF-8 string
    try
      DecryptedText := TEncoding.UTF8.GetString(PlaintextBytes);
    except
      on E: Exception do
        raise Exception.Create('Decryption succeeded, but the decrypted data is not valid UTF-8: ' + E.Message);
    end;

    Result := DecryptedText;

    // Finalize the cipher
    Cipher.Done;
  finally
    Cipher.Free;
  end;
end;

// Button Click Event to Test Encryption and Decryption
procedure TMainForm.ORM_Test1Click(Sender: TObject);
var
  OriginalText, EncryptedString, DecryptedString: string;
begin
  OriginalText := 'aBcé';
  Memo1.Clear;
  Memo1.Lines.Add('Original Text: ' + OriginalText);

  EncryptedString := EncryptString(OriginalText, '#$GetIntoH^&%$');
  Memo1.Lines.Add('Encrypted String: ' + EncryptedString);

  try
    DecryptedString := DecryptString(EncryptedString, '#$GetIntoH^&%$');
    Memo1.Lines.Add('Decrypted String: ' + DecryptedString);

    if DecryptedString = OriginalText then
      Memo1.Lines.Add('Decryption successful: Strings match')
    else
      Memo1.Lines.Add('Decryption failed: Strings do not match');
  except
    on E: Exception do
      Memo1.Lines.Add('Error during decryption: ' + E.Message);
  end;
end;

initialization
  RandomSeed; // Initialize cryptographically secure random number generator in DECRandom.pas with random seed

finalization

end.