0

I have database that is used by both Delphi (XE 10.4 or 11) and PHP (8+) versions and this database has some encrypted data (e.g. for the passwords) who should be handles identically between Delphi and PHP code. E.g. - I should write Delphi and PHP encryption procedures that works identically.

I opted for the AES (whose generalization Rijndael is) and here is my PHP code:

<?php
/**
 * Applies PKCS7 padding to a given string.
 *
 * @param string $data The plaintext data.
 * @param int    $blocksize The block size in bytes (default is 16).
 * @return string The padded data.
 */
function pkcs7_pad($data, $blocksize = 16) {
    $pad = $blocksize - (strlen($data) % $blocksize);
    return $data . str_repeat(chr($pad), $pad);
}

/**
 * Encrypts a plaintext string using AES-128-ECB.
 *
 * The key is hashed with SHA-256 and the first 16 bytes are used.
 * PKCS7 padding is applied to the plaintext before encryption.
 *
 * @param string $plaintext The plaintext to encrypt.
 * @param string $key       The encryption key.
 * @return string The encrypted data in Base64 encoding.
 */
function encryptAES($plaintext, $key) {
    // Hash the key with SHA-256 and use the first 16 bytes (AES-128 key length)
    $key = substr(hash('sha256', $key, true), 0, 16);

    // Pad the plaintext using PKCS7
    $padded = pkcs7_pad($plaintext);

    // Encrypt using AES-128-ECB mode
    $encryptedBytes = openssl_encrypt($padded, 'AES-128-ECB', $key, OPENSSL_RAW_DATA);

    // Return the encrypted data as a Base64 encoded string
    return base64_encode($encryptedBytes);
}

// Example usage with obfuscated test data
$testKey = "ObfuscatedTestKey123";     // Replace with your actual key
$testPlaintext = "ObfuscatedTextData";   // Replace with your actual plaintext

$encryptedBase64 = encryptAES($testPlaintext, $testKey);
echo "Encrypted (Base64): " . $encryptedBase64;
?>

And here is my Delphi code with Lockbox Rijndael:

unit MainFormU;

interface

uses
  Winapi.Windows, System.SysUtils, System.Classes, Vcl.Forms, Vcl.StdCtrls,
  LbCipher, LbClass, System.Hash, System.NetEncoding;

type
  TMainForm = class(TForm)
    btnEncrypt: TButton;
    edtPlainText: TEdit;
    lblEncrypted: TLabel;
    LbRijndael: TLbRijndael;  // Place a TLbRijndael component on your form.
    procedure btnEncryptClick(Sender: TObject);
  private
    { Private declarations }
    function PKCS7Pad(const Data: string; BlockSize: Integer): string;
    function BytesToRawString(const Bytes: TBytes): RawByteString;
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

const
  // Obfuscated test key. Replace with your actual key if needed.
  cKey = 'ObfuscatedTestKey';

{ Applies PKCS7 padding to the input string so that its length is a multiple of BlockSize. }
function TMainForm.PKCS7Pad(const Data: string; BlockSize: Integer): string;
var
  PadSize: Integer;
  PadChar: Char;
begin
  PadSize := BlockSize - (Length(Data) mod BlockSize);
  PadChar := Char(PadSize);
  Result := Data + StringOfChar(PadChar, PadSize);
end;

{ Converts a TBytes array to a RawByteString without any encoding conversion. }
function TMainForm.BytesToRawString(const Bytes: TBytes): RawByteString;
begin
  SetLength(Result, Length(Bytes));
  if Length(Bytes) > 0 then
    Move(Bytes[0], Result[1], Length(Bytes));
end;

{ Button click handler: derives a 16-byte key from a SHA-256 hash of cKey,
  applies PKCS7 padding to the plaintext, encrypts using AES-128-ECB (via TLbRijndael),
  and finally Base64-encodes the encrypted data. }
procedure TMainForm.btnEncryptClick(Sender: TObject);
var
  HashBytes, KeyBytes: TBytes;
  RawKey: RawByteString;
  Plaintext, PaddedPlaintext: string;
  EncryptedRaw: RawByteString;
  EncryptedBase64: string;
begin
  // Derive a 16-byte key from the SHA-256 hash of cKey.
  HashBytes := THashSHA2.GetHashBytes(cKey, THashSHA2.TSHA2Version.SHA256);
  SetLength(KeyBytes, 16);
  Move(HashBytes[0], KeyBytes[0], 16);
  RawKey := BytesToRawString(KeyBytes);

  // Use the text from the edit control or fallback to an obfuscated test plaintext.
  if edtPlainText.Text = '' then
    Plaintext := 'ObfuscatedPlainText'
  else
    Plaintext := edtPlainText.Text;

  // Apply PKCS7 padding to the plaintext.
  PaddedPlaintext := PKCS7Pad(Plaintext, 16);

  // Configure TLbRijndael: set ECB mode and assign the derived binary key.
  LbRijndael.CipherMode := cmECB;
  LbRijndael.SetKey(RawKey);

  // Encrypt the padded plaintext.
  EncryptedRaw := LbRijndael.EncryptString(PaddedPlaintext);

  // Base64 encode the encrypted binary data.
  EncryptedBase64 := TNetEncoding.Base64.Encode(EncryptedRaw);

  // Display the Base64-encoded encrypted string.
  lblEncrypted.Caption := EncryptedBase64;
end;

end.

Unfortunately, the results ar different between PHP and Delphi. I am not sure if I can mape Delphi Rijndael to PHP openssl_ecncrypt. E.g. PHP openssl_encrypt expects the key to be byte array, but Delphi Rijndael expects the key to be string and I am not sure whether my Delphi conversion of bytes to string is the right way to achieve that Rijndael behaves similarly to openssl_encrypt?

The same is with the result from openssl_encrypt (which is array of bytes) and from Rijndael (which is string). Should I convert the output from Rijndael back into bytes and then the base64_encode to get the string?

So - can I somehow rewrite Delphi code so, that is funcionality exactly (byte-by-byte, char-by-char) matches my PHP code? Or maybe Delphi Lockbox components are so divergent that this is not possible? I.e. divergent both from the PHP functions and from the AES standards (I guess, PHP's openssl_encrypt conforms to them?)?

Or maybe I should use completely different Delphi componets to achieve the result that is being received from PHP?

I am using Lockbox 2.08 version (at least components show me it). But I am not sure if Lockbox 3 is changed the behavior of its components radically?

6
  • I see stackoverflow.com/questions/tagged/delphi+php+encryption - there is quite a culture about it! Commented Mar 18 at 13:30
  • 3
    I personally don't have any experience using Lockbox 2.08 so I'm not sure about this but based on the fact that PHP by default only supports strings of one byte characters I suspect that your use of standard Delphi strings which are composed of double byte characters might be affecting the end result. So you should probably change all of your strings to AnsiString types. Also don't forget about your cKey constant (string literal) that in modern Delphi versions will be declared as WideString literal. So your cKey does not match the one in PHP. Commented Mar 18 at 16:15
  • 2
    Also Lockbox 2.08 is quite old so may consider using more modern Encryption library. For that I suggest you check DelphiEncryptionCompendium which is probably one of the most powerful encryption libraries for Delphi and it supports most of the modern Encryption and Hashing algorithms. Commented Mar 18 at 16:19
  • 7
    I've answered a couple of that "culture" and always wrote "encrypt bytes, not text". The same applies to this Q here: don't encrypt String anywhere. It seems to be black magic to most rookies that strings can differ as per language a lot and one should never assume the formula 1 byte = 1 character. Again: get rid of the type String in all your code and use an array of bytes (or TBytes). Problem solved. If you use bytes you automatically have to take care of how text should be encoded. Commented Mar 18 at 18:10
  • 1
    stackoverflow.com/search?q=is%3Aq+rijndael+php+delphi Commented Mar 21 at 18:01

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.