Nodejs, как реализовать шифрование OpenSSL AES-CBC (из PHP)?

В настоящее время я работаю над переводом алгоритма шифрования с 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 (см. ответ), существует еще одна проблема с кодировкой в ​​отношении зашифрованного текста. Зашифрованный двоичный текст должен быть импортирован как таковой в буфер, иначе кодировка UTF-8 по умолчанию испортит данные (кстати, вероятно, более эффективно обрабатывать зашифрованный текст как буфер, объединять части update() / final() и кодировать Base64url в конце ). Ре. проблема MD5: получение ключа из строки в шестнадцатеричном кодировании с помощью кодировки UTF-8 снижает безопасность, поскольку каждый байт имеет уменьшенный диапазон значений всего 16 по сравнению с 256 значениями.

Topaco 18.02.2023 20:17
Еще один бенчмарк PHP
Еще один бенчмарк PHP
Сегодня я наткнулся на забавный пост на r/ProgrammerHumor, который заставил меня задуматься об одной вещи, которая меня всегда интересовала....
Преобразование данных с помощью красноречивых аксессоров и мутаторов в Laravel
Преобразование данных с помощью красноречивых аксессоров и мутаторов в Laravel
Laravel поставляется с мощной функцией под названием "Eloquent Accessors and Mutators".
Настройка PHP-проекта на MacOS: простое руководство для начинающих
Настройка PHP-проекта на MacOS: простое руководство для начинающих
PHP, широко используемый язык сценариев с открытым исходным кодом, необходим для веб-разработки. Его совместимость с MacOS делает его популярным...
Конечные и Readonly классы в PHP
Конечные и Readonly классы в PHP
В прошлом, когда вы не хотели, чтобы другие классы расширяли определенный класс, вы могли пометить его как final.
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Запись файлов cookie в файл с помощью XSS R
Запись файлов cookie в файл с помощью XSS R
Привет всем :), здесь я продемонстрирую получение cookies, которые будут сохранены в виде txt файла, используя дефект XSS Reflected.
1
1
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема заключается в функции 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 указан в стандартах и ​​является двоичным.
  • Из функции невозможно увидеть, что она делает, вам нужно искать определение функции (не помогает то, что PHP здесь слабо типизирован, если бы он явно возвращал строку, это было бы более понятно для пользователя) .
  • Это также может работать очень плохо, если вы выполняете сравнение хеш-значений; даже если вы сравниваете строки, то легко использовать прописные буквы вместо строчных (и оба варианта одинаково допустимы, шестнадцатеричный код в верхнем регистре на самом деле легче читать людям, поскольку по той или иной причине мы больше фокусируемся на верхней части букв).

Хотя вам не следует использовать MD5 в первую очередь для получения ключей, следует отметить, что код в вопросе имеет ту же проблему: ключи в верхнем регистре не будут выполнять такое же шифрование/дешифрование.

По сути, функция MD5 в PHP использует принцип наименьшего удивления и выбрасывает его из окна. Вместо этого шестнадцатеричное или базовое 64 кодирование вывода можно было бы сделать необязательным, реализация NodeJS делает это правильно.

Спасибо за Ваш ответ. К сожалению, у меня нет доступа к API, который создал эту проблему (включая риски). Однако я свяжусь с поддерживающими разработчиками, чтобы сообщить им о риске, который это представляет. Однако я не уверен в том, как это создает разницу в выводе между кодом Javascript и PHP: оба примера кода имеют одинаковый ввод (включая ключ). Не могли бы вы уточнить, как включить изменения, которые необходимо внести в JS, чтобы получить правильный вывод, возвращаемый в коде PHP (независимо от того, что на данный момент это небезопасно)?

thim24 18.02.2023 23:34

Ключ должен быть в шестнадцатеричном виде, преобразованным в байты с использованием ASCII. Как указано, вы можете использовать вызов digest('hex') в NodeJS. Очевидно, это не тот ключ, который вам нужен, потому что каждый байт может иметь только 1 из 16 значений вместо 1 из 256. Именно об этом говорил Топако в своем комментарии.

Maarten Bodewes 18.02.2023 23:37

Другие вопросы по теме