I have a trouble with lockbox3 and PHP mcrypt. I can\'t pass IV to PHP. Delphi code:
var
Codec: TCodec;
CL: TCryptographicLibrary;
PlainStream: TString
You haven't said what version of Delphi you are using. This is an important detail. For the moment, I will assume it is Delphi 2010. There are a number of problems with your code. I will address them...
(1) In Delphi 2010 and later, strings are encoded in UTF-16LE, whilst in PHP, strings are UTF-8. Consider this line of yours...
PlainStream := TStringStream.Create(Edit1.Text);
What you are doing is creating the UTF-16LE encoding of your payload string. You encrypt this and pass it over to the PHP side and decrypt it. But you are not going to get the expected result because the decrypted bytes are UTF-16LE, but PHP expects them to be UTF-8.
(2) TP Lockbox 3 already had native methods for encrypting strings. Why not use them?
(3) The block size for all 3 variations of AES is 128 bits, which is 16 bytes. The size of the IV is always the size of the block. On the PHP side, as a matter of generic coding, you should always call mcrypt_enc_get_iv_size() (you did not). Please read the reference page here. In any case, for AES-256 must return 16 bytes. If not something is seriously wrong.
(4) Your passwords are not the same, so you can never reasonably expect a happy result. On the Delphi side, your password is encoded in UTF-16LE. On the PHP side, your password is the UTF-8 encoding of 'PasswordPassword', which can never be byte-for-byte equal to something valid in UTF-16.
(5) On the PHP side, you wrote..
$iv = substr($ciphertext, 0, 8);
You need to zero-extend this out to 16 bytes. Refer to this question.
As promised, here is some PHP code to decrypt ciphertext messages produced by TurboPower LockBox 3. You will need to craft a css file, otherwise presentation will be ugly.
Delphi-to-PHP Cryptography Tutorial
Decrypt with PHP from Delphi (TurboPower LockBox3)
' before each $0D character.
return $str;
}
function hexToStr($hex)
{
$hex2 = purgeWhiteSpace( $hex);
$str='';
for ($i=0; $i < strlen($hex2)-1; $i+=2)
{
$str .= chr(hexdec($hex2[$i].$hex2[$i+1]));
}
return $str;
}
function strToHex($str)
{
$hex='';
for ($i=0; $i < strlen($str); $i++)
{
$addend = dechex(ord($str[$i]));
if (strlen($addend) < 2)
$addend = '0' . $addend;
$hex .= $addend;
}
return $hex;
}
$normalisedRawCiphertext = purgeWhiteSpace( $rawInputCiphertext);
if ($ciphertext_trans == 'base64')
{
$ciphertext = base64_decode( $normalisedRawCiphertext);
}
else
{
$ciphertext = hexToStr( $normalisedRawCiphertext);
}
if ($cipherIn == 'AES-128')
{
$cipher = MCRYPT_RIJNDAEL_128;
$cipherName = 'AES-128';
}
else
{
// Extend here with more ciphers as required. Note: PHP does not support AES-256.
$cipher = MCRYPT_RIJNDAEL_128; // Example only.
$cipherName = '???'; // Example only.
}
if ($chain == 'CFB')
$mode = 'ncfb'; // Proper block-mode CFB. There is no constant for this.
else if ($chain == 'CBC')
$mode = MCRYPT_MODE_CBC;
else
$mode = MCRYPT_MODE_ECB;
$blockSize = mcrypt_get_block_size( $cipher, $mode);
$keySize = mcrypt_get_key_size( $cipher, $mode);
// Work-around PHP bugs.
if (($cipher == MCRYPT_RIJNDAEL_128) and ($keySize == 32))
{ $keySize = 16; } // AES-128 key size is 16 bytes.
if (($cipher == MCRYPT_RIJNDAEL_256) and ($blockSize == 32))
{ $blockSize = 16; } // AES-256 block size is 16 bytes.
$ivSize = $blockSize; // Always. mcrypt_get_iv_size() is pointless.
if ($chain == 'ECB')
{
$iv = str_pad( 'NOT USED', 16, chr(0));
// $ciphertext unchanged.
}
else
{
$iv = substr( $ciphertext, 0, 8);
$iv = str_pad( $iv, $ivSize, chr(0));
$ciphertext = substr( $ciphertext, 8);
}
$ciphertextLen = strlen( $ciphertext);
if (($ciphertextLen > 0) && ($ciphertextLen < $blockSize) && ($chain == 'CBC'))
{ $mode = MCRYPT_MODE_CFB; } // CFB 8-bit. This is NOT the same as CFB.
if (strlen($password)==$keySize)
{
$key = $password;
}
else
{
$shaPassword = sha1( $password, True);
for ($key = ''; strlen( $key) < $keySize; $key .= $shaPassword) {}
$key = substr( $key, 0, $keySize);
}
$countBlocks = $ciphertextLen / $blockSize;
$countWholeBlocks = floor( $countBlocks);
$isRound = $countBlocks == $countWholeBlocks;
if ($isRound)
{
$lastBlockSize = 0;
}
else
{
$countBlocks = $countWholeBlocks + 1;
$lastBlockSize = $ciphertextLen - ($countWholeBlocks * $blockSize);
}
$isCipherStealing = ($mode == MCRYPT_MODE_CBC) && ($countWholeBlocks >= 1) && !$isRound;
if ($isCipherStealing)
{ // Reverse ciphertext stealing.
/*
Ciphertext stealing algorithm - Encryption:
Mix := Enc( CV[N-2], X[N-2]);
Steal := Last( B-b, Mix);
Recycle := X[N-1] + Steal;
Y[N-2] := Enc( CV[N-2], Recycle);
Y[N-1] := Head( b, Mix);
Ciphertext stealing algorithm - Decryption:
Recycle := Dec( CV[N-2], Y[N-2]);
Steal := Last( B-b, Recycle);
Mix := Y[N-1] + Steal;
X[N-2] := Dec( CV[N-2], Mix);
X[N-1] := Head( b, Recycle);
*/
// 1. Recycle := Dec( CV[N-2], Y[N-2]);
$Recycle = mcrypt_decrypt ( $cipher, $key, substr( $ciphertext, 0, $countWholeBlocks * $blockSize), $mode, $iv);
$reconUpToX_N_3 = substr( $Recycle, 0, ($countWholeBlocks - 1) * $blockSize); // X[0]..X{N-3]
$Recycle = substr( $Recycle, ($countWholeBlocks - 1) * $blockSize, $blockSize);
// 2. Steal := Last( B-b, Recycle);
$Steal = substr( $Recycle, $lastBlockSize, $blockSize - $lastBlockSize);
// 3. Mix := Y[N-1] + Steal;
$Y_N1 = substr( $ciphertext, $countWholeBlocks * $blockSize, $lastBlockSize);
$Mix = $Y_N1 . $Steal;
// 4. X[N-2] := Dec( CV[N-2], Mix);
$reconUpToX_N_2 = mcrypt_decrypt ( $cipher, $key, substr( $ciphertext, 0, ($countWholeBlocks - 1) * $blockSize) . $Mix, $mode, $iv);
// 5. X[N-1] := Head( b, Recycle);
$reconX_N_1 = substr( $Recycle, 0, $lastBlockSize);
// Putting it alltogether.
$recon = $reconUpToX_N_2 . $reconX_N_1;
}
else
{ // Normal decyrption.
$recon = mcrypt_decrypt ( $cipher, $key, $ciphertext, $mode, $iv);
}
if (($chain == 'ECB') and ($recon != ''))
{ // Trim ECB padding.
$last = strlen($recon);
for ($l = strlen($recon); ($l >= 0) and (ord($recon[$l])==0); $l--)
{$last = $l;}
$recon = substr( $recon, 0, $last-1);
}
?>
Output
Summary2
Cipher is
Block size is bytes
Given ciphertext was a round blocks long.
Given ciphertext was a whole blocks long and bytes in an odd block.
Key size is bytes
Given chain mode was
Given password was ''
Ciphertext as hex is...
Reconstructed plaintext message is ''
Debug
Key as hex is...
IV as hex is...
$countBlocks =
$countWholeBlocks =
$isRound =
$isCipherStealing =
$lastBlockSize =
$Recycle =
$recon X[0..N-3] =
$Steal =
$Mix =
$recon X[0..N-2] =
$recon X[N-1] =
Reconstructed plaintext as hex is...
... and here is a matching Delphi program to produce the ciphertext messages for the preceding PHP web page, for test and demonstration purposes. (DFM file not included)...
unit umfmDelphi_to_PHP_Symetric;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ActnList, StdCtrls, uTPLb_Codec, uTPLb_BaseNonVisualComponent,
uTPLb_CryptographicLibrary, ExtCtrls;
type
TmfmDelphi_to_PHP_Symetric = class(TForm)
rgTestVectors: TRadioGroup;
rgChainMode: TRadioGroup;
edtPassword: TEdit;
memoPlaintext: TMemo;
lblPassword: TLabel;
lblPlaintext: TLabel;
cryptoMain: TCryptographicLibrary;
codecAES: TCodec;
memoOutput: TMemo;
btnEncrypt: TButton;
actlstMain: TActionList;
actEncrypt: TAction;
edtSeed: TEdit;
lblSeed: TLabel;
btnRandomize: TButton;
actRandomize: TAction;
rgCipher: TRadioGroup;
procedure actEncryptUpdate(Sender: TObject);
procedure actEncryptExecute(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure rgTestVectorsClick(Sender: TObject);
procedure rgChainModeClick(Sender: TObject);
procedure actRandomizeUpdate(Sender: TObject);
procedure actRandomizeExecute(Sender: TObject);
private
procedure LogFmt( const sLine: string; const Args: array of const);
function SpaceOut( const sCompacted: string): string;
public
{ Public declarations }
end;
var
mfmDelphi_to_PHP_Symetric: TmfmDelphi_to_PHP_Symetric;
implementation
uses uTPLb_Random, uTPLb_StreamUtils, uTPLb_Constants;
{$R *.dfm}
function StreamToHex( Data: TStream): string;
var
b: byte;
sByte: string;
begin
Data.Position := 0;
result := '';
while Data.Read( b, 1) = 1 do
begin
sByte := Format( '%x', [b]);
if Odd( Length( sByte)) then
sByte := '0' + sByte;
result := result + sByte
end
end;
procedure TmfmDelphi_to_PHP_Symetric.actEncryptExecute( Sender: TObject);
const
TestCaseNames: array[0..2] of string = ('Test Vector 1', 'Test Vector 2', 'Custom');
var
usPlaintext: UTF8String;
aCiphertext: ansistring;
OriginalSeed: int64;
stmCipher: TStream;
sHex: string;
begin
memoOutput.Clear;
case rgCipher.ItemIndex of
0: codecAES.BlockCipherId := Format( AES_ProgId, [128]);
end;
case rgChainMode.ItemIndex of
0: codecAES.ChainModeId := CFB_ProgId;
1: codecAES.ChainModeId := CBC_ProgId;
2: codecAES.ChainModeId := ECB_ProgId;
end;
codecAES.UTF8Password := edtPassword.Text;
usPlaintext := memoPlaintext.Lines.Text;
OriginalSeed := StrToInt64( edtSeed.Text);
TRandomStream.Instance.Seed := OriginalSeed;
codecAES.EncryptAnsiString( usPlaintext, aCiphertext);
// NextSeed := TRandomStream.Instance.Seed;
LogFmt( 'Test case = %s', [TestCaseNames[rgTestVectors.ItemIndex]]);
LogFmt( 'Cipher = %s', [codecAES.Cipher]);
LogFmt( 'Chain mode = %s', [codecAES.ChainMode]);
LogFmt( 'PRNG seed = %d', [OriginalSeed]);
LogFmt( 'Passord (UTF-8) = ''%s''', [codecAES.UTF8Password]);
LogFmt( '------------', []);
stmCipher := TMemoryStream.Create;
codecAES.Key.SaveToStream( stmCipher);
sHex := StreamToHex( stmCipher);
stmCipher.Free;
LogFmt( 'key as hex = %s', [sHex]);
LogFmt( 'Plaintext (UTF-8)', []);
LogFmt( '''%s''', [usPlaintext]);
LogFmt( '------------', []);
LogFmt( 'ciphertext (base64) [Includes prepended IV and block quantisation] =', []);
LogFmt( ' ''%s''', [ SpaceOut( aCiphertext)]);
LogFmt( '------------', []);
stmCipher := TMemoryStream.Create;
Base64_to_stream( aCiphertext, stmCipher);
sHex := StreamToHex( stmCipher);
stmCipher.Free;
LogFmt( 'ciphertext (hex) [Includes prepended IV and block quantisation] =', []);
LogFmt( ' ''%s''', [ SpaceOut( sHex)]);
LogFmt( '------------', []);
end;
procedure TmfmDelphi_to_PHP_Symetric.actEncryptUpdate( Sender: TObject);
begin
//
end;
procedure TmfmDelphi_to_PHP_Symetric.actRandomizeExecute(Sender: TObject);
begin
TRandomStream.Instance.Randomize;
edtSeed.Text := IntToStr( TRandomStream.Instance.Seed)
end;
procedure TmfmDelphi_to_PHP_Symetric.actRandomizeUpdate(Sender: TObject);
begin
(Sender as TAction).Enabled := rgTestVectors.ItemIndex = 2
end;
procedure TmfmDelphi_to_PHP_Symetric.FormCreate( Sender: TObject);
begin
memoOutput.Clear;
LogFmt( 'Select test case and chain mode.', []);
LogFmt( 'Enter password and plaintext message and then press the ''Encrypt'' button.', []);
end;
procedure TmfmDelphi_to_PHP_Symetric.LogFmt(
const sLine: string; const Args: array of const);
begin
memoOutput.Lines.Add( Format( sLine, Args))
end;
procedure TmfmDelphi_to_PHP_Symetric.rgChainModeClick( Sender: TObject);
begin
//
end;
procedure TmfmDelphi_to_PHP_Symetric.rgTestVectorsClick( Sender: TObject);
var
isCustom: boolean;
begin
case rgTestVectors.ItemIndex of
0: begin
edtPassword.Text := 'Your lips are smoother than vasoline.';
memoPlaintext.Lines.Text := 'Leeeeeeeeeroy Jenkins!';
// Above is constrained to:
// More than 16 and not a whole multiple of 16 bytes as UTF-8.
edtSeed.Text := '1';
rgChainMode.ItemIndex := 0;
rgCipher.ItemIndex := 0;
end;
1: begin
edtPassword.Text := 'ORATIO IN L. CATILINAM PRIMA';
memoPlaintext.Lines.Text := 'Quo usque tandem abutere, Catili';
// Above is constrained to:
// A whole multiple of 16 bytes as UTF-8, excluding the empty case.
edtSeed.Text := '333';
rgChainMode.ItemIndex := 0;
rgCipher.ItemIndex := 0
end;
2: ;
end;
isCustom := rgTestVectors.ItemIndex = 2;
edtPassword.ReadOnly := not isCustom;
memoPlaintext.ReadOnly := not isCustom;
edtSeed.ReadOnly := not isCustom;
rgChainMode.Enabled := isCustom;
rgCipher.Enabled := isCustom
end;
function TmfmDelphi_to_PHP_Symetric.SpaceOut( const sCompacted: string): string;
const
NewLineSpacing = 70;
BunchSpacing = 6;
var
i, j: integer;
begin
SetLength( result, 2 * Length( sCompacted));
i := 1;
for j := 1 to Length( sCompacted) do
begin
if ((j mod NewLineSpacing) = 1) and (j <> 1) then
begin
result[i] := #13;
Inc( i);
result[i] := #10;
Inc( i)
end
else if ((j mod BunchSpacing) = 1) and (j <> 1) then
begin
result[i] := ' ';
Inc( i)
end;
result[i] := sCompacted[j];
Inc( i)
end;
SetLength( result, i - 1)
end;
end.