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?
cKeyconstant (string literal) that in modern Delphi versions will be declared as WideString literal. So yourcKeydoes not match the one in PHP.Stringanywhere. It seems to be black magic to most rookies that strings can differ as per language a lot and one should never assume the formula1 byte = 1 character. Again: get rid of the typeStringin all your code and use an array of bytes (orTBytes). Problem solved. If you use bytes you automatically have to take care of how text should be encoded.