I am trying to write a function that returns the same result in Delphi (RAD Studio 10.2) as the following piece of code in PHP:
<?php
$method = 'AES-256-CTR';
$data = 'Hello, world!';
$key = 'bRuD5WYw5wd0rdHR9yLlM6wt2vteuini';
$vector = 'bf49ea9d61104d8c';
$crypt = openssl_encrypt($data, $method, $key, 0, $vector);
echo $crypt;
?>
I have come up with this function in Pascal (using the DCPcrypt v2.1 library written by David Barton):
procedure TMainForm.Encrypt1ButtonClick(Sender: TObject);
var
Cipher: TDCP_rijndael;
Key, Vector: RawByteString;
Data, Crypt: RawByteString;
begin
Data := 'Hello, world!';
SetLength(Crypt, Length(Data));
Key := 'bRuD5WYw5wd0rdHR9yLlM6wt2vteuini';
Vector := 'bf49ea9d61104d8c';
Cipher := TDCP_rijndael.Create(nil);
try
Cipher.Init(Key[1], 256, #Vector[1]);
Cipher.EncryptCTR(Data[1], Crypt[1], Length(Data));
finally
Cipher.Free;
end;
EncryptEdit.Text := DCPBase64.Base64EncodeStr(Crypt);
end;
And indeed this works (in Windows). Both PHP and Pascal return: pEP16OOxov9QDfraIg==
However, if I compile the same code for Android and run it on my tablet, I get a very different result. Why is that?
I did read the documentation about converting code for fmx, specifically the stuff that deals with string handling, but I still don't understand why. Even if RawByteString would be 0-based instead of 1-based, I still get a difference (tried with [0] instead of [1]). A RawByteString does not have a codepage attached, right? So the problem can't be caused by some string conversion (I think). So what is going on here?
After working on this for 3 days, I finally got it working. The key was to completely eliminate the use of strings and only use the TBytes based routines in DCPCrypt.
The code below is a test program for testing all the different chaining modes that DCPCrypt supports. I also added a function that implements the four padding modes I found here (for use with CBC and ECB):
https://en.wikipedia.org/wiki/Padding_(cryptography)
as well as zero padding and random padding.
I chose not to use DCPCrypt's own Base64 functions, because they turned out not to be compatible with FMX. Instead I use their counterparts from the System.NetEncoding unit.
Please, I consider myself to be just an average programmer, so I expect the true Delphi wizzards among you to find lots to criticize. But that's ok. I will adapt the code if good feedback is given.
As it is now, the code works and produces results that are compatible with PHP's openssl functions (tested with CTR mode). I only post this here in the hopes that it might be useful to someone looking for the same solution I was.
unit MainUnit;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.NetEncoding,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Layouts,
FMX.ScrollBox, FMX.Memo, FMX.Edit, FMX.Controls.Presentation,
DCPcrypt2, DCPsha256, DCPblockciphers, DCPrijndael;
type
TChainingMode = (cmCBC, cmCFB8bit, cmCFBblock, cmOFB, cmCTR, cmECB);
TPaddingMode = (pmZeroPadding, pmANSIX923, pmISO10126, pmISO7816, pmPKCS7, pmRandomPadding);
type
TMainForm = class(TForm)
ScrollBox: TScrollBox;
KeySizeLabel: TLabel;
ChainingLabel: TLabel;
EncodingLabel: TLabel;
PaddingLabel: TLabel;
KeyLabel: TLabel;
InitVectorLabel: TLabel;
DataLabel: TLabel;
DecryptedLabel: TLabel;
CipherLabel: TLabel;
EncryptedLabel: TLabel;
rbRijndael: TRadioButton;
rb128bit: TRadioButton;
rb256bit: TRadioButton;
rbANSI: TRadioButton;
rbUTF8: TRadioButton;
rbUnicode: TRadioButton;
rbCBC: TRadioButton;
rbOFB: TRadioButton;
rbCTR: TRadioButton;
rbECB: TRadioButton;
rbCFB8bit: TRadioButton;
rbCFBblock: TRadioButton;
rbZeroPadding: TRadioButton;
rbANSIX923: TRadioButton;
rbISO10126: TRadioButton;
rbISO7816: TRadioButton;
rbPKCS7: TRadioButton;
rbRandomPadding: TRadioButton;
KeyEdit: TEdit;
InitVectorEdit: TEdit;
DataMemo: TMemo;
EncryptedMemo: TMemo;
DecryptedMemo: TMemo;
EncryptButton: TButton;
DecryptButton: TButton;
procedure FormCreate(Sender: TObject);
procedure EncryptButtonClick(Sender: TObject);
procedure DecryptButtonClick(Sender: TObject);
public
procedure GetOptions(var Key: TBytes; var KeySize: integer; var InitVector: TBytes;
var Encoding: TEncoding; var ChainingMode: TChainingMode; var PaddingMode: TPaddingMode);
end;
var
MainForm: TMainForm;
implementation
{$R *.fmx}
{$R *.LgXhdpiPh.fmx ANDROID}
function BytesToHex(B: TBytes): string;
var
I: integer;
begin
Result := '';
for I := Low(B) to High(B) do Result := Result + IntToHex(B[I]) + ' ';
end;
procedure BytePadding(var Data: TBytes; BlockSize: integer; PaddingMode: TPaddingMode);
// Supports: ANSI X.923, ISO 10126, ISO 7816, PKCS7, zero padding and random padding
var
I, DataBlocks, DataLength, PaddingStart, PaddingCount: integer;
begin
BlockSize := BlockSize div 8; // convert bits to bytes
// Zero and Random padding do not use end-markers, so if Length(Data) is a multiple of BlockSize, no padding is needed
if PaddingMode in [pmZeroPadding, pmRandomPadding] then
if Length(Data) mod BlockSize = 0 then Exit;
DataBlocks := (Length(Data) div BlockSize) + 1;
DataLength := DataBlocks * BlockSize;
PaddingCount := DataLength - Length(Data);
// ANSIX923, ISO10126 and PKCS7 store the padding length in a 1 byte end-marker, so any padding length > $FF is not supported
if PaddingMode in [pmANSIX923, pmISO10126, pmPKCS7] then
if PaddingCount > $FF then Exit;
PaddingStart := Length(Data);
SetLength(Data, DataLength);
case PaddingMode of
pmZeroPadding, pmANSIX923, pmISO7816: // fill with $00 bytes
FillChar(Data[PaddingStart], PaddingCount, 0);
pmPKCS7: // fill with PaddingCount bytes
FillChar(Data[PaddingStart], PaddingCount, PaddingCount);
pmRandomPadding, pmISO10126: // fill with random bytes
for I := PaddingStart to DataLength-1 do Data[I] := Random($FF);
end;
case PaddingMode of
pmANSIX923, pmISO10126:
Data[DataLength-1] := PaddingCount; // set end-marker with number of bytes added
pmISO7816:
Data[PaddingStart] := $80; // set fixed end-markder $80
end;
end;
procedure EncryptAES(const Data: TBytes; var Crypt: TBytes; const Key: TBytes; KeySize: integer;
const InitVector: TBytes; ChainingMode: TChainingMode; PaddingMode: TPaddingMode); overload;
var
Cipher: TDCP_rijndael;
begin
Cipher := TDCP_rijndael.Create(nil);
try
Cipher.Init(Key[0], KeySize, #InitVector[0]);
// Copy Data => Crypt
Crypt := Copy(Data, 0, Length(Data));
// Padd Crypt to required length (for Block based algorithms)
if ChainingMode in [cmCBC, cmECB] then
BytePadding(Crypt, Cipher.BlockSize, PaddingMode);
// Encrypt Crypt using the algorithm specified in ChainingMode
case ChainingMode of
cmCBC: Cipher.EncryptCBC(Crypt[0], Crypt[0], Length(Crypt));
cmCFB8bit: Cipher.EncryptCFB8bit(Crypt[0], Crypt[0], Length(Crypt));
cmCFBblock: Cipher.EncryptCFBblock(Crypt[0], Crypt[0], Length(Crypt));
cmOFB: Cipher.EncryptOFB(Crypt[0], Crypt[0], Length(Crypt));
cmCTR: Cipher.EncryptCTR(Crypt[0], Crypt[0], Length(Crypt));
cmECB: Cipher.EncryptECB(Crypt[0], Crypt[0]);
end;
finally
Cipher.Free;
end;
end;
procedure DecryptAES(const Crypt: TBytes; var Data: TBytes; const Key: TBytes; KeySize: integer;
const InitVector: TBytes; ChainingMode: TChainingMode; PaddingMode: TPaddingMode); overload;
var
Cipher: TDCP_rijndael;
I: integer;
begin
Cipher := TDCP_rijndael.Create(nil);
try
Cipher.Init(Key[0], KeySize, #InitVector[0]);
// Copy Crypt => Data
Data := Copy(Crypt, 0, Length(Crypt));
// Decrypt Data using the algorithm specified in ChainingMode
case ChainingMode of
cmCBC: Cipher.DecryptCBC(Data[0], Data[0], Length(Data));
cmCFB8bit: Cipher.DecryptCFB8bit(Data[0], Data[0], Length(Data));
cmCFBblock: Cipher.DecryptCFBblock(Data[0], Data[0], Length(Data));
cmOFB: Cipher.DecryptOFB(Data[0], Data[0], Length(Data));
cmCTR: Cipher.DecryptCTR(Data[0], Data[0], Length(Data));
cmECB: Cipher.DecryptECB(Data[0], Data[0]);
end;
// Correct the length of Data, based on the used PaddingMode (only for Block based algorithms)
if ChainingMode in [cmCBC, cmECB] then
case PaddingMode of
pmANSIX923, pmISO10126, pmPKCS7: // these modes store the original Padding count in the last byte
SetLength(Data, Length(Data) - Data[Length(Data)-1]);
pmISO7816: // this mode uses a fixed end-marker. Find it and correct length accordingly.
for I := Length(Data)-1 downto 0 do
if Data[I] = $80 then
begin
SetLength(Data, I);
Break;
end;
end;
finally
Cipher.Free;
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
EncryptedMemo.Lines.Clear;
DecryptedMemo.Lines.Clear;
end;
procedure TMainForm.GetOptions(var Key: TBytes; var KeySize: integer; var InitVector: TBytes;
var Encoding: TEncoding; var ChainingMode: TChainingMode; var PaddingMode: TPaddingMode);
begin
KeySize := 256;
Encoding := TEncoding.ANSI;
ChainingMode := cmCBC;
PaddingMode := pmPKCS7;
if rb128bit.IsChecked then KeySize := 128;
if rb256bit.IsChecked then KeySize := 256;
if rbCBC.IsChecked then ChainingMode := cmCBC;
if rbCFB8bit.IsChecked then ChainingMode := cmCFB8bit;
if rbCFBblock.IsChecked then ChainingMode := cmCFBblock;
if rbOFB.IsChecked then ChainingMode := cmOFB;
if rbCTR.IsChecked then ChainingMode := cmCTR;
if rbECB.IsChecked then ChainingMode := cmECB;
if rbZeroPadding.IsChecked then PaddingMode := pmZeroPadding;
if rbANSIX923.IsChecked then PaddingMode := pmANSIX923;
if rbISO10126.IsChecked then PaddingMode := pmISO10126;
if rbISO7816.IsChecked then PaddingMode := pmISO7816;
if rbPKCS7.IsChecked then PaddingMode := pmPKCS7;
if rbRandomPadding.IsChecked then PaddingMode := pmRandomPadding;
if rbANSI.IsChecked then Encoding := TEncoding.ANSI;
if rbUTF8.IsChecked then Encoding := TEncoding.UTF8;
if rbUnicode.IsChecked then Encoding := TEncoding.Unicode;
Key := Encoding.GetBytes(KeyEdit.Text);
InitVector := Encoding.GetBytes(InitVectorEdit.Text);
end;
procedure TMainForm.EncryptButtonClick(Sender: TObject);
var
Keysize: integer;
Encoding: TEncoding;
ChainingMode: TChainingMode;
PaddingMode: TPaddingMode;
Key, InitVector, Data, Crypt: TBytes;
begin
GetOptions(Key, KeySize, InitVector, Encoding, ChainingMode, PaddingMode);
Data := Encoding.GetBytes(DataMemo.Text);
EncryptAES(Data, Crypt, Key, KeySize, InitVector, ChainingMode, PaddingMode);
EncryptedMemo.Text := TNetEncoding.Base64.EncodeBytesToString(Crypt);
end;
procedure TMainForm.DecryptButtonClick(Sender: TObject);
var
Keysize: integer;
Encoding: TEncoding;
ChainingMode: TChainingMode;
PaddingMode: TPaddingMode;
Key, InitVector, Data, Crypt: TBytes;
begin
GetOptions(Key, KeySize, InitVector, Encoding, ChainingMode, PaddingMode);
Crypt := TNetEncoding.Base64.DecodeStringToBytes(EncryptedMemo.Text);
DecryptAES(Crypt, Data, Key, KeySize, InitVector, ChainingMode, PaddingMode);
DecryptedMemo.Text := Encoding.GetString(Data);
end;
end.
Android strings start from position 0. You can use low(Data) that will return the first character of string, also the library internally uses string from position 1, that won't run in android or ios. For multi-platform we shouldn't use for i:=1 to length(string) instead we should use for l in string
I think LockBox3 should solve your problem.
Related
I am encrypting like:
plaintextstr := "0000000000000thankustackoverflow"
plaintext := []byte(plaintextstr)
key := []byte("abcdefghijklmnop")
block, _ := aes.NewCipher(key)
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
fmt.Printf("%x\n", ciphertext)
and the output is:
00000000000000000000000000000000d77aa6646bb541808ed23c88d4b06d30f42b01d6e806a02b29086bc82892334f
but the output from another version of this code writen in PHP is:
d77aa6646bb541808ed23c88d4b06d30f42b01d6e806a02b29086bc82892334fbf21ea861abbc3d72e44731978bb76c2
Notice the 00000000000000000000000000000000 is the exact length of the missing data at the end. And it's 32, the size of the orig plaintextstr. Any idea how to left shift all that in golang and get the missing data?
PHP:
<?php
include('Crypt/AES.php');
$aes = new Crypt_AES();
$aes->setKey('abcdefghijklmnop');
echo bin2hex($aes->encrypt("0000000000000thankustackoverflow"));
https://github.com/andrewarrow/phpseclib1/blob/master/Crypt/AES.php
https://github.com/andrewarrow/phpseclib1/blob/master/Crypt/Rijndael.php
Debug php output:
00000000000000000000000000000000 <--- IV
30303030303030303030303030746861 block 0
d77aa6646bb541808ed23c88d4b06d30 crypted 0
6e6b75737461636b6f766572666c6f77 block 16
f42b01d6e806a02b29086bc82892334f crypted 16
10101010101010101010101010101010 block 32
bf21ea861abbc3d72e44731978bb76c2 crypted 32
It is not a shifting problem, it is an output buffer size problem due to the prefixing of the iv to the encrypted data.
The IV is a block of nulls (0x00) since it is never set to a value. It is prepended to the encrypted data, that is common. A block of padding needs to be added since the input data is an exact multiple of the block size.
This the output buffer needs to be the size of the iv + the size of the data + the size of the padding block. (16 + 32 + 16 = 64)
Add another block size to the output buffer:
ciphertext := make([]byte, aes.BlockSize + len(plaintext) + aes.BlockSize)
The PHP version was encoding an extra 16 bytes of an "empty" string that was filled with 16s!
p1 := "0000000000000tha"
p2 := "nkustackoverflow"
p1b := []byte(p1)
p2b := []byte(p2)
p3b, _ := hex.DecodeString("10101010101010101010101010101010")
fmt.Println(p3b)
key := []byte("abcdefghijklmnop")
block, _ := aes.NewCipher(key)
ciphertext := make([]byte, aes.BlockSize+16)
iv := ciphertext[:aes.BlockSize]
mode := cipher.NewCBCEncrypter(block, iv)
i := 0
for {
if i == 0 {
mode.CryptBlocks(ciphertext[aes.BlockSize:], p1b)
} else if i == 1 {
mode.CryptBlocks(ciphertext[aes.BlockSize:], p2b)
} else if i == 2 {
mode.CryptBlocks(ciphertext[aes.BlockSize:], p3b)
}
fmt.Printf("%x\n", ciphertext[16:])
i += 1
if i > 2 {
break
}
}
This code prints the identical stuff in golang that php is.
There are a few variations of this question around but i haven't been able to pin the problem down. Trying to encrypt/unencrypt in PHP and Delphi
I assume I have missed some setting in Delphi and its to do with UTF-8
using http://aesencryption.net/ as a PHP Example the result we are trying to get. Image Blow
Password = 123
Key = Test
128 bit
Encrypts to uuIikEZSC9Sa1HAt/XKfGQ==
I want to be able to unencrypt this in Delphi
I'm using Delphi XE5
with https://github.com/SeanBDurkin/tplockbox
I can get encrypt/DeCrypt working inside Delphi but the PHP encrypted version string is different
Delphi encrypts 123 to vpdeLlfnxTGrSsa2TpbFvg==
Here is a quick example of the Delphi Encrypt
function TForm3.EncryptV2(plainText: UTF8String): String;
var CipherText : string;
FLibrary: TCryptographicLibrary;
FCodec: TCodec;
begin
mmo1.Lines.Add('plaintext = ' + plainText);
FLibrary := TCryptographicLibrary.Create(Self);
try
FCodec := TCodec.Create(Self);
try
FCodec.CryptoLibrary := FLibrary;
FCodec.StreamCipherId := BlockCipher_ProgId;
FCodec.BlockCipherId := Format(AES_ProgId, [256]);
FCodec.ChainModeId := ECB_ProgId; ;
FCodec.UTF8Password := 'test';
FCodec.EncryptString( plainText, CipherText, Tencoding.UTF8 );
FCodec.Burn;
result := CipherText;
finally
FCodec.Free;
end;
finally
FLibrary.Free;
end;
end;
Decrypt
function TForm3.DecryptV2(encryptedText: UTF8String): String;
var plainText : string;
FLibrary: TCryptographicLibrary;
FCodec: TCodec;
begin
FLibrary := TCryptographicLibrary.Create(Self);
try
FCodec := TCodec.Create(Self);
try
FCodec.CryptoLibrary := FLibrary;
FCodec.StreamCipherId := BlockCipher_ProgId;
FCodec.BlockCipherId := Format(AES_ProgId, [256]);
FCodec.ChainModeId := ECB_ProgId; ;
FCodec.UTF8Password := 'test';
mmo1.Lines.Add('Encrypted Text = ' + encryptedText);
FCodec.DecryptString( plainText, encryptedText,Tencoding.UTF8 );
mmo1.Lines.Add('DeCrypted Text = ' + plainText);
result := plainText;
finally
FCodec.Free;
end;
finally
FLibrary.Free;
end;
end;
Anyone have any suggestions?
Not sure what is wrong with lockbox, but here is code that matches aesencryption using OpenSSL, OverbyteIcsLibeay unit is from ICS library http://wiki.overbyte.be/wiki/index.php/ICS_Download
{$APPTYPE CONSOLE}
program aestest;
uses System.SysUtils, System.NetEncoding, OverbyteIcsLibeay;
type
TKey128 = packed array [0..15] of byte;
TIV128 = packed array [0..15] of byte;
function AES128EncryptDecrypt(var Source: TBytes; const Key: TKey128;
const InitializationVector: TIV128; Encrypt: boolean): boolean;
var
IV: TIV128;
CipherCtx: PEVP_CIPHER_CTX;
Dest: TBytes;
OutLen: Integer;
begin
Result := False;
IV := InitializationVector;
LoadLibeayEx;
SetLength(Dest, Length(Source) + Length(Key));
CipherCtx := f_EVP_CIPHER_CTX_new;
try
f_EVP_CIPHER_CTX_init(CipherCtx);
if Encrypt then
begin
if f_EVP_EncryptInit_ex(CipherCtx, f_EVP_aes_128_ecb(), nil, #Key[0], #IV[0]) then
begin
Result := f_EVP_EncryptUpdate(CipherCtx, #Dest[Low(Dest)], OutLen, #Source[Low(Source)], Length(Source));
if Result then
Source := Copy(Dest, Low(Dest), OutLen);
end;
end
else
begin
if f_EVP_DecryptInit_ex(CipherCtx, f_EVP_aes_128_ecb(), nil, #Key[0], #IV[0]) then
begin
SetLength(Source, Length(Source) + Length(Key));
Result := f_EVP_DecryptUpdate(CipherCtx, #Dest[Low(Dest)], OutLen, #Source[Low(Source)], Length(Source));
if Result then
Source := Copy(Dest, Low(Dest), OutLen);
end;
end;
f_EVP_CIPHER_CTX_cleanup(CipherCtx);
finally
f_EVP_CIPHER_CTX_free(CipherCtx);
end;
end;
function AES128Encrypt(var Source: TBytes; const Key: TKey128;
const InitializationVector: TIV128): boolean;
begin
Result := AES128EncryptDecrypt(Source, Key, InitializationVector, True);
end;
function AES128Decrypt(var Source: TBytes; const Key: TKey128;
const InitializationVector: TIV128): boolean;
begin
Result := AES128EncryptDecrypt(Source, Key, InitializationVector, False);
end;
const
DefaultInitializationVector: TIV128 = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
var
B: TBytes;
KeyBytes: TBytes;
KeyPass: TKey128;
begin
// padding text with zeroes up to 16 bytes
B := TEncoding.UTF8.GetBytes('123'#0#0#0#0#0#0#0#0#0#0#0#0#0);
// encrypting
KeyBytes := TEncoding.UTF8.GetBytes('test');
Move(KeyBytes[0], KeyPass[0], Length(KeyBytes));
AES128Encrypt(B, KeyPass, DefaultInitializationVector);
Writeln(TNetEncoding.Base64.EncodeBytesToString(B));
// decrypting
AES128Decrypt(B, KeyPass, DefaultInitializationVector);
Writeln(TEncoding.UTF8.GetString(B));
end.
Also f_EVP_aes_128_ecb function is currently not included in OverbyteIcsLibeay, so you'll need to add this line to interface section
f_EVP_aes_128_ecb : function: PEVP_CIPHER; cdecl = nil;
and these lines to LoadLibeay procedure
f_EVP_aes_128_ecb := GetProcAddress(GLIBEAY_DLL_Handle, 'EVP_aes_128_ecb');
if not Assigned(f_EVP_aes_128_ecb) then
raise Exception.Create(Msg + 'EVP_aes_128_ecb');
I am trying to encrypt / decrypt a string using Rijndael from php to Delphi and back.
If I decrypt a PHP string from Delphi ... works fine.
If I encrypt a string with Delphi the result string is ok but shorter
for test I used a string with 62 character. the encrypted string with delphi is long 4 char less of PHP
these the strings ... last characters:
PHP: GyLWj1anBJRmE8mBsaO5cvTrcbvvA==
Delphi: GyLWj1anBJRmE8mBsaO5cvTrcbv
thanks for any advices
I use this source code example:
PHP:
function encrypt ($key, $value)
{
$padSize = 16 - (strlen ($value) % 16) ;
$value = $value . str_repeat (chr ($padSize), $padSize) ;
$output = mcrypt_encrypt (MCRYPT_RIJNDAEL_128, $key, $value, MCRYPT_MODE_CBC, 'xxxxxxx') ;
return base64_encode ($output) ;
}
Delphi encrypt:
function EncryptData3(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
cipher: TDCP_rijndael;
key, iv, src, dest, b64: TBytes;
index, slen, bsize, pad: integer;
begin
//key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
//iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
key := TEncoding.ASCII.GetBytes(AKey);
iv := TEncoding.ASCII.GetBytes(AIv);
src := TEncoding.ascii.GetBytes(Data);
cipher := TDCP_rijndael.Create(nil);
try
cipher.CipherMode := cmCBC;
// Add padding.
// Resize the Value array to make it a multiple of the block length.
// If it's already an exact multiple then add a full block of padding.
slen := Length(src);
bsize := (cipher.BlockSize div 8);
pad := bsize - (slen mod bsize);
Inc(slen, pad);
SetLength(src, slen);
for index := pad downto 1 do
begin
src[slen - index] := pad;
end;
SetLength(dest, slen);
cipher.Init(key[0], 256, #iv[0]); // DCP uses key size in BITS not BYTES
cipher.Encrypt(src[0], dest[0], slen);
b64 := Base64EncodeBytes(dest);
result := TEncoding.Default.GetString(b64);
finally
cipher.Free;
end;
end;
Delphi decrypt ... not works:
function DecryptData3(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
key, iv, src, dest: TBytes;
cipher: TDCP_rijndael;
slen, pad: integer;
begin
//key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
//iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
key := TEncoding.ASCII.GetBytes(AKey);
iv := TEncoding.ASCII.GetBytes(AIv);
src := Base64DecodeBytes(TEncoding.UTF8.GetBytes(Data));
cipher := TDCP_rijndael.Create(nil);
try
cipher.CipherMode := cmCBC;
slen := Length(src);
SetLength(dest, slen);
cipher.Init(key[0], 256, #iv[0]); // DCP uses key size in BITS not BYTES
cipher.Decrypt(src[0], dest[0], slen);
// Remove the padding. Get the numerical value of the last byte and remove
// that number of bytes
pad := dest[slen - 1];
SetLength(dest, slen - pad);
// Base64 encode it
result := TEncoding.Default.GetString(dest);
finally
cipher.Free;
end;
end;
I don't know if I used a correct way ... but if I convert the Bytes Value in String and I use this Base64Encode in this link:
Simple code to encrypt an .INI file string using a password
now I encrypt correctly. this is the example:
SetString(stringValue, PAnsiChar(#dest[0]), slen);
result := Base64Encode2(stringValue);
We have php code:
define('myaesKey', 'znwoq8fq0jf2qjve8laper9f'); // 192 bits and 25 ch.
function encode($CodeTo) {
$Type = 'rijndael-128';
$Mode = 'ecb';
$IV = "1234567890123450";
$Object = mcrypt_module_open($Type, '', $Mode, '');
mcrypt_generic_init($Object , myaesKey, $IV);
$Enc2Code = mcrypt_generic($Object , $CodeTo);
mcrypt_generic_deinit($Object);
mcrypt_module_close($Object);
return bin2hex($secEncCode);
}
Length of $CodeTo is 5, CodeTo is readable symbols of English Alphabet, function send somethind like this
1e49651ba23801907e1d67c5a7c18e06
aefdc02bbcb8ed8e8209a935aa62be53
I tried to decode by diff. ways, one of this :
const
KeySize = 24; // 32 bytes = 256 bits 24 - 192
BlockSize = 16; // 16 bytes = 128 bits
function Decrypt(AText:AnsiString):String;
var
Cipher : TDCP_rijndael; i:Integer;
Data, Key, IV,NewStr : ansistring;
begin
// Pad Key and IV with zeros as appropriate
Key := PadWithZeros(ansistring('znwoq8fq0jf2qjve8laper9f'),KeySize);
IV := PadWithZeros(ansistring('1234567890123450'),BlockSize);
// Decode the Base64 encoded string
NewStr:='';
for i:=1 to (Length(AText) div 2) do
NewStr:=NewStr+chr(byte(StrToInt('$'+Copy(AText,(i-1)*2+1,2))));
Data := NewStr;
// Create the cipher and initialise according to the key length
Cipher := TDCP_rijndael.Create(nil);
if Length(ansistring('znwoq8fq0jf2qjve8laper9f')) <= 16 then
Cipher.Init(Key[1],128,#IV[1])
else if Length(ansistring('znwoq8fq0jf2qjve8laper9f')) <= 24 then
Cipher.Init(Key[1],192,#IV[1])
else
Cipher.Init(Key[1],256,#IV[1]);
// Decrypt the data
// Cipher.DecryptCBC(Data[1],Data[1],Length(Data));
Cipher.DecryptECB(Data[1],Data[1]);
// Free the cipher and clear sensitive information
Cipher.Free;
FillChar(Key[1],Length(Key),0);
// Display the result
result:= Data;
end;
but the decoded text is wrong
6d309aab9887deed8da964cca8818eb4
µ€ц‰ъиTDHQ ЮB№еП
Why?
Can someone help? Easy to decode it by http://www.tools4noobs.com/online_tools/decrypt/ withot IV ...
Try to use this
function AESDecrypt(AData, AKey: String): string;
var
KeyByte,Data,Dest:TBytes;
KeyBlock:integer;
Cipher:TDCP_rijndael;
begin
KeyByte:=TEncoding.UTF8.GetBytes(AKey);
while (Length(KeyByte) mod 16 <> 0) do begin
SetLength(KeyByte,Length(KeyByte)+1);
KeyByte[Length(KeyByte)-1]:=0;
end;
SetLength(Data,Length(AData) div 2);
SetLEngth(Dest,Length(AData) div 2);
Data:=GetBytesFromHex(AData);
Cipher:= TDCP_rijndael.Create(nil);
KeyBlock:=192; //by PHP code comment
Cipher.Init(KeyByte[0],KeyBlock,nil); //for ECB method IV is optional
try
for i := 1 to (Length(AData) div 16) do
begin
Cipher.DecryptECB(Data[(i-1)*16],Dest[(i-1)*16]);
end;
finally
Cipher.Burn;
end;
AData:=TEncoding.UTF8.GetString(Dest);
Result:=AData;
end;
I have problems with decrypting strings sent from PHP to Delphi using the rijndael cipher.
I'm using mcrypt on the PHP side and DCP_rijndael on the Delphi side.
At the moment I have the below code.
PHP:
function encRJ($key, $iv, $data)
{
$r = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
$r = base64_encode($r);
return $r;
}
And in Delphi:
function decRJ(Data: string; Key: string; IV: string): string;
var ciph: TDCP_rijndael;
begin
Data := Base64DecodeStr(Data);
ciph:= TDCP_rijndael.Create(Self);
ciph.Init(Key[1], 256, #IV[1]);
ciph.DecryptCBC(Data[1], Data[1], Length(Data));
ciph.Free;
Result := Data;
end;
I have tried using several Units on the Internet implementing the cipher, and found out most people are saying about the DCP components. Even so, I haven't managed to make it correctly decrypt. I've tried using Byte arrays for the parameters, AnsiStrings, WideStrings, etc, but unfortunately no luck.
Excuse me if I'm missing something really obvious here, as my mind isn't in good shape atm, after hours of searching for the matter.
I seem to have spent too long on this but...
Your problem is the block size. TDCP_rijndael is equivalent to MCRYPT_RIJNDAEL_128 (not _256). The '256' value in ciph.Init(...) call is still correct though. Other than that it looks pretty much ok. That is, assuming you're using ansistrings for key/iv or you're using non-unicode Delphi.
For unicode Delphi versions I'd be inclined to use TBytes and key[0] / iv[0].
Padding may still be an issue. If so, then here's what I've mangled up based on the PHP manual pages and some trial and error.
PHP:
function Encrypt($src, $key, $iv)
{
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
//echo "Block size: " . $block . "\r\n";
$pad = $block - (strlen($src) % $block);
$src .= str_repeat(chr($pad), $pad);
$enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $src, MCRYPT_MODE_CBC, $iv);
$r = base64_encode($enc);
return $r;
}
function Decrypt($src, $key, $iv)
{
$enc = base64_decode($src);
$dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv);
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, 'cbc');
$pad = ord($dec[($len = strlen($dec)) - 1]);
return substr($dec, 0, strlen($dec) - $pad);
}
Delphi:
function DecryptData(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
key, iv, src, dest: TBytes;
cipher: TDCP_rijndael;
slen, pad: integer;
begin
//key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
//iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
key := TEncoding.ASCII.GetBytes(AKey);
iv := TEncoding.ASCII.GetBytes(AIv);
src := Base64DecodeBytes(TEncoding.UTF8.GetBytes(Data));
cipher := TDCP_rijndael.Create(nil);
try
cipher.CipherMode := cmCBC;
slen := Length(src);
SetLength(dest, slen);
cipher.Init(key[0], 256, #iv[0]); // DCP uses key size in BITS not BYTES
cipher.Decrypt(src[0], dest[0], slen);
// Remove the padding. Get the numerical value of the last byte and remove
// that number of bytes
pad := dest[slen - 1];
SetLength(dest, slen - pad);
// Base64 encode it
result := TEncoding.Default.GetString(dest);
finally
cipher.Free;
end;
end;
function EncryptData(Data: string; AKey: AnsiString; AIv: AnsiString): string;
var
cipher: TDCP_rijndael;
key, iv, src, dest, b64: TBytes;
index, slen, bsize, pad: integer;
begin
//key := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AKey));
//iv := Base64DecodeBytes(TEncoding.UTF8.GetBytes(AIv));
key := TEncoding.ASCII.GetBytes(AKey);
iv := TEncoding.ASCII.GetBytes(AIv);
src := TEncoding.UTF8.GetBytes(Data);
cipher := TDCP_rijndael.Create(nil);
try
cipher.CipherMode := cmCBC;
// Add padding.
// Resize the Value array to make it a multiple of the block length.
// If it's already an exact multiple then add a full block of padding.
slen := Length(src);
bsize := (cipher.BlockSize div 8);
pad := bsize - (slen mod bsize);
Inc(slen, pad);
SetLength(src, slen);
for index := pad downto 1 do
begin
src[slen - index] := pad;
end;
SetLength(dest, slen);
cipher.Init(key[0], 256, #iv[0]); // DCP uses key size in BITS not BYTES
cipher.Encrypt(src[0], dest[0], slen);
b64 := Base64EncodeBytes(dest);
result := TEncoding.Default.GetString(b64);
finally
cipher.Free;
end;
end;
The PHP and Delphi functions now give me the same answer.
EDIT
Base64DecodeBytes was a bit of code I added to the DCP Base64 unit:
function Base64DecodeBytes(Input: TBytes): TBytes;
var
ilen, rlen: integer;
begin
ilen := Length(Input);
SetLength(result, (ilen div 4) * 3);
rlen := Base64Decode(#Input[0], #result[0], ilen);
// Adjust the length of the output buffer according to the number of valid
// b64 characters
SetLength(result, rlen);
end;
EDIT 2018 (Raising the dead...):
As requested, here is the encoding method, unchecked and pulled straight from an old source file I found.
DISCLAIMER: It is many years old and untested in recent memory and not used since Delphi 2010. There are probably many better alternatives now. Use at your own risk.
function Base64EncodeBytes(Input: TBytes): TBytes;
var
ilen: integer;
begin
ilen := Length(Input);
SetLength(result, ((ilen + 2) div 3) * 4);
Base64Encode(#Input[0], #result[0], ilen);
end;
Neither your PHP nor your Delphi methods appear to specify any padding. If the default paddings are different then you will get problems. Explicitly specify PKCS7 (or PKCS5) for both.
GregS' comment about the result of decoding Base64 is correct. You are supplying encrypted cyphertext to your decRJ() method. That will be random appearing bytes. Attempting to convert it to UTF-8 will mangle it enough that it cannot be decrypted. The incoming cyphertext must be converted from Base64 direct to a byte array. Cyphertext is not a character string, which is why it needs to be converted to Base64 to be transmitted as text. It will only be text again after it has been decrypted back to plaintext.