Delphi (FMX): DCPCrypt2 в Windows дает другой результат в Android / IOS

Я пытаюсь написать функцию, которая возвращает тот же результат в Delphi (RAD Studio 10.2), что и следующий фрагмент кода в PHP:

<?php
$method = 'AES-256-CTR';
$data = 'Hello, world!';
$key = 'bRuD5WYw5wd0rdHR9yLlM6wt2vteuini';
$vector = 'bf49ea9d61104d8c';
$crypt = openssl_encrypt($data, $method, $key, 0, $vector);
echo $crypt;
?>

Я придумал эту функцию в Pascal (используя библиотеку DCPcrypt v2.1, написанную Дэвидом Бартоном):

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;

И действительно, это работает (в Windows). И PHP, и Паскаль возвращают: pEP16OOxov9QDfraIg==

Однако, если я скомпилирую тот же код для Android и запустлю его на своем планшете, я получу совсем другой результат. Это почему?

Я прочитал документацию о преобразовании кода для fmx, в частности материал, который касается обработки строк, но я до сих пор не понимаю, почему. Даже если RawByteString будет основан на 0, а не на 1, я все равно получу разницу (пробовал с [0] вместо [1]). RawByteString не имеет прикрепленной кодовой страницы, верно? Таким образом, проблема не может быть вызвана некоторым преобразованием строк (я думаю). и так, что здесь происходит?

0

Решение

Строки Android начинаются с позиции 0. Вы можете использовать low(Data) это вернет первый символ строки, также библиотека внутренне использует строку из позиции 1, которая не будет работать в Android или IOS. Для мультиплатформенности мы не должны использовать for i:=1 to length(string) вместо этого мы должны использовать for l in string

Я думаю LockBox3 должен решить вашу проблему.

2

Другие решения

Поработав над этим в течение 3 дней, я наконец заработал. Ключ заключался в том, чтобы полностью исключить использование строк и использовать только процедуры на основе TBytes в DCPCrypt.
Приведенный ниже код является тестовой программой для тестирования всех различных режимов цепочки, которые поддерживает DCPCrypt. Я также добавил функцию, которая реализует четыре режима заполнения, которые я нашел здесь (для использования с CBC и ECB):
https://en.wikipedia.org/wiki/Padding_(cryptography)
а также заполнение нулями и случайное заполнение.
Я решил не использовать собственные функции Base64 DCPCrypt, потому что они оказались несовместимыми с FMX. Вместо этого я использую их аналоги из модуля System.NetEncoding.
Пожалуйста, я считаю себя обычным программистом, поэтому я ожидаю, что настоящие волшебники Delphi среди вас найдут много критики. Но это нормально. Я буду адаптировать код, если будет дан хороший отзыв.
Как и сейчас, код работает и выдает результаты, которые совместимы с функциями PHP openssl (протестировано в режиме CTR). Я публикую это здесь только в надежде, что это может пригодиться кому-то, кто ищет то же решение, что и я.

    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.
1

По вопросам рекламы [email protected]