トップへ(mam-mam.net/)

Fully Implemented AES Encryption in Delphi: Mam.AES.pas with OpenSSL‑Compatible CBC, CTR, and ECB Modes

Japanese

Fully Implemented AES Encryption in Delphi: Mam.AES.pas with OpenSSL‑Compatible CBC, CTR, and ECB Modes

This page introduces Mam.AES.pas, a Delphi unit that provides complete AES encryption and decryption functionality.
It is a pure‑Delphi implementation requiring no external DLLs, supporting AES‑128/192/256 in CBC, CTR, and ECB modes, with OpenSSL‑compatible salt generation and built‑in key/IV hashing.
The unit also includes practical features such as Base64 encoding, round‑key generation, and proper handling of initialization vectors—covering everything needed for real‑world encryption workflows.

Usage

The full source code for Mam.AES.pas is provided at the bottom of this page.
Save it as Mam.AES.pas and include it with:
uses Mam.AES;
(It should work on Delphi XE8 or later.)

The recommended modes are AES‑128‑CBC, AES‑192‑CBC, AES‑256‑CBC, AES‑128‑CTR, AES‑192‑CTR, and AES‑256‑CTR.
The ECB variants (AES‑128‑ECB, AES‑192‑ECB, AES‑256‑ECB) are not recommended.
It is also recommended to use a randomly generated salt value for key derivation.

Block Size Key Length Initialization Vector (IV)
AES‑128‑ECB128‑bit (16 bytes)128‑bit (16 bytes)None
AES‑192‑ECB128‑bit (16 bytes)192‑bit (24 bytes)None
AES‑256‑ECB128‑bit (16 bytes)256‑bit (32 bytes)None
AES‑128‑CBC128‑bit (16 bytes)128‑bit (16 bytes)128‑bit (16 bytes)
AES‑192‑CBC128‑bit (16 bytes)192‑bit (24 bytes)128‑bit (16 bytes)
AES‑256‑CBC128‑bit (16 bytes)256‑bit (32 bytes)128‑bit (16 bytes)
AES‑128‑CTR128‑bit (16 bytes)128‑bit (16 bytes)128‑bit (16 bytes)
AES‑192‑CTR128‑bit (16 bytes)192‑bit (24 bytes)128‑bit (16 bytes)
AES‑256‑CTR128‑bit (16 bytes)256‑bit (32 bytes)128‑bit (16 bytes)

(1) Usage Example — AES‑128‑ECB (Not Recommended)

When using AES in ECB mode, no initialization vector (IV) is used.

uses Mam.AES,
     System.NetEncoding;

procedure TForm1.Button1Click(Sender: TObject);
var
  aes: TMamAes;
  data, password: String;
  dec, key, enc: TBytes;
  st: string;
begin
  // Data to encrypt
  data := 'This string will be encrypted';
  // Encryption password (used directly as a 16‑byte key)
  password := '1234567890123456';

  // Prepare data for encryption (convert UTF‑8 string to byte array)
  dec := TEncoding.UTF8.GetBytes(data);
  key := TEncoding.ASCII.GetBytes(password);  // Encryption key

  aes := TMamAes.Create(TMamAesType.MamAES128ECB);
  // Perform AES‑128‑ECB encryption
  enc := aes.Encrypt(dec, key);
  // Perform AES‑128‑ECB decryption
  dec := aes.Decrypt(enc, key);

  // Convert decrypted byte array back to UTF‑8 string
  st := TEncoding.UTF8.GetString(dec);
  ShowMessage(st);

  aes.Free;
end;

(2) Usage Example — AES‑192‑CBC

This example demonstrates AES‑192‑CBC encryption and decryption using the same key‑derivation method as OpenSSL.
A random salt value is generated, and both the key and IV are derived from password + salt using MD5 hashing.
The functions OpenSslEncrypt and OpenSslDecrypt handle this process automatically.

uses Mam.AES,
     System.NetEncoding;

procedure TForm1.Button1Click(Sender: TObject);
var
  aes: TMamAes;
  data, password: String;
  dec, enc: TBytes;
  st: string;
begin
  aes := TMamAes.Create(TMamAesType.MamAES192CBC);

  // UTF‑8 text to encrypt (Japanese text used here as an example)
  data := 'This string will be encrypted';
  dec := TEncoding.UTF8.GetBytes(data);

  password := 'abcdefghijklmnop';

  // Perform AES encryption (OpenSSL‑compatible key/IV derivation)
  enc := aes.OpenSslEncrypt(dec, password);

  // Perform AES decryption
  dec := aes.OpenSslDecrypt(enc, password);

  // Convert decrypted bytes back to UTF‑8 string
  st := TEncoding.UTF8.GetString(dec);
  ShowMessage(st);

  aes.Free;
end;

Mam.AES.pas

unit Mam.AES;

interface
uses Winapi.Windows,system.SysUtils;

type
  TMamAesType=(
    MamAES128ECB,MamAES192ECB,MamAES256ECB,
    MamAES128CBC,MamAES192CBC,MamAES256CBC,
    MamAES128CTR,MamAES192CTR,MamAES256CTR
  );
  TAesMatrix44=array[0..3] of array[0..3]of byte;
  PAesMatrix44=^TAesMatrix44;

  TMamAes = class(TObject)
    private
      fAesType: TMamAesType;
      fKeyNum: Byte;        // Key length
      fKey: TBytes;         // Key bytes (128, 192, or 256 bits)
      fIV: TBytes;          // Initialization Vector for CBC/CTR (fixed 128-bit)
      fNumOfRound: Byte;    // Number of AES rounds required
      //fRound: Byte;        // Current round (unused)
      fNk: Byte;            // Key length in units of Nb (4-byte words)
      fRoundKey: TBytes;    // Expanded round keys
      fData: TBytes;        // Data buffer for encryption/decryption
      fIsECB: Boolean;      // ECB mode flag
      fIsCBC: Boolean;      // CBC mode flag
      fIsCTR: Boolean;      // CTR mode flag

      // Generate round keys (Key Expansion)
      procedure KeyExpansion();
      // Set initialization vector (IV)
      procedure SetIv(iv: TBytes);
      // Set AES mode (ECB128, CBC128, etc.)
      procedure SetAesType(AesType: TMamAesType);
      // XOR with IV (for CBC/CTR)
      procedure XorIV(buf: PByte);
      procedure Cipher(State: PAesMatrix44);
      procedure InvCipher(State: PAesMatrix44);
      procedure AddRoundKey(Round: Byte; State: PAesMatrix44);
      procedure SubBytes(State: PAesMatrix44);
      procedure InvSubBytes(State: PAesMatrix44);
      procedure ShiftRows(State: PAesMatrix44);
      procedure InvShiftRows(State: PAesMatrix44);
      procedure MixColumns(State: PAesMatrix44);
      procedure InvMixColumns(State: PAesMatrix44);
      function Multiply(x, y: Byte): Byte;
      function XTime(x: Byte): Byte;

    public
      // Constructor
      constructor Create(AesType: TMamAesType);

      // Encryption
      function Encrypt(data, key: TBytes): TBytes; overload;
      function Encrypt(data, key, iv: TBytes): TBytes; overload;

      // Decryption
      function Decrypt(data, key: TBytes): TBytes; overload;
      function Decrypt(data, key, iv: TBytes): TBytes; overload;

      // Base64 Encode
      function Base64Encode(data: TBytes): String;
      // Base64 Decode
      function Base64Decode(data: String): TBytes;

      // OpenSSL‑compatible encryption:
      // - Generate random salt
      // - Derive key from password + salt using MD5
      // - Derive IV using MD5
      // - Perform AES encryption/decryption
      function OpenSslEncrypt(data: TBytes; Password: String): TBytes;
      function OpenSslDecrypt(data: TBytes; Password: String): TBytes;
  end;

implementation

uses System.Hash,
     System.NetEncoding;


const
  // Number of AES columns (encryption operates on 4-byte words)
  Nb: Byte = 4;
  // AES block size (fixed 128-bit = 16 bytes)
  AES_BlockSize: Byte = 16;

const
SBox:array[0..255]of byte=(
$63, $7c, $77, $7b, $f2, $6b, $6f, $c5, $30, $01, $67, $2b, $fe, $d7, $ab, $76,
$ca, $82, $c9, $7d, $fa, $59, $47, $f0, $ad, $d4, $a2, $af, $9c, $a4, $72, $c0,
$b7, $fd, $93, $26, $36, $3f, $f7, $cc, $34, $a5, $e5, $f1, $71, $d8, $31, $15,
$04, $c7, $23, $c3, $18, $96, $05, $9a, $07, $12, $80, $e2, $eb, $27, $b2, $75,
$09, $83, $2c, $1a, $1b, $6e, $5a, $a0, $52, $3b, $d6, $b3, $29, $e3, $2f, $84,
$53, $d1, $00, $ed, $20, $fc, $b1, $5b, $6a, $cb, $be, $39, $4a, $4c, $58, $cf,
$d0, $ef, $aa, $fb, $43, $4d, $33, $85, $45, $f9, $02, $7f, $50, $3c, $9f, $a8,
$51, $a3, $40, $8f, $92, $9d, $38, $f5, $bc, $b6, $da, $21, $10, $ff, $f3, $d2,
$cd, $0c, $13, $ec, $5f, $97, $44, $17, $c4, $a7, $7e, $3d, $64, $5d, $19, $73,
$60, $81, $4f, $dc, $22, $2a, $90, $88, $46, $ee, $b8, $14, $de, $5e, $0b, $db,
$e0, $32, $3a, $0a, $49, $06, $24, $5c, $c2, $d3, $ac, $62, $91, $95, $e4, $79,
$e7, $c8, $37, $6d, $8d, $d5, $4e, $a9, $6c, $56, $f4, $ea, $65, $7a, $ae, $08,
$ba, $78, $25, $2e, $1c, $a6, $b4, $c6, $e8, $dd, $74, $1f, $4b, $bd, $8b, $8a,
$70, $3e, $b5, $66, $48, $03, $f6, $0e, $61, $35, $57, $b9, $86, $c1, $1d, $9e,
$e1, $f8, $98, $11, $69, $d9, $8e, $94, $9b, $1e, $87, $e9, $ce, $55, $28, $df,
$8c, $a1, $89, $0d, $bf, $e6, $42, $68, $41, $99, $2d, $0f, $b0, $54, $bb, $16
);

InvSBox:array[0..255]of byte=(
$52, $09, $6a, $d5, $30, $36, $a5, $38, $bf, $40, $a3, $9e, $81, $f3, $d7, $fb,
$7c, $e3, $39, $82, $9b, $2f, $ff, $87, $34, $8e, $43, $44, $c4, $de, $e9, $cb,
$54, $7b, $94, $32, $a6, $c2, $23, $3d, $ee, $4c, $95, $0b, $42, $fa, $c3, $4e,
$08, $2e, $a1, $66, $28, $d9, $24, $b2, $76, $5b, $a2, $49, $6d, $8b, $d1, $25,
$72, $f8, $f6, $64, $86, $68, $98, $16, $d4, $a4, $5c, $cc, $5d, $65, $b6, $92,
$6c, $70, $48, $50, $fd, $ed, $b9, $da, $5e, $15, $46, $57, $a7, $8d, $9d, $84,
$90, $d8, $ab, $00, $8c, $bc, $d3, $0a, $f7, $e4, $58, $05, $b8, $b3, $45, $06,
$d0, $2c, $1e, $8f, $ca, $3f, $0f, $02, $c1, $af, $bd, $03, $01, $13, $8a, $6b,
$3a, $91, $11, $41, $4f, $67, $dc, $ea, $97, $f2, $cf, $ce, $f0, $b4, $e6, $73,
$96, $ac, $74, $22, $e7, $ad, $35, $85, $e2, $f9, $37, $e8, $1c, $75, $df, $6e,
$47, $f1, $1a, $71, $1d, $29, $c5, $89, $6f, $b7, $62, $0e, $aa, $18, $be, $1b,
$fc, $56, $3e, $4b, $c6, $d2, $79, $20, $9a, $db, $c0, $fe, $78, $cd, $5a, $f4,
$1f, $dd, $a8, $33, $88, $07, $c7, $31, $b1, $12, $10, $59, $27, $80, $ec, $5f,
$60, $51, $7f, $a9, $19, $b5, $4a, $0d, $2d, $e5, $7a, $9f, $93, $c9, $9c, $ef,
$a0, $e0, $3b, $4d, $ae, $2a, $f5, $b0, $c8, $eb, $bb, $3c, $83, $53, $99, $61,
$17, $2b, $04, $7e, $ba, $77, $d6, $26, $e1, $69, $14, $63, $55, $21, $0c, $7d
);
RCon:array[0..10]of byte=(
  $8d, $01, $02, $04, $08, $10, $20, $40, $80, $1b, $36);
MamBase64: String =
  'ABCDEFGHIJKLMNOPQRSTUVWXYZ'+
  'abcdefghijklmnopqrstuvwxyz'+
  '0123456789+/';


{ TMamAes }

procedure TMamAes.AddRoundKey(Round: Byte; State: PAesMatrix44);
var i,j:Cardinal;
begin
  for i := 0 to 3 do
    for j := 0 to 3 do
      state^[i][j]:=State^[i][j] xor fRoundKey[(Round*Nb*4)+(i*Nb)+j];
end;

function TMamAes.Base64Decode(data:String):TBytes;
var i,Len:integer;
    b:byte;
    i1,i2,i3,i4:integer;
begin
  SetLength(Result,0);
  i:=0;
  Len:=length(data);
  while i<Len do
  begin
    i1:=Pos(data.Substring(i,1),MamBase64)-1;
    inc(i);
    i2:=Pos(data.Substring(i,1),MamBase64)-1;
    inc(i);
    i3:=Pos(data.Substring(i,1),MamBase64)-1;
    inc(i);
    i4:=Pos(data.Substring(i,1),MamBase64)-1;
    if i3=-1 then
    begin
      b:=(i1 shl 2)+((i2 and $30) shr 4);
      SetLength(Result,Length(Result)+1);
      Result[Length(Result)-1]:=b;
    end
    else if i4=-1 then
    begin
      b:=(i1 shl 2)+((i2 and $30) shr 4);
      SetLength(Result,Length(Result)+1);
      Result[Length(Result)-1]:=b;
      b:=((i2 and $0f) shl 4) + ((i3 and $3c) shr 2);
      SetLength(Result,Length(Result)+1);
      Result[Length(Result)-1]:=b;
    end
    else
    begin
      b:=(i1 shl 2)+((i2 and $30) shr 4);
      SetLength(Result,Length(Result)+1);
      Result[Length(Result)-1]:=b;
      b:=((i2 and $0f) shl 4) + ((i3 and $3c) shr 2);
      SetLength(Result,Length(Result)+1);
      Result[Length(Result)-1]:=b;
      b:=((i3 and $03) shl 6)+ (i4 and $3f);
      SetLength(Result,Length(Result)+1);
      Result[Length(Result)-1]:=b;
    end;
    inc(i);
  end;
end;

function TMamAes.Base64Encode(data: TBytes): String;
var i,Len:Cardinal;
    enc:array[0..3] of String;
    b1,b2,b3:byte;
begin
  Len:=Length(data);
  result:='';
  i:=0;
  while i<Len do
  begin
    enc[0]:='';
    enc[1]:='';
    enc[2]:='';
    enc[3]:='';
    //ZeroMemory(@enc,4);
    b1:=data[i];
    enc[0]:=MamBase64.Substring((b1 and $fc) shr 2,1);
    inc(i);
    if i>=Len then
    begin
      enc[1]:=MamBase64.Substring((b1 and $3) shl 4,1);
      enc[2]:='=';
      enc[3]:='=';
    end
    else
    begin
      b2:=data[i];
      enc[1]:=MamBase64.Substring(((b1 and $3) shl 4 )+((b2 and $f0) shr 4),1);
      inc(i);
      if i>=Len then
      begin
        enc[2]:=MamBase64.Substring((b2 and $f) shl 2,1);
        enc[3]:='=';
      end
      else
      begin
        b3:=data[i];
        enc[2]:=MamBase64.Substring(((b2 and $f) shl 2)+((b3 and $c0) shr 6),1);
        enc[3]:=MamBase64.Substring(b3 and $3f,1);
      end;
    end;
    Result:=Result+enc[0]+enc[1]+enc[2]+enc[3];
    inc(i);
  end;
end;

procedure TMamAes.Cipher(State: PAesMatrix44);
var Round:Byte;
begin
  Round:=0;
  AddRoundKey(Round,State);
  inc(round);
  while true do
  begin
    SubBytes(State);
    shiftrows(State);
    if Round>=fNumOfRound then break;
    mixcolumns(State);
    AddRoundKey(Round,State);
    inc(Round);
  end;
  AddRoundKey(Round,State);
end;
procedure TMamAes.InvCipher(State: PAesMatrix44);
var Round:byte;
begin
  AddRoundKey(fNumOfRound,State);
  Round:=fNumOfRound-1;
  while true do
  begin
    InvShiftRows(State);
    InvSubBytes(State);
    AddRoundKey(Round,State);
    if round=0 then break;
    InvMixColumns(state);
    dec(Round);
  end;
end;

constructor TMamAes.Create(AesType:TMamAesType);
//Constructor
begin
  inherited Create();
  setLength(fIV,AES_BlockSize);
  ZeroMemory(fIV,AES_BlockSize);
  SetAesType(AesType);
end;

function TMamAes.Decrypt(data, key: TBytes):TBytes;
var i,len,bi:Cardinal;
    NextIv:TBytes;
    buf:TBytes;
begin
  //Set encryption key
  len:=Length(key);
  SetLength(fkey,fKeyNum);
  ZeroMemory(@fkey[0],fKeyNum);
  if len>fKeyNum then len:=fKeyNum;
  move(key[0],fkey[0],len);

  KeyExpansion();

  //Store data to be decrypted
  len:=length(data);
  SetLength(fData,len);
  ZeroMemory(fData,len);
  Move(data[0],fData[0],len);

  if fIsECB or fIsCBC then
  begin
    //AES‑ECB or AES‑CBC
    setlength(NextIv,AES_BlockSize);
    i:=0;
    while i<len do
    begin
      move(fData[i],NextIv[0],AES_BlockSize);
      invCipher(PAesMatrix44(@fData[i]));
      if fIsCBC then
      begin
        XorIv(@fData[i]);
        move(NextIv[0],fIV[0],AES_BlockSize);
      end;

      inc(i,AES_BlockSize);
    end;
    //PKCS#7
    len:=len-fData[len-1];
    setlength(fData,len);
  end
  else
  begin
    //AES-CTR mode
    i:=0;
    bi:=AES_BlockSize;
    setlength(buf,AES_BlockSize);
    while i<len do
    begin
      if bi=AES_BlockSize then
      begin
        Move(fIV[0],buf[0],AES_BlockSize);
        Cipher(PAesMatrix44(@buf[0]));
        for bi := (AES_BlockSize-1) downto 0 do
        begin
          if (fIV[bi]=255) then
          begin
            fIV[bi]:=0;
          end
          else
          begin
            inc(fIV[bi]);
            break;
          end;
        end;
        bi:=0;
      end;
      fData[i] := (fData[i] xor buf[bi]);
      inc(i);
      inc(bi);
    end;
  end;
  Result:=fData;
end;

function TMamAes.Decrypt(data, key, iv: TBytes):TBytes;
begin
  SetIv(iv);
  Result:=Decrypt(data,key);
end;

function TMamAes.Encrypt(data, key: TBytes):TBytes;
var i,bi:Cardinal;
    Len:Cardinal;
    LenPKCS7:Cardinal;
    buf:TBytes;
begin
  //Set encryption key
  Len:=Length(key);
  SetLength(fkey,fKeyNum);
  ZeroMemory(@fkey[0],fKeyNum);
  if Len>fKeyNum then Len:=fKeyNum;
  move(key[0],fkey[0],Len);

  KeyExpansion();

  //Store data to be encrypted
  Len:=length(data);
  SetLength(fData,Len);
  ZeroMemory(fData,Len);
  Move(data[0],fData[0],Len);


  if fIsECB or fIsCBC then
  begin
    //AES-ECB or AES-CBC

    //PKCS#7
    LenPKCS7:=(Len div 16 * 16)+16;
    setlength(fData,LenPKCS7);
    for i := Len to LenPKCS7-1 do
      fData[i]:=byte(LenPKCS7-Len);
    Len:=LenPKCS7;

    i:=0;
    while i<Len do
    begin
      if fIsCBC then self.XorIV(@fData[i]);
      self.Cipher(PAesMatrix44(@fData[i]));
      if fIsCBC then
        Move(fData[i],fIV[0],AES_BlockSize);
      Inc(i,AES_BlockSize);
    end;
  end
  else
  begin
    //AES-CTR mode
    i:=0;
    bi:=AES_BlockSize;
    setlength(buf,AES_BlockSize);
    while i<len do
    begin
      if bi=AES_BlockSize then
      begin
        Move(fIV[0],buf[0],AES_BlockSize);
        Cipher(PAesMatrix44(@buf[0]));
        for bi := (AES_BlockSize-1) downto 0 do
        begin
          if (fIV[bi]=255) then
          begin
            fIV[bi]:=0;
          end
          else
          begin
            inc(fIV[bi]);
            break;
          end;
        end;
        bi:=0;
      end;
      fData[i] := (fData[i] xor buf[bi]);
      inc(i);
      inc(bi);
    end;
  end;
  result:=fData;
end;

function TMamAes.Encrypt(data, key, iv: TBytes):TBytes;
begin
  self.SetIv(iv);
  result:=self.Encrypt(data,key);
end;

procedure TMamAes.KeyExpansion;
var AES_keyExpSize:Cardinal;
    i,j,k:Cardinal;
    tmp:array[0..3] of Byte;
    b:Byte;
begin
  AES_keyExpSize:=176+32*0;
  if fNumOfRound=10 then AES_keyExpSize:=176+32*0;
  if fNumOfRound=12 then AES_keyExpSize:=176+32*1;
  if fNumOfRound=14 then AES_keyExpSize:=176+32*2;
  SetLength(fRoundKey,AES_keyExpSize);
  for i := 0 to fNk-1 do
  begin
    fRoundKey[i*4+0]:=fKey[i*4+0];
    fRoundKey[i*4+1]:=fKey[i*4+1];
    fRoundKey[i*4+2]:=fKey[i*4+2];
    fRoundKey[i*4+3]:=fKey[i*4+3];
  end;
  for i := fNk to Nb*(fNumOfRound+1)-1 do
  begin
    k:=(i-1)*4;
    tmp[0]:=fRoundKey[k+0];
    tmp[1]:=fRoundKey[k+1];
    tmp[2]:=fRoundKey[k+2];
    tmp[3]:=fRoundKey[k+3];
    if (i mod fNk)=0 then
    begin
      b:=tmp[0];
      tmp[0]:=tmp[1];
      tmp[1]:=tmp[2];
      tmp[2]:=tmp[3];
      tmp[3]:=b;

      tmp[0] := sbox[tmp[0]];
      tmp[1] := sbox[tmp[1]];
      tmp[2] := sbox[tmp[2]];
      tmp[3] := sbox[tmp[3]];

      tmp[0]:=tmp[0] xor Rcon[i div fNk];
    end;

    if fNumOfRound=14 then
    begin //aes256
      if (i mod fNk)=4 then
      begin
        tmp[0] := sbox[tmp[0]];
        tmp[1] := sbox[tmp[1]];
        tmp[2] := sbox[tmp[2]];
        tmp[3] := sbox[tmp[3]];
      end;
    end;
    j:=i*4;
    k:=(i-fNk)*4;
    fRoundKey[j+0]:=fRoundKey[k+0] xor tmp[0];
    fRoundKey[j+1]:=fRoundKey[k+1] xor tmp[1];
    fRoundKey[j+2]:=fRoundKey[k+2] xor tmp[2];
    fRoundKey[j+3]:=fRoundKey[k+3] xor tmp[3];
  end;
end;

procedure TMamAes.MixColumns(State: PAesMatrix44);
var i:integer;
    tmp,tmp1,tmp2:byte;
begin
  for i := 0 to 3 do
  begin
    tmp :=State^[i][0];
    tmp1:=State^[i][0] xor State^[i][1] xor
          State^[i][2] xor State^[i][3];
    tmp2:=State^[i][0] xor State^[i][1];
    tmp2:=XTime(tmp2);
    State^[i][0]:= State^[i][0] xor tmp2 xor tmp1;
    tmp2:=State^[i][1] xor State^[i][2];
    tmp2:=XTime(tmp2);
    State^[i][1]:= State^[i][1] xor tmp2 xor tmp1;
    tmp2:=State^[i][2] xor State^[i][3];
    tmp2:=XTime(tmp2);
    State^[i][2]:= State^[i][2] xor tmp2 xor tmp1;
    tmp2:=State^[i][3] xor tmp;
    tmp2:=XTime(tmp2);
    State^[i][3]:= State^[i][3] xor tmp2 xor tmp1;
  end;
end;
procedure TMamAes.InvMixColumns(State: PAesMatrix44);
var i:Cardinal;
    tmp1,tmp2,tmp3,tmp4:byte;
begin
  for i := 0 to 3 do
  begin
    tmp1:=State^[i][0];
    tmp2:=State^[i][1];
    tmp3:=State^[i][2];
    tmp4:=State^[i][3];
    State^[i][0]:=
      Multiply(tmp1,$0e) xor Multiply(tmp2,$0b) xor
      Multiply(tmp3,$0d) xor Multiply(tmp4,$09);
    State^[i][1]:=
      Multiply(tmp1,$09) xor Multiply(tmp2,$0e) xor
      Multiply(tmp3,$0b) xor Multiply(tmp4,$0d);
    State^[i][2]:=
      Multiply(tmp1,$0d) xor Multiply(tmp2,$09) xor
      Multiply(tmp3,$0e) xor Multiply(tmp4,$0b);
    State^[i][3]:=
      Multiply(tmp1,$0b) xor Multiply(tmp2,$0d) xor
      Multiply(tmp3,$09) xor Multiply(tmp4,$0e);
  end;
end;

procedure TMamAes.SetAesType(AesType: TMamAesType);
//Set AES mode (ECB, CBC, CTR)
begin
  fIsECB:=False;
  fIsCBC:=False;
  fIsCTR:=False;
  //Set mode flags
  fAesType:=AesType;
  if (fAesType=MamAES128ECB) or
     (fAesType=MamAES128CBC) or
     (fAesType=MamAES128CTR) then
    fKeyNum:=16
  else if (fAesType=MamAES192ECB) or
          (fAesType=MamAES192CBC) or
          (fAesType=MamAES192CTR) then
    fKeyNum:=24
  else
    fKeyNum:=32;
  if (fAesType=MamAES128ECB) or
     (fAesType=MamAES192ECB) or
     (fAesType=MamAES256ECB) then fIsECB:=True;
  if (fAesType=MamAES128CBC) or
     (fAesType=MamAES192CBC) or
     (fAesType=MamAES256CBC) then fIsCBC:=True;
  if (fAesType=MamAES128CTR) or
     (fAesType=MamAES192CTR) or
     (fAesType=MamAES256CTR) then fIsCTR:=True;

  fNk:=fKeyNum div Nb;//number of 32‑bit words in the key
  fNumOfRound:=fNk+6; //Number of AES rounds
end;

procedure TMamAes.SetIv(iv: TBytes);
var len:Cardinal;
//Set initialization vector (IV)
begin
  setLength(fIV,AES_BlockSize);
  ZeroMemory(@fIV[0],AES_BlockSize);
  len:=length(iv);
  if len>AES_BlockSize then len:=AES_BlockSize;
  Move(iv[0],fIV[0],len);
end;

procedure TMamAes.ShiftRows(State: PAesMatrix44);
var tmp:Byte;
begin
  tmp         :=State^[0][1];
  State^[0][1]:=State^[1][1];
  State^[1][1]:=State^[2][1];
  State^[2][1]:=State^[3][1];
  State^[3][1]:=tmp;
  tmp         :=State^[0][2];
  State^[0][2]:=State^[2][2];
  State^[2][2]:=tmp;
  tmp         :=State^[1][2];
  State^[1][2]:=State^[3][2];
  State^[3][2]:=tmp;
  tmp         :=State^[0][3];
  State^[0][3]:=State^[3][3];
  State^[3][3]:=State^[2][3];
  State^[2][3]:=State^[1][3];
  State^[1][3]:=tmp;
end;
procedure TMamAes.InvShiftRows(State: PAesMatrix44);
var tmp:Byte;
begin
  tmp         :=state^[3][1];
  state^[3][1]:=state^[2][1];
  state^[2][1]:=state^[1][1];
  state^[1][1]:=state^[0][1];
  state^[0][1]:=tmp;
  tmp         :=state^[0][2];
  state^[0][2]:=state^[2][2];
  state^[2][2]:=tmp;
  tmp         :=state^[1][2];
  state^[1][2]:=state^[3][2];
  state^[3][2]:=tmp;
  tmp         :=state^[0][3];
  state^[0][3]:=state^[1][3];
  state^[1][3]:=state^[2][3];
  state^[2][3]:=state^[3][3];
  state^[3][3]:=tmp;
end;

procedure TMamAes.SubBytes(State: PAesMatrix44);
var i,j:integer;
begin
  for i := 0 to 3 do
    for j := 0 to 3 do
      State^[j][i]:= SBox[State^[j][i]];
end;
procedure TMamAes.InvSubBytes(State: PAesMatrix44);
var i,j:integer;
begin
  for i := 0 to 3 do
    for j := 0 to 3 do
      State^[j][i]:= InvSBox[State^[j][i]];
end;

procedure TMamAes.XorIV(buf: PByte);
var i:Cardinal;
begin
  for i := 0 to AES_BlockSize-1 do
  begin
    buf^:=buf^ xor fIV[i];
    inc(buf);
  end;
end;

function TMamAes.Multiply(x, y: Byte): Byte;
begin
  result:=(
    ((y and 1) * x) xor
    (((y shr 1) and 1)*xtime(x)) xor
    (((y shr 2) and 1)*xtime(xtime(x))) xor
    (((y shr 3) and 1)*xtime(xtime(xtime(x)))) xor
    (((y shr 4) and 1)*xtime(xtime(xtime(xtime(x)))))
  );
end;

function TMamAes.OpenSslDecrypt(data: TBytes; Password: String): TBytes;
var b1,b2,b3:TBytes;
    pass,salted,salt,iv,pass_salt,key:TBytes;
    i:Cardinal;
    flag:boolean;
    dec:TBytes;
    md5:THashMD5;
    data_nosalt:TBytes;
begin
  // 'Salted__' + 8‑byte salt
  // key = MD5(password + salt)
  // IV  = MD5(key + password + salt)

  result:=dec;

  //Minimum length check (except for CTR mode)
  if not ((fAesType=TMamAesType.MamAES128CTR) or
     (fAesType=TMamAesType.MamAES192CTR) or
     (fAesType=TMamAesType.MamAES256CTR)) then
  begin
    if length(data)<32 then exit;
  end;

  //Validate and extract salt
  salted:=TEncoding.ASCII.GetBytes('Salted__');
  flag:=false;
  for i := 0 to 7 do
    if data[i]<>salted[i] then flag:=true;
  if flag then exit;
  setlength(salt,8);
  move(data[8],salt[0],8);

  // Remove 'Salted__' + salt header
  setlength(data_nosalt,length(data)-16);
  move(data[16],data_nosalt[0],length(data)-16);

  //Convert password to byte array
  pass:=TEncoding.ASCII.GetBytes(Password);

  //Build pass + salt byte array
  SetLength(pass_salt,Length(pass)+Length(salt));
  Move(pass[0],pass_salt[0],Length(pass));
  Move(salt[0],pass_salt[Length(pass)],Length(salt));

  md5:=THashMD5.Create;
  md5.Reset;
  md5.Update(pass_salt);
  b1:=md5.HashAsBytes;

  md5.Reset;
  setlength(b2,16+Length(pass_salt));
  move(b1[0],b2[0],length(b1));
  move(pass_salt[0],b2[length(b1)],length(pass_salt));
  md5.Update(b2);
  b2:=md5.HashAsBytes;

  md5.Reset;
  setlength(b3,16+Length(pass_salt));
  move(b2[0],b3[0],length(b2));
  move(pass_salt[0],b3[length(b2)],length(pass_salt));
  md5.Update(b3);
  b3:=md5.HashAsBytes;

  setlength(iv,16);
  if fKeyNum=16 then
  begin
    setlength(key,16);
    move(b1[0],key[0],16);
    move(b2[0],iv[0],16);
  end
  else if fKeyNum=24 then
  begin
    setlength(key,24);
    move(b1[0],key[0],16);
    move(b2[0],key[16],8);
    move(b2[8],iv[0],8);
    move(b3[0],iv[8],8);
  end
  else
  begin
    setlength(key,32);
    move(b1[0],key[0],16);
    move(b2[0],key[16],16);
    move(b3[0],iv[0],16);
  end;
  dec:=self.Decrypt(data_nosalt,key,iv);
  result:=dec;
end;

function TMamAes.OpenSslEncrypt(data: TBytes; Password: String): TBytes;
var i:Cardinal;
    md5:THashMD5; //Available in Delphi XE8 and later
    salt,pass,pass_salt:TBytes;
    b1,b2,b3:TBytes;
    key,iv:TBytes;
    enc:TBytes;
    salted:TBytes;
    res:TBytes;
begin
  // 'Salted__' + 8‑byte salt
  // key = MD5(password + salt)
  // IV  = MD5(key + password + salt)

  pass:=TEncoding.ASCII.GetBytes(Password);

  //Generate random 8‑byte salt
  Randomize;
  SetLength(salt,8);
  for i := 0 to 7 do
    salt[i]:=Random(256);

  //Build pass + salt byte array
  SetLength(pass_salt,Length(pass)+Length(salt));
  Move(pass[0],pass_salt[0],Length(pass));
  Move(salt[0],pass_salt[Length(pass)],Length(salt));

  md5:=THashMD5.Create;
  md5.Reset;
  md5.Update(pass_salt);
  b1:=md5.HashAsBytes;

  md5.Reset;
  setlength(b2,16+Length(pass_salt));
  move(b1[0],b2[0],length(b1));
  move(pass_salt[0],b2[length(b1)],length(pass_salt));
  md5.Update(b2);
  b2:=md5.HashAsBytes;

  md5.Reset;
  setlength(b3,16+Length(pass_salt));
  move(b2[0],b3[0],length(b2));
  move(pass_salt[0],b3[length(b2)],length(pass_salt));
  md5.Update(b3);
  b3:=md5.HashAsBytes;

  // Build key and IV depending on AES key size
  setlength(iv,16);
  if fKeyNum=16 then  //AES-128
  begin
    setlength(key,16);
    move(b1[0],key[0],16);
    move(b2[0],iv[0],16);
  end
  else if fKeyNum=24 then  //AES-192
  begin
    setlength(key,24);
    move(b1[0],key[0],16);
    move(b2[0],key[16],8);
    move(b2[8],iv[0],8);
    move(b3[0],iv[8],8);
  end
  else  //AES-256
  begin
    setlength(key,32);
    move(b1[0],key[0],16);
    move(b2[0],key[16],16);
    move(b3[0],iv[0],16);
  end;
  // Perform AES encryption
  enc:=self.Encrypt(data,key,iv);

  // Build OpenSSL‑compatible output: 'Salted__' + salt + encrypted data
  salted:=TEncoding.ASCII.GetBytes('Salted__');

  setlength(res,length(salted)+length(salt)+length(enc));
  move(salted[0],res[0],length(salted));
  move(salt[0],res[8],length(salt));
  move(enc[0],res[16],length(enc));
  result:=res;
end;

function TMamAes.XTime(x: byte): byte;
begin
  result:=((x shl 1) xor (((x shr 7) and 1) * $1b));
end;

end.