Современная библиотека криптографии PHP около 2023 года.
Синопсис:
Эта библиотека является оберткой вокруг библиотеки натрия PHP и библиотеки PHP OpenSSL.
Код натрия в этой библиотеке основан на примере, приведенном в документации для функции PHP SOTIUM_CRYPTO_SECRETBOX ().
Код OpenSSL в этой библиотеке основан на примере, приведенном в документации для функции php openssl_encrypt ().
Эта библиотека направлена на то, чтобы гарантировать, что данные, которые он шифрует, столь же безопасны, как и секретные ключи, используемые для их шифрования. Также принимаются меры в попытке гарантировать, что зашифрованные данные были уплотнены.
Эта библиотека не может решить сложную проблему управления ключами.

Эта библиотека находится в стадии разработки.
Я делюсь этим кодом с друзьями и коллегами, запрашиваю как можно больше критики и обратной связи. Когда я чувствую, что эта библиотека настолько хороша, насколько я могу сделать, я обновлю эту статусную заметку. В то же время, когда изменения разрывы почти уверены, и недостатки шифрования вполне возможны. Если вы найдете что -то, что, вы думаете, я должен знать, пожалуйста, дайте мне знать!
Я хочу прояснить с вами, что эта библиотека большая и сложная, и она не видела особого использования; Без сомнения, нагруженные тонкими ошибками, которые еще не были обнаружены. Я думаю, что эта база кода имеет потенциал для созревания в надежный и надежный инструмент, но нам нужно пройти через процесс.
Пожалуйста, прочитайте этот раздел.
Есть много способов, которыми вы можете пойти не так с вашим крипто -кодом. Эта библиотека была написана как попытка уменьшить криптографические сжимные сьями; Надеюсь, это не представило ничего!
Первое, что нужно знать о крипто, - это ваши данные, так же безопасно, как и ваши ключи. О управлении ключами есть больше, чем я могу вам сказать здесь (и я все равно не эксперт), но вот несколько вещей, о которых нужно подумать:
Некоторые другие вещи, о которых нужно знать:
get_error() после шифрования, чтобы убедиться, что его нулевая ошибка не указывает на ошибку).Еще одна вещь, которая удивила меня, когда я узнал об этом, хотя, как только вы знаете, совершенно очевидно, что вы не должны сжимать свои данные, прежде чем шифровать их. Это не всегда проблема, но в определенных обстоятельствах это может быть, так что, вероятно, лучше всего никогда не делать этого.
Проблема сжатия состоит в том, что если злоумышленник может управлять некоторыми входными данными, они могут включить конкретное значение, а затем, если выход уменьшается в размере, они могут знать, что другой вход также включен в конкретное значение. Оу.
Эта база кода не является зрелой или хорошо протестированной, прежде чем вы его используете, вы должны прочитать весь код, чтобы убедиться, что он соответствует вашим стандартам качества. Если вы это сделаете, мне будет приятно услышать от вас.
Если вы можете придумать что -нибудь еще, о чем все должны знать, и будьте осторожны, пожалуйста, дайте мне знать!
Не хочу rtfm ..? И вот я пишу все это ... Шиш. По крайней мере, прочитайте предупреждения, перечисленные выше.
#!/bin/bash
set -euo pipefail;
mkdir -p kickass-demo/lib
cd kickass-demo
git clone https://github.com/jj5/kickass-crypto.git lib/kickass-crypto 2>/dev/null
php lib/kickass-crypto/bin/gen-demo-config.php > config.php
cat > demo.php <<'EOF'
<?php
require_once __DIR__ . '/lib/kickass-crypto/inc/sodium.php';
require_once __DIR__ . '/config.php';
$ciphertext = kickass_round_trip()->encrypt( 'secret text' );
$plaintext = kickass_round_trip()->decrypt( $ciphertext );
echo "the secret data is: $plaintext.n";
EOF
php demo.php
Для немного большей проработки, возможно, ознакомьтесь с примером кода.
Или, если вам нужны суть о том, как эта библиотека считывает код в библиотечной структуре или в другом коде.
Ну и дела, это началось достаточно просто, но в итоге это стало довольно сложным.
Я хотел отправиться в тупик некоторые относительно конфиденциальные данные (номера версий строк для оптимистичного управления параллелизмом) между моим сервером и его клиентами относительно безопасной модой, секретностью и фальсификацией предпочтительнее.
Я слышал, что библиотека OpenSSL была доступна в PHP, поэтому я искал информацию о том, как это использовать. Я нашел пример кода в документации PHP для функции openssl_encrypt ().
Первоначально мне было неясно, как использовать этот код. В частности, было трудно понять, что делать с тремя частями: тег аутентификации, вектор инициализации и текст шифра. В конце концов я понял, что могу просто объединить их. Но если бы я сделал это, мне нужно было бы стандартизировать их длину и размещение, чтобы я мог их получить позже ...
... А потом я подумал, что было бы лучше замаскировать мой фактический размер данных, заполняя их на фиксированные длины на определенных границах, поэтому я сделал это ...
... А потом я хотел поддержать богатые данные, которые требовали какой -либо формы сериализации. Первоначально я использовал функцию php serialize (), но это было изменено позже на json_encode ().
Пример кода не указывал ничего о том, как вращать ключи поддерживаемым образом. Поэтому я придумал два варианта использования, поддерживаемые этой библиотекой, с различными подходами к управлению ключами для обратного пути и сценариях At-Rest. Эта библиотека позволяет вращаться в новых ключах при сохранении поддержки старых ключей, как вы, вероятно, обычно делаете.
Затем я сложил в тщательном подходе к обработке исключений и отчетности об ошибках, некоторых модульных тестировании и валидации, смягчении атаки времени, локаторах обслуживания, демонстрации использования, ограничениях размера данных, инициализации пассийной фразы, сценариях генерации ключей, телеметрией и тому подобным.
По сути, вся эта библиотека была всего лишь всем, что я должен был сделать, чтобы я мог использовать встроенную реализацию библиотеки PHP OpenSSL.
А потом ... люди начали рассказывать мне о библиотеке натрия и предполагали, что вместо этого я использую это. Поскольку я уже проделал кучу работы для управления ключами и сериализации ввода и форматирования и кодирования сообщений, и так далее, я подумал, что могу просто использовать все это и обеспечить обертку вокруг натрия. Вот что я сделал.
Теперь, если вы используете эту библиотеку, вы можете решить, хотите ли вы использовать реализацию натрия или реализацию OpenSSL. Поскольку две реализации могут счастливо сосуществовать, вы также можете написать код, чтобы перейти от одного к другому, если вы так желаете. Реализации никогда не делятся конфигурацией ключей или форматами данных, они полностью отделены. (Тем не менее, не совсем тривиально переключать алгоритмы шифрования, и вам, вероятно, придется отключиться в автономном режиме, чтобы перенести все ваши данные, и если вы не сможете сделать это, у вас будет плохое время, поэтому не планируйте переключение алгоритмов, если вы не уверены, начинайте с натрия и придерживаться его.)
Я не считаю, что эта библиотека бросает свой собственный крипто , скорее я думаю о том, что выясняют, как на самом деле использовать натрий и OpenSSL . Если бы я совершил какие -либо ошибки, очевидные или иным образом, я бы очень признателен об этом.
Предполагая, что я не забываю об этом время от времени, здесь есть демонстрационная система:
Демонстрационная установка просто показывает, как зашифрованные данные об обратном пути между клиентом и сервером с помощью HTML и HTTP.
Демо -код доступен в этой библиотеке в SRC/ Demo/ Directory, если вы хотите разместить его самостоятельно.
Предполагая, что я не забываю обновлять их время от времени, документы PHP здесь:
Как упомянуто выше, вы можете проверить код из GIT с такой командой:
git clone https://github.com/jj5/kickass-crypto.git
Этот код не выпущен, нет стабильной версии.
Если вы хотите включить клиентскую библиотеку для использования в вашем приложении, включите либо INC/SODIUM.PHP, либо файл INC/OPENSSL.PHP, который позаботится о загрузке всего остального; Используйте что -то подобное:
require_once __DIR__ . '/lib/kickass-crypto/inc/sodium.php';
После загрузки этой библиотеки вы обычно получаете доступ через локаторы Service kickass_round_trip() или kickass_at_rest() , которые задокументированы ниже, что -то вроде этого:
$ciphertext = kickass_round_trip()->encrypt( 'secret text' );
$plaintext = kickass_round_trip()->decrypt( $ciphertext );
echo "the secret data is: $plaintext.n";
Потребовалось много работы, чтобы сделать вещи такими простыми!
Если вы хотите разместить демонстрационный код, вам необходимо размещать файлы в SRC/ Demo/ и включить действительный файл config.php в базовый каталог проекта (это каталог, который включает этот файл readme.md). Для демонстрационных целей действительный файл config.php должен только определить постоянную строку для CONFIG_SODIUM_SECRET_CURR , но он должен быть длинной и случайной строкой, вы можете генерировать соответствующую строку с:
php bin/gen-key.php
Или вы можете просто генерировать целый демо -файл config.php с помощью:
php bin/gen-demo-config.php > config.php
Вот несколько заметок о программных файлах и строках кода.
Total Number of Files = 128
Total Number of Source Code Files = 128
| Каталог | Файлы | По языку |
|---|---|---|
| тест | 63 | PHP = 59, SH = 4 |
| код | 35 | PHP = 35 |
| бин | 22 | PHP = 13, SH = 9 |
| внедорожник | 7 | PHP = 7 |
| демо | 1 | PHP = 1 |
| Язык | Файлы | Процент |
|---|---|---|
| PHP | 115 | (89,84%) |
| шнур | 13 | (10,16%) |
Total Physical Source Lines of Code (SLOC) = 9,210
Development Effort Estimate, Person-Years (Person-Months) = 2.06 (24.70)
(Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months) = 0.70 (8.46)
(Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule) = 2.92
Total Estimated Cost to Develop = $ 278,044
(average salary = $56,286/year, overhead = 2.40).
| Каталог | Слоя | По языку |
|---|---|---|
| код | 5,136 | PHP = 5136 |
| тест | 3363 | PHP = 3193, SH = 170 |
| бин | 603 | PHP = 423, SH = 180 |
| демо | 71 | PHP = 71 |
| внедорожник | 37 | PHP = 37 |
| Язык | Слоя | Процент |
|---|---|---|
| PHP | 8 860 | (96,20%) |
| шнур | 350 | (3,80%) |
Этот код должен работать на PHP 7,4 или более. Если вы попытаетесь запустить этот код на более старой версии PHP, он попытается войти в систему сообщения об ошибке, а затем выйти из процесса.
Этот код проверит, чтобы убедиться, что он работает на 64-разрядной платформе. Если это не так жаловаться и выйдет.
Если вы загрузите модуль натрия, библиотека гарантирует, что библиотека натрия фактически доступна. Если это не так, процесс будет жаловаться и выйдет.
Если вы загрузите модуль OpenSSL, библиотека гарантирует, что библиотека OpenSSL фактически доступна. Если это не так, процесс будет жаловаться и выйдет.
Я считаю, что этот код должен работать в любой операционной системе, но я тестировал его только на Linux. Если бы вы добились успеха в MacOS или Windows, я был бы рад услышать об этом.
Сценарии оболочки написаны для Bash. Если у вас нет удара, вам может понадобиться порт.
Этот код поддерживает два конкретных вариантов использования:
Ключи управляются отдельно и по -разному для каждого случая использования.
Подробная информация о том, как поддерживается каждый вариант использования, описаны ниже.
Использование этой библиотеки для шифрования At-Rest, как правило, является большим риском и большим обязательством, чем использование ее просто для шифрования в туалете. Если вы потеряете свои клавиши шифрования в оба конца или вынуждены срочно повернуть их, это, вероятно, будет менее проблемой, чем если бы что-то подобное произошло с вашими клавишами At-Rest.
Основным вариантом использования, для которого была разработана эта библиотека, заключалась в том, чтобы поддержать круглую ощущение нескольких килобайт данных, содержащих слегка чувствительные, но не критические номера версий строк для оптимистичного управления параллелизмом. По сравнению с альтернативной (не шифрованием или сделками с оптимистическими данными управления параллелизмом) использование этой библиотеки является улучшением. Является ли он действительно в других приложениях, - это открытый вопрос, я не уверен. Конечно, вы не должны использовать эту библиотеку, если она не обеспечивает уровень безопасности, который вам требуется.
Предпочтительным и поддерживаемым способом назначения секретов в файлах конфигурации является константы, используя функцию php define (). Проблема с использованием полей класса/экземпляра или глобальных переменных заключается в том, что значения могут довольно легко проникнуть в код отладки и регистрации, это менее вероятно (хотя и все еще возможно) для констант. Точно так же, если вам нужно кэшировать глобальные/статические данные (например, чтение из файла конфигурации), лучший способ сделать это с локальной статической переменной в функции, если это возможно, как использование полей экземпляра, полей класса или глобалов может легче привести к секретной утечке.
Чтобы привести вам пример, давайте создадим тестовый файл с именем double-define.php как это:
<?php
define( 'TEST', 123 );
define( 'TEST', 456 );
Затем, когда мы запускаем код, происходит что -то подобное:
$ php double-define.php
PHP Warning: Constant TEST already defined in ./double-define.php on line 4
PHP Stack trace:
PHP 1. {main}() ./double-define.php:0
PHP 2. define($constant_name = 'TEST', $value = 456) ./double-define.php:4
Если это постоянное значение содержало ваш секретный ключ, то у вас был очень плохой день.
Самый безопасный способ определить константу в PHP - это проверить, что она еще не определена сначала, потому что попытка определить уже определенную константу приведет к ошибке. Если вы находите уже определенную константу, вы можете либо прервать сообщение об ошибке (если вы не предоставляете слишком много деталей, потому что общедоступная сеть может ее увидеть), либо просто сохранить существующее значение и не пытайтесь переопределить его. Генератор конфигуратора bin/gen-demo-config.php использует первый подход и вызывает функцию php die() если обнаружено дубликат. Вы можете увидеть, что происходит путем включения сгенерированного файла config.php дважды, как:
require __DIR__ . '/config.php';
require __DIR__ . '/config.php';
Вы можете найти пример того, что произойдет, если вы дважды включите config.php в config-die.php.
Следовательно, как и в большинстве исходных файлов PHP, лучше всего использовать require_once при включении файла config.php :
require_once __DIR__ . '/config.php';
Когда я назову вещи, которые являются секретными, я убедитесь, что имя содержит строку «Pass» (как в «пароле», «passwd» и «passfrase», или даже, на протяжении, «паспорт») или «секрет». В моих средах для ведения журнала (которые не включены в эту библиотеку), я чищу и отредактировал все, что соответствует имени (нечувствительно к случаю) до ведения диагностических данных. Я призываю вас принять эту практику.
В этой библиотеке, если переменная или константа могут содержать конфиденциальные данные, она будет названа либо с «проходом», либо «секретом» в качестве подстроения в имени.
Не пишите конфиденциальные данные в журналы.
Поместите либо «пройти», либо «секрет» во имя чувствительных переменных, полей или константов.
Здесь я объясняю, что на самом деле означают эти подобные термины звучания в контексте этой библиотеки.
Если вы используете модули по умолчанию, формат данных является либо «KA0» для модуля OpenSSL, либо «kas0» для модуля натрия.
Если вы наследуете базовую структуру и определяете свой собственный крипто -модуль, формат данных по умолчанию представляет собой «xka0» для модуля, основанного на реализации OpenSSL или «xkas0» для модуля, основанного на реализации натрия, в противном случае ваша реализация do_get_const_data_format() Определяет, что формат данных будет известно, что вы сможете сделать что -то, что можно начнете, что -то не так долго, что можно будет начинать. реализации.
Вам необходимо использовать правильный модуль для формата данных, чтобы успешно расшифровать зашифрованный текст.
Кодирование данных - это либо JSON, PHP -сериализация, либо текст. Предполагая, что у вас есть правильный модуль для формата данных (выше) и с предостережением, обсуждаемой ниже, вы можете расшифровать все, независимо от кодирования данных, кодирующих данные. Шифрование будет выполнено с использованием кодирования настроенных данных, см. Config_encryption_data_encoding, это может быть одно из:
Обратите внимание, что вы не сможете использовать PHPS -кодирование, если вы также не определите config_encryption_phps_enable, это связано с тем, что детериализация PHP может быть небезопасной, поэтому она отключена по умолчанию. Честно говоря, это немного ручной. Я только что слышал слухи о том, что php unserialize() может привести к инъекции кода, но я не уверен, правда ли это или что именно это значит. Я реализовал сериализацию и десериализацию PHP и дал ей немного теста, но я не знаю, действительно ли она небезопасна или нет. Я уверен, что кодирование JSON и текстовых данных должно быть безопасным.
В дополнение к унаследованию от KickassCrypto и переопределению конкретной функциональности, большая часть конфигурации доступна через константы конфигурации. Поиск для CONFIG_SODIUM , чтобы найти то, что доступно для натрия и CONFIG_OPENSSL чтобы найти то, что доступно для OpenSSL.
Помните, что в настоящее время этот код настроен непосредственно в файле config.php .
В будущем файл config.php будет включать отдельно управляемые файлы конфигурации, будучи:
В этих файлах будут сценарии управления для автоматического вращения и обеспечения клавиш.
Опытные пользователи Linux знают, что вы не редактируете /etc/sudoers напрямую, вы редактируете его с помощью visudo , чтобы вы могли проверить, что вы не случайно не представили синтаксисную ошибку и шлачивали свою систему.
Я намерен предоставить аналогичные сценарии для редактирования и управления config.php и другими файлами конфигурации. Так что речь идет о этих обновлениях. В то же время ... просто будьте очень осторожны .
Одна вещь, которую вы должны быть очень осторожны, вы не делаете, это управлять своими ключами в чем -либо, кроме файла PHP с расширением файла .php. Если вы поместите свои ключи в файл «.ini» или что -то в этом роде , они вполне могут быть поданы в качестве простого текста на вашем веб -сервере . Так что не делай этого. Также будьте осторожны, чтобы не вводить синтаксические ошибки в свой файл конфигурации или другие исходные файлы, работающие в производстве, потому что детали могут протекать с потенциальными сообщениями об ошибках.
Как упомянуто в предыдущем разделе, достаточное количество конфигурации обеспечивается поддержкой именованных констант конфигурации.
В дополнение к константам конфигурации, что вы можете сделать, если вы наследуете от базового класса KickassCrypto и переопределите его методы.
В качестве альтернативы констант конфигурации (которые могут быть определены только один раз на процесс и после этого не могут быть изменены). Существуют методы экземпляра, как get_config_...() для параметров конфигурации и get_const_...() для постоянной оценки. Наиболее важные константы и параметры конфигурации считываются косвенно через эти аксессуры, поэтому вы сможете надежно переопределить их.
Большинство вызовов PHP встроенных функций выполняются тонкими обертками с помощью защищенных функций на KickassCrypto . Они определены в черте KICKASS_WRAPPER_PHP . Это косвенному обращению позволяет перехватить и потенциально модифицированные определенные вызовы функции PHP. Это было сделано в первую очередь для поддержки инъекции неисправностей во время единичных испытаний, но вы можете использовать для других целей для изменения деталей реализации.
Вещи, которые считаются чувствительными в KickassCrypto определяются как частные или окончательные . Если это не частное, и это не окончательно, это справедливая игра для переоценки (если я не сделал ошибку). В частности, методы экземпляра, которые начинаются с do_ были специально предназначены для замены или перехвата реализаторами.
Эта библиотека содержит две функции локатора обслуживания, которые управляют экземпляром крипто -библиотеки каждый, это: это:
kickass_round_trip()kickass_at_rest()Вы можете заменить экземпляр службы, предоставляемой функцией локатора сервиса, вызывая функцию и передавая новый экземпляр как единственный параметр, например, это:
class MyKickassCrypto extends KickassCryptoKickassCrypto {
protected function do_is_valid_config( &$problem = null ) { return TODO; }
protected function do_get_passphrase_list() { return TODO; }
// ... other function overrides ...
}
kickass_round_trip( new MyKickassCrypto );
В идеале эта библиотека будет соответствовать вашим требованиям из коробки (или с определенной конфигурацией), и вам не нужно будет заменить экземпляры, предоставленные локаторами службы по умолчанию.
Локатор сервиса создаст для вас новый экземпляр по умолчанию в первом вызове с локатором службы, если у него еще нет экземпляра. Независимо от того, является ли реализация по умолчанию модуль натрия или модуль OpenSSL, зависит от порядка, который вы включали файлы inc/sodium.php и inc/openssl.php ; Если вы включили всю библиотеку с inc/library.php модуль натрия будет иметь приоритет.
Независимо от того, загрузили ли вы локаторы службы для модуля натрия или модуля OpenSSL, вы сможете переопределить экземпляр по умолчанию, вызывая локатор Service с новым экземпляром в качестве аргумента.
Процесс шифрования примерно:
Обратите внимание, что библиотека натрия использует незю, а не вектор инициализации (к аналогичному эффекту), а натрий обрабатывает свою собственную тег аутентификации.
Когда эта библиотека кодирует свой зашифрованный текст, она включает в себя префикс формата данных «kas0/» для реализации натрия и «ka0/» для реализации OpenSSL.
Ноль («0») в префиксе формата данных предназначен для версии Zero , которая предназначена для того, чтобы подразумевать, что интерфейс нестабилен и может измениться .
Будущие версии этой библиотеки могут реализовать новый префикс формата данных для стабильного формата данных.
Когда эта библиотека декодирует свой шифвый текст, она проверяет префикс формата данных. В настоящее время поддерживается только "kas0/" или "ka0/".
Версия нулевой формат данных, упомянутый выше, в настоящее время подразумевает следующее:
После того, как кодирование данных (JSON по умолчанию обсуждается в следующем разделе), заполнение и длина данных префикс. Перед шифрованием сообщение отформатировано, например, это:
$message = $encoded_data_length . '|json|' . $encoded_data . $this->get_padding( $pad_length );
Длина данных JSON отформатируется как 8-символьное шестнадцатеричное значение. Размер 8 символов является постоянным и не варьируется в зависимости от величины длины данных JSON.
Причина заполнения заключается в том, чтобы скрыть фактический размер данных. Заполнение выполняется в границах до 4 киб (2 12 байтов), которые мы называем кусочками. Размер куски настраивается, и по умолчанию может измениться в будущем.
Затем, если мы шифруем с натрием, сообщение зашифруется с помощью sodium_crypto_secretbox() , а затем NONE и зашифрованный текст объединяются вместе, например:
$nonce . $ciphertext
В противном случае, если мы шифруем с OpenSSL, сообщение зашифровано с помощью AES-256-GCM, а вектор инициализации, зашифрованный текст и тег аутентификации объединяются вместе, например:
$iv . $ciphertext . $tag
Тогда все основано на базе64 с функцией php base64_encode () и добавляется префикс формата данных.
Для натрия это сделано так:
"KAS0/" . base64_encode( $nonce . $ciphertext )
И для OpenSSL это сделано так:
"KA0/" . base64_encode( $iv . $ciphertext . $tag )
Процесс расшифровки рассчитывает найти 24 -байт -нерею и зашифрованный текст для формата данных «KAS0» и вектора инициализации 12 байтов, зашифрованный текст и тег аутентификации 16 байтов для формата данных KA0.
После расшифровки зашифрованного текста библиотека рассчитывает найти размер данных JSON в качестве строки ASCII, представляющего кодированное значение HEX 8 символов, за которым следует один символ трубы, за которым следует четыре кодирующие данные символов ('JSON' или «PHPS»), за которым следует один характер трубы, за которым следует JSON (или сериализованные данные PHP), а затем наполнение. Затем библиотека может извлечь данные JSON/Serialized из его заполнения и позаботиться о остальной части декодирования.
До шифрования входные данные кодируются как JSON с использованием функции PHP JSON_ENCODE (). Первоначально эта библиотека использовала функцию PHP serialize (), но, по-видимому, это может привести к некоторым сценариям выполнения кода (я не уверен в деталях), поэтому было решено, что кодирование JSON было безопаснее. Таким образом, теперь мы используем кодирование JSON.
Использование JSON в качестве формата кодирования данных имеет некоторые незначительные последствия, касающиеся значений, которые мы можем поддержать. В частности, мы не можем кодировать экземпляры объектов, которые впоследствии могут быть декодированы обратно в экземпляры объектов (если объекты реализуют jsonerializable интерфейс, они могут быть сериализованы как данные, но они будут декодированы только в массивы PHP, а не объекты PHP, из которых они пришли); Некоторые нечетные значения с плавающей запятой не могут быть представлены (то есть NAN, POS INF, NEG Info и NEG Zero); и бинарные струны не могут быть представлены в JSON.
По умолчанию эти параметры используются для кодирования JSON:
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
Но эти варианты не повлияют на способность реализации декодировать JSON. Реализации могут точно настраивать кодирование и декодирование JSON, если необходимо, переопределив методы Data_Encode () и data_decode (). В качестве альтернативы вы можете назначить параметры кодирования и декодирования JSON в вашем файле config.php с помощью CONFIG_ENCRYPTION_JSON_ENCODE_OPTIONS и CONFIG_ENCRYPTION_JSON_DECODE_OPTIONS , например:
define( 'CONFIG_ENCRYPTION_JSON_ENCODE_OPTIONS', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
define( 'CONFIG_ENCRYPTION_JSON_ENCODE_OPTIONS', JSON_THROW_ON_ERROR );
Эта библиотека должна работать независимо от того, указана ли JSON_THROW_ON_ERROR или нет.
Если вы указываете JSON_PARTIAL_OUTPUT_ON_ERROR в вариантах кодирования JSON, ваши данные могут молча стать недействительным, так что сделайте это на свой собственный риск. Возможно, противостоятельно я обнаружил, что включение JSON_PARTIAL_OUTPUT_ON_ERROR -это наименее худшая стратегия, потому что, по крайней мере, в этом случае вы что-то получаете. Если вы не включите JSON_PARTIAL_OUTPUT_ON_ERROR , если какая-либо часть вашего ввода не может быть кодирована (например, когда у вас есть двоичные строки, которые не в действительной кодировании, такой как UTF-8), то все данные удаляются. С JSON_PARTIAL_OUTPUT_ON_ERROR только опубликованная часть непредставленной части. На данный момент JSON_PARTIAL_OUTPUT_ON_ERROR не указан автоматически, но это то, что я мог бы вернуться в будущем.
Если вы используете какой -либо из этих вариантов кодирования/декодирования JSON, вы вполне можете иметь плохое время:
JSON_NUMERIC_CHECKJSON_INVALID_UTF8_IGNOREJSON_INVALID_UTF8_SUBSTITUTE Когда эта библиотека шифрует свои данные, она подгоняет свои выводы до настраиваемого размера куски.
Константа конфигурации для размера чанка - CONFIG_ENCRYPTION_CHUNK_SIZE .
Размер чанка по умолчанию составляет 4096 (2 12 ).
Если вы хотите увеличить размер чанка до 8 192, вы можете сделать это в своем файле config.php , как это:
define( 'CONFIG_ENCRYPTION_CHUNK_SIZE', 8912 );
Вы можете изменить определенный размер чанка, и он начнет применяться к новым данным, и старые данные, зашифрованные с другим размером куски
Пока наблюдаются пределы размера данных (они обсуждаются дальше), эта библиотека может шифровать все, что может быть кодировано как JSON PHP.
Это включает в себя множество вещей, например:
Вещи, которые нельзя поддерживать с помощью JSON:
Обратите внимание, что логическое значение false не может быть зашифровано. Это не потому, что мы не могли зашифровать это, это потому, что мы возвращаем его, когда дешифрование терпит неудачу. Таким образом, мы отказываемся шифровать ложь, чтобы его не могли путать с ошибкой при расшифровании.
Если вам нужно зашифровать логическое значение, ложь. Рассмотрим либо поместить его в массив, например, это:
$input = [ 'value' => false ];
Или кодировать как json, например:
$input = json_encode( false );
Если вы сделаете какую -либо из этих вещей, вы сможете зашифровать свою ценность.
Стоит отметить, что в PHP «струны» представляют собой по существу байтовые массивы, что означает, что они могут содержать по существу «двоичные» данные. Такие бинарные данные не могут быть представлены как JSON, однако. Если вам нужно обрабатывать бинарные данные, лучше всего кодировать их как base64 с base64_encode () или шестнадцатеричным с bin2hex (), а затем шифруйте это.
В будущем возможность работать с данными, которые не всегда кодируются JSON, может быть добавлена в эту библиотеку. Дайте мне знать, если это функция, которую вы хотите иметь.
Примечание. Использование сериализации PHP вместо кодирования JSON теперь является вариантом; Эта документация должна быть обновлена, чтобы объяснить, как она работает и как ее использовать. Преимущество сериализации PHP заключается в том, что она поддерживает больше типов данных и форматов, чем может.
После того, как данные кодируются как JSON, это ограничено настраиваемой максимальной длиной.
Константа конфигурации для максимальной длины кодирования JSON - это CONFIG_ENCRYPTION_DATA_LENGTH_MAX .
Предел кодирования данных по умолчанию составляет 67,108,864 (2^ 26 ) байт, что составляет примерно 67 Мб или 64 миб.
Можно настроить этот предел кодирования данных, если вам нужно сделать его больше или меньше. Просто имейте в виду, что если вы сделаете лимит слишком большим, вы получите проблемы с памятью, и ваш процесс может быть прекращен.
Если вы хотите уменьшить предел кодирования данных, вы можете сделать это в своем файле config.php , как это:
define( 'CONFIG_ENCRYPTION_DATA_LENGTH_MAX', pow( 2, 25 ) );
Эта библиотека не сжимает входные данные, потому что сжатие может вводить криптографические недостатки, такие как в атаке Crime SSL/TLS.
Проблема заключается в том, что если злоумышленник может изменить часть простого текста, они могут выяснить, существуют ли данные, которые они вводятся в других частях простого текста, потому что, если они введены в значение, и результат меньше, потому что они существуют в той части простого текста, который они не знали, но делают сейчас!
Очень важно, чтобы вы не сжимали данные, которые злоумышленник может предоставить другие данные, которые являются секретными. Лучше всего не сжимать вообще.
Если во время шифрования или дешифрования встречается ошибка, задержка между 1 миллисекундой (1 мс) и 10 секунд (10 с). Это смягчение против потенциальных атак времени. См. S2N и Lucky 13 для обсуждения.
Note that avoiding timing attacks is hard. A malicious guest on your VPS host (or a malicious person listening to your server's fans! ?) could figure out that your process is sleeping rather than doing actual work.
This library includes a method called delay() , and this method is called automatically on the first instance of an error. The delay() method does what is says on the tin: it injects a random delay into the process. The delay() method is public and you can call it yourself if you feel the need. Each time delay() is called it will sleep for a random amount of time between 1 millisecond and 10 seconds.
The programmer using this library has the opportunity to override the do_delay() method and provide their own delay logic.
If that do_delay() override throws an exception it will be handled and an emergency delay will be injected.
If you do override do_delay() but don't actually delay for at least the minimum duration (which is 1 ms) then the library will inject the emergency delay.
The main reason for allowing the implementer to customize the delay logic is so that unit tests can delay for a minimum amount of time. Ordinarily there shouldn't be any reason to meddle with the delay logic and it might be less safe to do so.
When an instance of one of of the following is created the configuration settings are validated.
KickassSodiumRoundTripKickassSodiumAtRestKickassOpenSSLRoundTripKickassOpenSSLAtRestIf the configuration settings are not valid the constructor will throw an exception. If the constructor succeeds then encryption and decryption later on should also (usually) succeed. If there are any configuration problems that will mean encryption or decryption won't be able to succeed (such as secret keys not having been provided) the constructor should throw.
This library defines its own exception class called KickassException . This works like a normal Exception except that it adds a method getData() which can return any data associated with the exception. A KickassException doesn't always have associated data.
Of course not all problems will be able to be diagnosed in advance. If the library can't complete an encryption or decryption operation after a successful construction it will signal the error by returning the boolean value false. Returning false on error is a PHP idiom, and we use this idiom rather than raising an exception to limit the possibility of an exception being thrown while an encryption secret or passphrase is on the call stack.
The problem with having sensitive data on the call stack when an exception is raised is that the data can be copied into stack traces, which can get saved, serialized, displayed to users, logged, etc. We don't want that so we try very hard not to raise exceptions while sensitive data might be on the stack.
If false is returned on error, one or more error messages will be added to an internal list of errors. The caller can get the latest error by calling the method get_error . If you want the full list of errors, call get_error_list .
If there were any errors registered by the OpenSSL library functions (which the OpenSSL module calls to do the heavy lifting), then the last such error is available if you call the get_openssl_error() . You can clear the current error list (and OpenSSL error message) by calling the method clear_error() .
For the PHP Sodium implementation the function we use is sodium_crypto_secretbox(). That's XSalsa20 stream cipher encryption with Poly1305 MAC authentication and integrity checking.
For the PHP OpenSSL implementation the cipher suite we use is AES-256-GCM. That's Advanced Encryption Standard encryption with Galois/Counter Mode authentication and integrity checking.
Secret keys are the secret values you keep in your config.php file which will be processed and turned into passphrases for use by the Sodium and OpenSSL library functions. This library automatically handles converting secret keys into passphrases so your only responsibility is to nominate the secret keys.
The secret keys used vary based on the use case and the module. There are two default use cases, known as round-trip and at-rest.
The "256" in AES-256-GCM means that this cipher suite expects 256-bit (32 byte) passphrases. The Sodium library sodium_crypto_secretbox() function also expects a 256-bit (32 byte) passphrase.
We use a hash algorithm to convert our secret keys into 256-bit binary strings which can be used as the passphrases the cipher algorithms expect.
The minimum secret key length required is 88 bytes. When these keys are generated by this library they are generated with 66 bytes of random data which is then base64 encoded.
The secret key hashing algorithm we use is SHA512/256. That's 256-bits worth of data taken from the SHA512 hash of the secret key. When this hash code is applied with raw binary output from an 88 byte base64 encoded input you should be getting about 32 bytes of randomness for your keys.
The Sodium library expects to be provided with a nonce, in lieu of an initialization vector.
To understand what problem the nonce mitigates, think about what would happen if you were encrypting people's birthday. If you had two users with the same birthday and you encrypted those birthdays with the same key, then both users would have the same ciphertext for their birthdays. When this happens you can see who has the same birthday, even when you might not know exactly when it is. The initialization vector avoids this potential problem.
Our AES-256-GCM cipher suite supports the use of a 12 byte initialization vector, which we provide. The initialization vector ensures that even if you encrypt the same values with the same passphrase the resultant ciphertext still varies.
This mitigates the same problem as the Sodium nonce.
Our AES-256-GCM cipher suite supports the validation of a 16 byte authentication tag.
The "GCM" in AES-256-GCM stands for Galois/Counter Mode. The GCM is a Message Authentication Code (MAC) similar to a Hash-based Message Authentication Code (HMAC) which you may have heard of before. The goal of the GCM authentication tag is to make your encrypted data tamperproof.
The Sodium library also uses an authentication tag but it takes care of that by itself, it's not something we have to manage. When you parse_binary() in the Sodium module the tag is set to false.
This library requires secure random data inputs for various purposes:
There are two main options for generating suitable random data in PHP, those are:
Both are reasonable choices but this library uses random_bytes().
If the random_bytes() function is unable to generate secure random data it will throw an exception. See the documentation for details.
We also use the PHP random_int() function to generate a random delay for use in timing attack mitigation.
The round-trip use case is for when you want to send data to the client in hidden HTML form <input> elements and have it POSTed back later.
This use case is supported with two types of secret key.
The first key is called the current key and it is required.
The second key is called the previous key and it is optional.
Data is always encrypted with the current key.
Data is decrypted with the current key, and if that fails it is decrypted with the previous key. If decryption with the previous key also fails then the data cannot be decrypted, in that case the boolean value false will be returned to signal the error.
When you rotate your round-trip secret keys you copy the current key into the previous key, replacing the old previous key, and then you generate a new current key.
The config setting for the current key for the Sodium module is: CONFIG_SODIUM_SECRET_CURR .
The config setting for the current key for the OpenSSL module is: CONFIG_OPENSSL_SECRET_CURR .
The config setting for the previous key for the Sodium module is: CONFIG_SODIUM_SECRET_PREV .
The config setting for the previous key for the OpenSSL module is: CONFIG_OPENSSL_SECRET_PREV .
To encrypt round-trip data:
$ciphertext = kickass_round_trip()->encrypt( 'secret data' );
To decrypt round-trip data:
$plaintext = kickass_round_trip()->decrypt( $ciphertext );
The at-rest use case if for when you want to encrypt data for storage in a database or elsewhere.
This use case is supported with an arbitrarily long list of secret keys.
The list must include at least one value. The first value in the list is used for encryption. For decryption each secret key in the list is tried until one is found that works. If none work the data cannot be decrypted and the boolean value false is returned to signal the error.
When you rotate your at-rest secret keys you add a new master key as the first item in the list. You need to keep at least one extra key, and you can keep as many in addition to that as suits your purposes.
After you rotate your at-rest secret keys you should consider re-encrypting all your existing at-rest data so that it is using the latest key. After you have re-encrypted your at-rest data, you can remove the older key.
The config setting for the key list for the Sodium module is: CONFIG_SODIUM_SECRET_LIST .
The config setting for the key list for the OpenSSL module is: CONFIG_OPENSSL_SECRET_LIST .
Please be aware: if you restore an old backup of your database, you will also need to restore your old keys.
Be very careful that you don't lose your at-rest secret keys. If you lose these keys you won't be able to decrypt your at-rest data.
To encrypt at-rest data:
$ciphertext = kickass_at_rest()->encrypt( 'secret data' );
To decrypt at-test data:
$plaintext = kickass_at_rest()->decrypt( $ciphertext );
It has been noted that key management is the hardest part of cybersecurity. This library can't help you with that.
Your encrypted data is only as secure as the secret keys.
If someone gets a copy of your secret keys, they will be able to decrypt your data.
If someone gets a copy of your encrypted data now, they can keep it and decrypt it if they get a copy of your secret keys in the future. So your keys don't have to be only secret now, but they have to be secret for all time.
If you lose your secret keys, you won't be able to decrypt your data.
Your round-trip data is probably less essential than your at-rest data.
It's a very good idea to make sure you have backups of the secret keys for your essential round-trip or at-rest data. You can consider:
When doing key management it is important to make sure your config files are edited in a secure way. A syntax error in a config file could lead to a secret key being exposed to the public web. If this happened you would have to rotate all of your keys immediately and then destroy the old compromised keys, even then it might be too late .
It would be a good idea to stand ready to do a key rotation in an automated and tested fashion immediately in case of emergency.
When you rotate your round-trip and at-rest keys you need to make sure they are synchronized across all of your web servers.
I intend to implement some facilities to help with key deployment and config file editing but those facilities are not done yet.
This library supports encrypted data at-rest, and encrypted data round-trips. Another consideration is data in motion. Data in motion is also sometimes called data in transit.
Data is in motion when it moves between your web servers and your database server. Data is also in motion when it moves between your web servers and the clients that access them. You should use asymmetric encryption for your data in motion. Use SSL encryption support when you connect to your database, and use HTTPS for your web clients.
This library is a server-side component. We don't support encrypting data client-side in web browsers.
This library collects some basic telemetry:
Call KickassCrypto::GetTelemetry() to get the telemetry and KickassCrypto::ReportTelemetry() to report it.
The unit tests are in the src/test/ directory, numbered sequentially.
There's some test runners in bin/dev/, as you can see. Read the scripts for the gory details but in brief:
There are also some silly tests, but we won't talk about those. They are not ordinarily run. And they're silly.
If you want to add a normal/fast test create the unit test directory as src/test/test-XXX , then add either fast.php or fast.sh . If you create both then fast.sh will have precedence and fast.php will be ignored.
If you want to add a slow test create the unit test directory as src/test/test-XXX , then add either slow.php or slow.sh . If you create both then slow.sh will have precedence and slow.php will be ignored.
You usually only need to supply a shell script if your unit tests require multiple processes to work. That can happen when you need to test different constant definitions. As you can't redefine constants in PHP you have to restart your process if you want to run with different values.
See existing unit tests for examples of how to use the simple unit test host.
I have heard of and used PHPUnit (although I haven't used it for a long while). I don't use it in this project because I don't feel I need it or that it adds much value. Tests are a shell script, if that's missing they're a PHP script. If I need to make assertions I call assert(). Легкий.
Here are some notes about the various idioms and approaches taken in this library.
In the code you will see things like this:
protected final function is_valid_settings( int $setting_a, string $setting_b ) : bool {
if ( strlen( $setting_b ) > 20 ) { return false; }
return $this->do_is_valid_settings( $setting_a, $setting_b );
}
protected function do_is_valid_settings( $setting_a, $setting_b ) {
if ( $setting_a < 100 ) { return false; }
if ( strlen( $setting_b ) > 10 ) { return false; }
return 1;
}
There are several things to note about this idiom.
In talking about the above code we will call the first function is_valid_settings() the "final wrapper" (or sometimes the "main function') and we call the second function do_is_valid_settings() the "default implementation".
The first thing to note is that the final wrapper is_valid_settings() is declared final and thus cannot be overridden by implementations; and the second thing to note is that the final wrapper declares the data types on its interface.
In contrast the default implementation do_is_valid_settings() is not marked as final, and it does not declare the types on its interface.
This is an example of Postel's Law, which is also known as the Robustness Principle. The final wrapper is liberal in what it accepts, such as with the return value one ( 1 ) from the default implementation; and conservative in what it does, such as always returning a properly typed boolean value and always providing values of the correct type to the default implementation.
Not needing to write out and declare the types on the interface of the default implementation also makes implementation and debugging easier, as there's less code to write. (Also I find the syntax for return types a bit ugly and have a preference for avoiding it when possible, but that's a trivial matter.)
Ordinarily users of this code will only call the main function is_valid_settings() , and anyone implementing new code only needs to override do_is_valid_settings() .
In general you should always wrap any non-final methods (except for private ones) with a final method per this idiom, so that you can have callers override functionality as they may want to do but retain the ability to maintain standards as you may want to do.
If you're refactoring a private method to make it public or protected be sure to introduce the associated final wrapper.
One last thing: if your component has a public function, it should probably be a final wrapper and just defer to a default implementation.
Default implementations should pretty much always be protected, certainly not public, and maybe private if you're not ready to expose the implementation yet.
Having types on the interface of the final method is_valid_settings() confers three main advantages.
The first is that the interface is strongly typed, which means your callers can know what to expect and PHP can take care of fixing up some of the smaller details for us.
The second advantage of this approach is that our final wrapper function is marked as final. This means that the implementer can maintain particular standards within the library and be assured that those standards haven't been elided, accidentally or otherwise.
Having code that you rely on marked as final helps you to reason about the possible states of your component. In the example given above the requirement that $setting_b is less than or equal to 20 bytes in length is a requirement that cannot be changed by implementations; implementations can only make the requirements stronger, such as is done in the default implementation given in the example, where the maximum length is reduced further to 10 bytes.
Another advantage of the typed interface is that it provides extra information which can be automatically added into the documentation. The typed interface communicates intent to the PHP run-time but also to other programmers reading, using, or maintaining the code.
Not having types on the interface of the default implementation do_is_valid_settings() confers four main advantages.
The first is that it's easier to type out and maintain the overriding function as you don't need to worry about writing out the types.
Also, in future, the is_valid_settings() might declare a new interface and change its types. If this happens it can maintain support for both old and new do_is_valid_settings() implementations without implementers necessarily needing to update their code.
The third advantage of an untyped interface for the do_is_valid_settings() function is that it allows for the injection of "impossible" values. These are values which will never be able to make it past the types declared on the main function is_valid_settings() and into the do_is_valid_settings() function, and being able to inject such "impossible" values can make unit testing of particular situations easier, as you can pass in a value that could never possibly occur in production in order to signal something from the test in question.
The fourth and perhaps most important implication of the approach to the default implementation is that it is not marked as final which means that programmers inheriting from your class can provide a new implementation, thereby replacing, or augmenting, the default implementation.
One way a programmer can go wrong is to infinitely recurse. For example like this:
class InfiniteRecursion extends KickassCryptoOpenSslKickassOpenSslRoundTrip {
protected function do_encrypt( $input ) {
return $this->encrypt( $input );
}
}
If the do_encrypt() function calls the encrypt() function, the encrypt() function will call the do_encrypt() function, and then off we go to infinity.
If you do this and you have Xdebug installed and enabled that will limit the call depth to 256 by default. If you don't have Xdebug installed and enabled PHP will just start recurring and will continue to do so until it hits its memory limit or runs out of RAM.
Since there's pretty much nothing this library can do to stop programmers from accidentally writing code like the above what we do is to detect when it's probably happened by tracking how deep our calls are nested using an enter/leave discipline, like this:
try {
$this->enter( __FUNCTION__ );
// 2023-04-07 jj5 - do work...
return $result;
}
catch ( AssertionError $ex ) {
throw $ex;
}
catch ( Throwable $ex ) {
try {
$this->handle( $ex, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) {
try {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) { ; }
}
}
finally {
try { $this->leave( __FUNCTION__ ); } catch ( Throwable $ignore ) { ; }
}
The leave() function has no business throwing an exception, but we wrap it in a try-catch block just in case.
The example code above is shown with typical catch blocks included, but the key point is that the very first thing we do is register the function entry with the call to enter() and then in our finally block we register the function exit with the call to leave() .
If a function enters more than the number of times allowed by KICKASS_CRYPTO_RECURSION_LIMIT without leaving then an exception is thrown in order to break the recursion. At the time of writing KICKASS_CRYPTO_RECURSION_LIMIT is defined as 100, which is less than the Xdebug limit of 256, which means we should always be able to break our own recursive loops.
And for all the trouble we've gone to if the inheritor calls themselves and recurs directly there is nothing to be done:
class EpicFail extends KickassCryptoOpenSslKickassOpenSslRoundTrip {
protected function do_encrypt( $input ) {
return $this->do_encrypt( $input );
}
}
As mentioned above and elaborated on in the following section this library won't usually throw exceptions from the methods on its public interface because we don't want to leak secrets from our call stack if there's a problem.
Instead of throwing exceptions the methods on the classes in this library will usually return false instead, or some other invalid value such as null or [] .
The avoidance of exceptions is only a firm rule for sensitive function calls which handle secret keys, passphrases, unencrypted content, or any other sensitive data. At the time of writing it's possible for the public get_error_list() function to throw an exception if the implementer has returned an invalid value from do_get_error_list() , apart from in that specific and hopefully unlikely situation everything else should be exception safe and use the boolean value false (or another appropriate sentinel value) to communicate errors to the caller.
Sometimes because of the nature of a typed interface it's not possible to return the boolean value false and in some circumstances the empty string ( '' ), an empty array ( [] ), null ( null ), the floating-point value zero ( 0.0 ), or the integer zero ( 0 ) or minus one ( -1 ) may be returned instead; however, returning false is definitely preferred if it's possible.
Aside: in some cases minus one ( -1 ) can be used as the sentinel value to signal an error, such as when you want to indicate an invalid array index or an invalid count, but unlike in some other languages in PHP minus one isn't necessarily an invalid array index, and returning false is still preferred. This library does use minus one in some cases, if there's a problem with managing the telemetry counters.
The fact that an error has occurred can be registered with your component by a call to error() so that if the callers get a false return value they can interrogate your component with a call to get_error() or get_error_list() to get the recent errors (the caller can clear these errors with clear_error() too).
In our library the function for registering that an error has occurred is the error() function defined in the KickassCrypto class.
In some error situations the best and safest thing to do is swallow the error and return a sensible and safe and uncontroversial default value as a fallback.
Here's a quick run-down:
get_error_list() you get an exception with no errorget_error() you get null and an errorclear_error() it's void but with an errorhandle() you get a log entry, no errornotify() it will be handled then ignored, no errorignore() you get a log entry, no errorthrow() it will throw anywayerror() your error may not be properly registered, it always returns falsecount_*() counter you get -1 and no errorincrement_counter() you get -1 and no errorget_const_data_format() you get an empty string and no errorget_const_*() constant accessor you get the value defined by the default constant and no errorget_config_*() config accessor you get the value defined by the default constant (or false if there is no such thing) and no errorget_const() you get the default value and no errorget_passphrase_list() you get an empty array and an errorget_encryption_passphrase() you get null and no erroris_*() method you will get false and no errorget_data_encoding() you will get an empty string and no errorget_data_format() you will get false and no errorconvert_secret_to_passphrase() you will get false and no errorget_padding() you will get false and no errorget_delay() you will get false and no error (an emergency delay will be injected)delay() you will get void and no error (an emergency delay will be injected)log_error() you will get false and no error (but we try to be forgiving)This library is very particular about exception handling and error reporting.
If you have sensitive data on your call stack you must not throw exceptions. Sensitive data includes:
If you encounter a situation from which you cannot continue processing of the typical and expected program logic the way to register this problem is by calling the error() function with a string identifying and describing the problem and then returning false to indicate failure.
As the error() function always returns the boolean value false you can usually register the error and return false on the same like, like this:
return $this->error( __FUNCTION__, 'something bad happened.' );
When I nominate error strings I usually start them with a lowercase letter and end them with a period.
Note that it's okay to intercept and rethrow PHP AssertionError exceptions. These should only ever occur during development and not in production. If you're calling code you don't trust you might not wish to rethrow AssertionError exceptions, but if you're calling code you don't trust you've probably got bigger problems in life.
If you have a strong opinion regarding AssertionError exceptions and think I should not rethrow them I would be happy to hear from you to understand your concern and potentially address the issue.
Following is some example code showing how to handle exceptions and manage errors.
protected final function do_work_with_secret( $secret ) {
try {
$result = str_repeat( $secret, 2 );
$this->call_some_function_you_might_not_control( $result );
return $result;
}
catch ( AssertionError $ex ) {
throw $ex;
}
catch ( Throwable $ex ) {
try {
$this->handle( $ex, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) {
try {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) { ; }
}
}
try {
return $this->error( __FUNCTION__, 'error working with string.' );
}
catch ( Throwable $ignore ) {
try {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) { ; }
}
return false;
}
In actual code you would define an error constant for use instead of the string literal 'error working with string.' Полем In this library the names of error constants begin with "KICKASS_CRYPTO_ERROR_" and they are defined in the src/code/global/constant/framework.php file.
Note that we don't even assume it's safe to call handle() , ignore() , or error() ; we wrap all such calls in try-catch handlers too. There are some edge case situations where even these functions which are supposed to be thread safe can lead to exceptions, such as when there's infinite recursion which gets aborted by the run-time. If you're an expert on such matters the code might do with a review from you.
Now I will agree that the above code is kind of insane, it's just that it seems to me like there's no avoiding it if we want to be safe. We have to explicitly allow the AssertionError exception every single time in every single method just so that assertions remain useful to us as a development tool, and then when we handle other exceptions we want to make some noise about them so we call handle() , but the thing is that handle() will defer to do_handle() which can be overridden by implementers, which means it can throw... so if handle() throws we don't want to just do nothing, we want to give the programmer a last chance to learn of their errant code, so we notify that we're going to ignore the exception with a call to ignore() , but that will defer to do_ignore() , which the programmer could override, and throw from... but if that happens we will just silently ignore such a problem.
And then if we get through all of that and our function hasn't returned then that's an error situation so we want to notify the error, but error() defers to do_error() and that could be overridden and throw, so we wrap in a try-catch block and then do the exception ignore dance again.
I mean it's all over the top and excessive but it should at least be safe and it meets two requirements:
In the usual happy code path none of the exception handling code even runs.
There are a bunch of functions for testing boolean conditions, and they begin with "is_" and return a boolean. These functions should only do the test and return true or false, they should not register errors using the error() function, if that's necessary the caller will do that.
The is_() functions can be implemented using the typed final wrapper idiom documented above.
Following is a good example from the code.
protected final function is_valid_secret( $secret ) : bool {
try {
$is_valid = $this->do_is_valid_secret( $secret );
// ...
assert( is_bool( $is_valid ) );
return $is_valid;
}
catch ( AssertionError $ex ) {
throw $ex;
}
catch ( Throwable $ex ) {
try {
$this->handle( $ex, __FILE__, __LINE__, __FUNCTION__ );
}
catch ( Throwable $ignore ) {
$this->ignore( $ignore, __FILE__, __LINE__, __FUNCTION__ );
}
}
return false;
}
Note that do_is_valid_secret() also has a secret on the call stack, so it should be implemented as exception safe in the same way (in case it is called directly from some other part of the code).
Note too that it's okay to just rethrow assertion violations, these should never happen in production and they make testing the code easier.
The approach to unit-testing taken by this library is simple and powerful. There are three types of test which can be defined for each unit test:
Each script will be either a shell script with the same name, eg fast.sh , or if that's missing a PHP script with the same name, eg fast.php . The test runner just finds these scripts and runs them. This is easy to do and provides all the power we need to run our tests, including support for the various situations where each test instance needs to run in its own process and be isolated from other testing environments.
If you have flakey and unreliable tests you can stick them in as silly tests. The fast and slow tests are the important ones, and you shouldn't put slow tests in the fast test scripts. The fast tests are for day to day programming and testing and the slow scripts are for running prior to a version release.
Here are some notes regarding notable components:
config.php file for the demoSome countries have banned the import or use of strong cryptography, such as 256 bit AES.
Please be advised that this library does not contain cryptographic functions, they are provided by your PHP implementation.
Copyright (c) 2023 John Elliot V.
This code is licensed under the MIT License.
See the contributors file.
I should probably be more disciplined with my commit messages... if this library matures and gets widely used I will try to be more careful with my commits.
The Kickass Crypto ASCII banner is in the Graffiti font courtesy of TAAG.
The string "kickass" appears in the source code 1,506 times (including the ASCII banners).
SLOC and file count reports generated using David A. Wheeler's 'SLOCCount'.
I'd love to hear from you! Hit me up at [email protected]. Put "Kickass Crypto" in the subject line to make it past my mail filters.