in the code below, change the decryption method to use Cipher.CalcMAC to detect if the encrypted data was tampered:

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_Test2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

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

{$R *.dfm}

// HexToBytes Function
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
    // Extract two characters from the hex string
    Temp := Copy(Hex, 2 * I + 1, 2);

    // Attempt to convert the hex substring to an integer
    if not TryStrToInt('$' + Temp, ByteInt) then
      raise Exception.CreateFmt('Invalid hex character at position %d', [2 * I + 1]);

    // Ensure the integer is within the byte range
    if (ByteInt < 0) or (ByteInt > 255) then
      raise Exception.CreateFmt('Hex value out of byte range at position %d', [2 * I + 1]);

    // Assign the byte value
    ByteValue := Byte(ByteInt);
    Result[I] := ByteValue;
  end;
end;

// Base64ToBytes and BytesToBase64 Functions
function BytesToBase64(const Bytes: TBytes): string;
begin
  Result := TNetEncoding.Base64.EncodeBytesToString(Bytes);
end;

function Base64ToBytes(const Base64: string): TBytes;
begin
  Result := TNetEncoding.Base64.DecodeStringToBytes(Base64);
end;

// EncryptString Function using Base64
function EncryptString(const SourceText, Password: string): string;
var
  Cipher: TCipher_Rijndael;
  CipherKeyBytes: TBytes;
  KeyKDF: TBytes;
  IVBytes: TBytes;
  Input, Ciphertext, SeedBytes, CombinedOutput: TBytes;
  EncryptedBase64: string;
  I: Integer;
begin
  Cipher := TCipher_Rijndael.Create;
  try
    // Set the cipher mode (CBC)
    Cipher.Mode := cmCBCx;

    // Convert the password to a byte array (UTF8)
    CipherKeyBytes := TEncoding.UTF8.GetBytes(Password);

    // Generate a random seed for the KDF (16 bytes)
    SeedBytes := RandomBytes(16);

    // Use the KDF to derive the key from the password and seed
    KeyKDF := THash_Whirlpool0.KDFx(
      CipherKeyBytes[0],
      Length(CipherKeyBytes),
      SeedBytes[0],
      Length(SeedBytes),
      8 // Adjust based on your KDF's requirements
    );

    // Generate a random IV (16 bytes for AES)
    IVBytes := RandomBytes(16);

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

    // Convert the source text to bytes (UTF8)
    Input := TEncoding.UTF8.GetBytes(SourceText);

    // Encrypt the input bytes
    Ciphertext := Cipher.EncodeBytes(Input);

    // 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));

    // Convert the combined bytes to a Base64 string
    EncryptedBase64 := BytesToBase64(CombinedOutput);

    Result := EncryptedBase64;

    // Clean up cipher internal data
    Cipher.Done;
  finally
    Cipher.Free;
  end;
end;

// DecryptString Function using Base64
function DecryptString(const EncryptedBase64: string; const Password: string): string;
var
  Cipher: TCipher_Rijndael;
  CipherKeyBytes: TBytes;
  KeyKDF: TBytes;
  IVBytes: TBytes;
  CombinedBytes, SeedBytes, CipherBytes, PlainBytes: TBytes;
  DecryptedText: string;
  I: Integer;
begin
  Cipher := TCipher_Rijndael.Create;
  try
    // Convert Base64 string to bytes
    CombinedBytes := Base64ToBytes(EncryptedBase64);

    // Ensure there are at least 32 bytes (16 SeedBytes + 16 IVBytes)
    if Length(CombinedBytes) < 32 then
      raise Exception.Create('Invalid encrypted data: insufficient length for SeedBytes and IVBytes');

    // 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 Ciphertext (remaining bytes)
    SetLength(CipherBytes, Length(CombinedBytes) - 32);
    Move(CombinedBytes[32], CipherBytes[0], Length(CipherBytes));

    // Set the cipher mode (must match encryption)
    Cipher.Mode := cmCBCx;

    // Convert the password to a byte array (UTF8)
    CipherKeyBytes := TEncoding.UTF8.GetBytes(Password);

    // Use the KDF to derive the key from the password and seed
    KeyKDF := THash_Whirlpool0.KDFx(
      CipherKeyBytes[0],
      Length(CipherKeyBytes),
      SeedBytes[0],
      Length(SeedBytes),
      8 // Same key size multiplier as in encryption
    );

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

    // Decrypt the CipherBytes
    PlainBytes := Cipher.DecodeBytes(CipherBytes);

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

    Result := DecryptedText;

    // Clean up cipher internal data
    Cipher.Done;
  finally
    Cipher.Free;
  end;
end;

procedure TMainForm.ORM_Test2Click(Sender: TObject);
begin
 var encstring:= EncryptString('aBcé','#$GetIntoH^&%$');
 showmessage(encstring);
 var decstring:= DecryptString(encstring,'#$GetIntoH^&%$');

 showmessage(encstring+#13+decstring);
end;

initialization

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

finalization

end.