Задача обеспечения идентичного шифрования данных между Delphi и PHP для совместимости баз данных является распространенной, особенно когда речь идет о хранении конфиденциальной информации, такой как пароли. В контексте вопроса используется AES (Rijndael) в режиме ECB с PKCS7 padding. Проблема заключается в том, что предоставленные примеры кода на Delphi (с использованием Lockbox) и PHP (с использованием openssl_encrypt) дают разные результаты.
Анализ проблемы и предложенное решение
Основная причина расхождений, как правильно заметили в комментариях к оригинальному вопросу, кроется в нескольких факторах:
Типы данных "String": Delphi String (в современных версиях) является UnicodeString, то есть использует двухбайтовую кодировку UTF-16. PHP же по умолчанию работает с однобайтовыми строками. Это может привести к несовместимости ключей и данных.
Кодировка ключа: openssl_encrypt в PHP ожидает ключ в виде массива байт, а Lockbox (TLbRijndael) в Delphi принимает ключ в виде строки. Преобразование байт в строку (и обратно) может быть выполнено некорректно и привести к расхождениям.
Версия Lockbox: Lockbox 2.08 - довольно старая версия. Возможно, существуют различия в реализации по сравнению с более современными библиотеками, следующими стандартам AES.
Обработка результатов шифрования: openssl_encrypt возвращает массив байт, а EncryptString в Lockbox - строку. Неправильная конвертация в Base64 может привести к разным результатам.
Решение: Использование TBytes и явная работа с байтами
Наиболее надежное решение - полностью отказаться от использования типа String для данных, подлежащих шифрованию и ключей. Вместо этого используйте TBytes (динамический массив байт) в Delphi и массивы байт в PHP.
Delphi (с использованием Delphi Encryption Compendium - DEC)
Рекомендуется использовать Delphi Encryption Compendium (DEC) вместо Lockbox 2.08, так как DEC - это современная и активно поддерживаемая библиотека, предоставляющая более надежную реализацию AES.
uses
System.SysUtils, System.Classes, System.NetEncoding,
DECUtil, DECCrypt, DECCipher, DECBase64, DECAES;
function PKCS7Pad(const Data: TBytes; BlockSize: Integer): TBytes;
var
PadSize: Integer;
i: Integer;
begin
PadSize := BlockSize - (Length(Data) mod BlockSize);
SetLength(Result, Length(Data) + PadSize);
Move(Data[0], Result[0], Length(Data));
for i := 0 to PadSize - 1 do
Result[Length(Data) + i] := Byte(PadSize);
end;
function EncryptAES_DEC(Plaintext: string; Key: string): string;
var
AES: TDECAES;
KeyBytes, PlaintextBytes, PaddedBytes, EncryptedBytes: TBytes;
HashBytes: TBytes;
begin
// 1. Получаем байты ключа из SHA-256 хеша
HashBytes := THashSHA2.GetHashBytes(Key, THashSHA2.TSHA2Version.SHA256);
SetLength(KeyBytes, 16);
Move(HashBytes[0], KeyBytes[0], 16);
// 2. Преобразуем plaintext в TBytes (предполагаем UTF-8 кодировку)
PlaintextBytes := TEncoding.UTF8.GetBytes(Plaintext);
// 3. Применяем PKCS7 padding
PaddedBytes := PKCS7Pad(PlaintextBytes, 16);
// 4. Шифруем с помощью DEC
AES := TDECAES.Create(nil);
try
AES.Initialize(KeyBytes, amCBC128, cpPKCS7); // ECB не рекомендуется, но для совместимости
AES.Encrypt(PaddedBytes, EncryptedBytes);
finally
AES.Free;
end;
// 5. Кодируем в Base64
Result := TNetEncoding.Base64.EncodeBytesToString(EncryptedBytes);
end;
// Пример использования
procedure TForm1.Button1Click(Sender: TObject);
var
Encrypted: string;
begin
Encrypted := EncryptAES_DEC('ObfuscatedTextData', 'ObfuscatedTestKey123');
ShowMessage(Encrypted);
end;
PHP (как в исходном вопросе)
<?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;
?>
Ключевые моменты:
UTF-8 кодировка: В Delphi используется TEncoding.UTF8.GetBytes для преобразования строки в байты. Убедитесь, что в PHP также используется UTF-8 кодировка для plaintext.
PKCS7 Padding: Функции PKCS7Pad в Delphi и PHP должны быть идентичными.
SHA-256 Hashing: Используйте одинаковую реализацию SHA-256 в Delphi и PHP для получения ключа. В примерах используется THashSHA2 в Delphi и hash('sha256', $key, true) в PHP. Аргумент true в PHP hash() обеспечивает возврат бинарного представления хеша.
Base64 Encoding: Используйте TNetEncoding.Base64.EncodeBytesToString в Delphi и base64_encode в PHP.
Режим ECB: Режим ECB (Electronic Codebook) не рекомендуется для большинства случаев из-за его уязвимости к атакам. Рассмотрите возможность использования более безопасного режима, такого как CBC (Cipher Block Chaining) или CTR (Counter). В примере с DEC, я использовал amCBC128 для CBC, но закомментировал, что для совместимости можно использовать ECB. Если используете CBC, убедитесь, что используете одинаковый IV (Initialization Vector) в Delphi и PHP.
Альтернативное решение: Использование gSOAP
Если требуется более сложная интеграция между Delphi и PHP, можно рассмотреть использование gSOAP. gSOAP позволяет создавать веб-сервисы, которые могут быть вызваны как из Delphi, так и из PHP. Это позволит инкапсулировать логику шифрования на стороне сервера и обеспечить более надежный и безопасный обмен данными.
Важно:
Тщательно протестируйте код шифрования и дешифрования в Delphi и PHP с различными тестовыми данными, чтобы убедиться в их идентичности.
Убедитесь, что используете безопасные ключи шифрования и храните их в безопасном месте.
Рассмотрите возможность использования более безопасных алгоритмов шифрования и режимов работы, если это возможно.
Следуя этим рекомендациям, вы сможете добиться идентичного AES-128-ECB шифрования в Delphi и PHP, обеспечив совместимость ваших баз данных. Помните, что безопасность - это непрерывный процесс, требующий постоянного внимания и обновления.
Контекст описывает проблему несовместимости AES-128-ECB шифрования между Delphi и PHP при работе с базами данных и предлагает решение, основанное на использовании байтовых массивов, SHA-256 хеширования ключа и библиотеки Delphi Encryption Compendium (DEC
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.