В настоящее время я работаю над переводом алгоритма шифрования с PHP на Typescript для использования в очень специфическом API, который требует, чтобы публикуемые данные были зашифрованы с помощью ключа API и секрета. Вот предоставленный пример того, как правильно зашифровать данные в PHP для использования с API (способ реализации ключа и IV не может быть изменен):
$iv = substr(hash("SHA256", $this->ApiKey, true), 0, 16);
$key = md5($this->ApiSecret);
$output = openssl_encrypt($Data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
$completedEncryption = $this->base64Url_Encode($output);
return $completedEncryption;
В приведенном выше коде единственное, что делает функция base64Url_Encode, — это преобразование двоичных данных в допустимую строку Base64URL.
А теперь код, как я его реализовал внутри Typescript:
import { createHash, createCipheriv } from 'node:crypto'
const secretIV = createHash('sha256').update(this.ApiKey).digest().subarray(0, 16)
// Generate key
/*
Because the OpenSSL function in PHP automatically pads the string with /null chars,
do the same inside NodeJS, so that CreateCipherIV can accept it as a 32-byte key,
instead of a 16-byte one.
*/
const md5 = createHash('md5').update(this.ApiSecret).digest()
const key = Buffer.alloc(32)
key.set(md5, 0)
// Create Cipher
const cipher = createCipheriv('aes-256-cbc', key, secretIV)
let encrypted = cipher.update(data, 'utf8', 'binary');
encrypted += cipher.final('binary');
// Return base64URL string
return Buffer.from(encrypted).toString('base64url');
Приведенный выше код Typescript НЕ дает того же результата, что и код PHP, указанный ранее. Я просмотрел исходный код OpenSSL, убедился, что алгоритмы заполнения совпадают (pcks5 и pcks7), и проверил, имеет ли каждый входной буфер ту же длину байта, что и вход внутри PHP. В настоящее время я думаю, является ли это какой-то двоичной неправильной формой, которая вызывает изменение данных внутри Javascript?
Я надеюсь, что какой-нибудь специалист может помочь мне с этим вопросом. Может быть, я что-то упустил из виду. Заранее спасибо.
Проблема заключается в функции md5 в PHP, которая по умолчанию использует шестнадцатеричный вывод вместо двоичного:
md5(string $string, bool $binary = false): string
По этой же причине код не жалуется на то, что ключ (составленный из хэша MD5) слишком мал, ему подается 32 байта после кодирования ASCII или UTF8 вместо 16 байтов — размер вывода MD5 — вы г использовать для AES-128.
По-видимому, он использует кодировку нижнего регистра, хотя даже это не указано. Вы можете указать кодировку и для NodeJS, см. документацию по методу дайджеста. Кажется, он также использует строчные буквы, хотя я также не могу найти точную спецификацию кодировки.
После того, как вы выполнили задание, попробуйте удалить код как можно скорее, так как вам никогда не следует вычислять IV по ключу; комбинация ключа и IV должна быть уникальной, поэтому приведенный выше код не является безопасным IND-CPA, если ключ используется повторно.
Вот почему PHP API плохо определен для MD5:
Хотя вам не следует использовать MD5 в первую очередь для получения ключей, следует отметить, что код в вопросе имеет ту же проблему: ключи в верхнем регистре не будут выполнять такое же шифрование/дешифрование.
По сути, функция MD5 в PHP использует принцип наименьшего удивления и выбрасывает его из окна. Вместо этого шестнадцатеричное или базовое 64 кодирование вывода можно было бы сделать необязательным, реализация NodeJS делает это правильно.
Спасибо за Ваш ответ. К сожалению, у меня нет доступа к API, который создал эту проблему (включая риски). Однако я свяжусь с поддерживающими разработчиками, чтобы сообщить им о риске, который это представляет. Однако я не уверен в том, как это создает разницу в выводе между кодом Javascript и PHP: оба примера кода имеют одинаковый ввод (включая ключ). Не могли бы вы уточнить, как включить изменения, которые необходимо внести в JS, чтобы получить правильный вывод, возвращаемый в коде PHP (независимо от того, что на данный момент это небезопасно)?
Ключ должен быть в шестнадцатеричном виде, преобразованным в байты с использованием ASCII. Как указано, вы можете использовать вызов digest('hex') в NodeJS. Очевидно, это не тот ключ, который вам нужен, потому что каждый байт может иметь только 1 из 16 значений вместо 1 из 256. Именно об этом говорил Топако в своем комментарии.
Помимо неправильной кодировки в отношении MD5 (см. ответ), существует еще одна проблема с кодировкой в отношении зашифрованного текста. Зашифрованный двоичный текст должен быть импортирован как таковой в буфер, иначе кодировка UTF-8 по умолчанию испортит данные (кстати, вероятно, более эффективно обрабатывать зашифрованный текст как буфер, объединять части update() / final() и кодировать Base64url в конце ). Ре. проблема MD5: получение ключа из строки в шестнадцатеричном кодировании с помощью кодировки UTF-8 снижает безопасность, поскольку каждый байт имеет уменьшенный диапазон значений всего 16 по сравнению с 256 значениями.