Есть ли в PHP какой-нибудь способ заставить переменную «прилипать» к нескольким вызовам моего скрипта? В частности, мне нужно вычислить hash_hmac('sha-256', $data, $key), где данные, вероятно, будут различаться при каждом вызове моего скрипта, но ключ должен оставаться одинаковым для всех вызовов на конкретном экземпляре сервера (Apache2). На самом деле мне нужно, чтобы ключ инициализировался псевдослучайным значением один раз для каждого экземпляра сервера (Apache2). Обратите внимание: я не хочу, чтобы ключ был жестко запрограммирован или загружен из файла конфигурации. Ключ должен генерироваться псевдослучайным образом при запуске экземпляра сервера (Apache2) или когда он необходим (читается) в первый раз. Кроме того, ключ должен «придерживаться» в памяти до тех пор, пока работает конкретный экземпляр сервера, но как только экземпляр сервера выключается, он должен быть удален без возможности его повторного создания!
Я был бы в порядке, если бы $_SERVER содержал любое значение, которое было бы «уникальным» для каждого экземпляра сервера с очень высокой вероятностью, чтобы я мог просто взять это начальное значение и пропустить его через KDF, чтобы получить свой уникальный ключ. Но в $_SERVER кажется такой записи нет, или я что-то упускаю?
Кроме того, я не могу использовать uniqid(), потому что он будет давать разное уникальное значение при каждом вызове (вызове сценария), а не значение, уникальное для каждого экземпляра сервера (Apache2).
Кроме того, я не могу использовать файлы cookie или session_start(), потому что ключ не должен быть индивидуальным и не должен быть виден пользователям! Я понимаю, что содержимое $_SESSION не видно пользователю(ям), но я не хочу отслеживать какие-либо данные по каждому пользователю, если в этом нет необходимости – и когда это будет служить лишь обходным решением невозможности сохранить один " глобальное» значение.
Вероятно, я мог бы использовать БД «в памяти» с чем-то вроде SQLite, но на самом деле это похоже на то, чтобы расколоть орех кувалдой. Эта «проблема» требования, чтобы переменная (значение) была инициализирована один раз, а затем «прикреплялась» к нескольким вызовам сценария, кажется чем-то естественным, возникающим во многих приложениях! Так неужели нет простого и прямого решения этой проблемы? 🤔
Для пояснения: в этой конкретной настройке нет никакого риска для безопасности при использовании одного и того же MAC-ключа для всех пользователей (определенного экземпляра сервера), поскольку мы никогда не показываем этот ключ пользователям. Мы хотим гарантировать, что пользователь вернет «подлинное» сообщение на тот же экземпляр сервера, с которого оно было первоначально получено. Если пользователь изменяет данное сообщение, мы обнаруживаем это при возврате, поскольку MAC-адрес больше не соответствует. То же самое справедливо и в случае, если сообщение было возвращено на «неправильный» сервер, поскольку разные серверы должны иметь разные ключи MAC, и поэтому MAC-адреса не будут совпадать. Использование отдельного ключа для каждого пользователя не улучшает ситуацию в этом сценарии и просто не является необходимым, но это добавит новые сложности (например, управление сеансами), которых я действительно хочу избежать...
Шмоп сработает? Первая попытка чтения потребует инициализации ключа. php.net/manual/en/book.shmop.php
Что касается вашего последнего абзаца: да, проблема очень распространена, и у нас есть базы данных, файловые системы, хранилища памяти, «секретные хранилища» и т. д. для ее поддержки. Однако необычным является аспект «уникальность для жизненного цикла процесса».
@ChrisHaas "А шмоп подойдет?" Он выдержит перезапуск Apache. Только перезапуск всей машины приведет к сбросу настроек. И может возникнуть состояние гонки, если первые два запроса выполняются одновременно.
Вы можете использовать технику, описанную здесь, для запуска PHP-скрипта при запуске Apache. Этот сценарий инициализирует общую память с помощью shmop.
Технически использование «общей памяти» (shmop) может помочь сохранить данные при нескольких вызовах сценариев. Но такое ощущение, что мы злоупотребляем «общей памятью», которая предназначена для обмена данными между процессами, в качестве «внутрипроцессного» буфера — то, что обычно было бы достижимо с помощью простого буфера с malloc(), если бы мы этого не сделали. ограничены инструментами, которые дает нам PHP. Кроме того, API shmop_* довольно громоздкий для таких простых вещей, как создание переменной «прилипанием». Наконец, как мне реализовать и правильно синхронизировать «однократную» инициализацию с API ???
Почему ключ не должен быть для каждого пользователя/для каждого сеанса? это бессмысленно.
«который предназначен для обмена данными между процессами». Это необходимо при использовании PHP в качестве CGI.
@Kaii Я думаю, теоретически мы могли бы использовать отдельный ключ для каждого пользователя, но в этом нет необходимости. На самом деле нам нужен уникальный ключ для каждого сервера. Я бы хотел отслеживать отдельный ключ для каждого пользователя только в том случае, если в этом есть необходимость, а не в качестве обходного пути невозможности отслеживать один ключ для каждого экземпляра сервера. Кроме того, если мы хотим отслеживать отдельный ключ для каждого пользователя, нам придется использовать файлы cookie сеанса, верно? Это имеет юридические последствия, поскольку нам необходимо согласие пользователя, прежде чем нам будет разрешено хранить файлы cookie (по крайней мере, здесь, в ЕС). Вот этого мне хотелось бы избежать...
@Oliver Я думаю, что «традиционный» CGI передает параметры через переменные командной строки и среды, а «выходные данные» затем возвращаются через канал (stdout). Современный FastCGI использует сетевой протокол (сокеты домена Unix) для связи между «основным» процессом и «рабочим» процессом. Ни один из этих способов не использует «разделяемую память» в смысле объектов общей памяти POSIX (т. е. shmop_* API).
@ Schraubstock1990 Я думаю совершенно противоположно: я бы всегда использовал уникальный ключ для каждого пользователя или запроса, или что-то еще, что составляет наименьшую возможную долю ключа шифрования. Я бы сделал это только в том случае, если есть веское требование использовать общий ключ между запросами/пользователями. Лучшие практики здесь не просто так... см. другие мои комментарии под моим ответом, я сначала прочитал и ответил там.
@Schraubstock1990 Schraubstock1990 все, что вы говорите о сеансах PHP, неверно: пользователю недоступно содержимое переменных сеанса (если только ваш код не отправляет его ему), а также нет необходимости использовать файлы cookie. Вам нужно только передать идентификатор сеанса: вы можете отправить идентификатор с переменными GET или POST, поэтому сеансы также работают для пользователей без файлов cookie. Файл cookie необходим только для повторной идентификации пользователя, когда он закрывает окно браузера и возвращается позже.
@Kaii Я не хотел сказать, что данные в $_SESSION видны пользователю. Хорошо, теперь я вижу, что мы можем проводить сеансы PHP без необходимости использования файлов cookie. Но я все же не хочу отслеживать какие-либо данные отдельно для каждого пользователя (например, в сеансе PHP), если в этом даже нет необходимости. Я думаю, что использование сеанса для каждого пользователя только для переноса значения от одного вызова скрипта к другому – значения, которое на самом деле не зависит от пользователя и, следовательно, может (должно) быть «долговременной» глобальной переменной – было бы некачественным. решение. Я бы предпочел использовать решение на базе APCu.
Поздно, но есть ли какой-либо недостаток в использовании переменной среды для пользователя *UNIX, запускающего PHP, и ее инициализации при запуске сервера? Он выдержит остановку Apache, но если он будет инициализирован при запуске, он будет просто переопределен.
@ЙоханнесХ. Да, использование переменной среды действительно может быть выходом. Но недостаток, который я вижу, заключается в том, что это не решение «только для PHP», а вместо этого оно основано на инициализации, выполняемой «вне» PHP перед запуском приложения PHP.






Мне интересно узнать, почему вы хотите это сделать? Каков вариант использования? Вы просите о подходе, который сделает ваше приложение чрезвычайно тесно связанным с конкретной настройкой на конкретном веб-сервере на конкретной платформе или, по крайней мере, ОС. например, вы можете использовать идентификатор корневого процесса Apache2:
netstat -lntp | grep apache | awk -F '[/ ]*' '{print $7}'
Чтобы получить уникальный идентификатор процесса, который будет меняться при каждом перезапуске apache2 (но, как уже упоминалось, это заставит вас придерживаться определенной настройки), предыдущая команда будет вызываться с помощью exec. Например, предыдущая команда не будет работать, когда вы запускаете PHP и Apache2 за докером. Использование такой функции, как posix_getppid(), может решить эту проблему, но функция получает родительский идентификатор, поэтому для Apache2 это не важно.
С другой стороны, использование случайного числа/строки в качестве ключей приведет к потере сохраненных зашифрованных/хешированных данных.
В качестве альтернативы вы можете сохранить ключ в файле tmp, базе данных или сеансе и использовать cronjob или запланированное задание для периодической перезагрузки ключа.
Спасибо за идею, но мне нужен «ключ», который будет уникальным с очень высокой вероятностью, например псевдослучайная строка байтов, сгенерированная CSRNG. Что-то вроде идентификатора процесса или IP-адреса хоста недостаточно. Создать случайный «ключ» легко, например. с openssl_random_pseudo_bytes(), но проблема заключается в том, чтобы сделать его постоянным при многократном вызове скрипта (на протяжении всего времени существования экземпляра сервера)! Пришлось придумать собственное решение, поскольку использование базы данных «в памяти» или даже локального файла было бы неприятным решением.
Я не использую «уникальный» ключ для долгосрочного шифрования/хранения, не волнуйтесь! Мне просто нужен ключ для вычисления MAC на основе информации, которую я предоставлю клиенту. Когда клиент позже вернет информацию на сервер, мне нужно убедиться, что информация действительно исходит от этого конкретного экземпляра сервера. Таким образом, ключ действительно должен быть уникальным для каждого экземпляра сервера. Но нам не нужно выполнять эту проверку более чем на несколько минут или около того, то есть нет необходимости сохранять ключ в долгосрочной перспективе.
Поскольку удовлетворительного решения не было, я потратил день на создание простого расширения PHP, которое делает именно то, что мне нужно. Поскольку это может быть полезно для других, я публикую это здесь.
Он генерирует уникальный идентификатор с помощью системного вызова getentropy() в функции инициализации модуля, преобразует его в строку Base64 и предоставляет его пользовательскому скрипту PHP как константу через REGISTER_STRING_CONSTANT(). Это простой и эффективный способ гарантировать, что идентификатор генерируется ровно один раз для каждого экземпляра сервера при запуске сервера. Затем он сохраняется в «статической» памяти. Доступ к идентификатору как к константе PHP также намного проще, чем другие подходы 😀
Его можно использовать следующим образом:
<?php
echo 'Unique instance ID: "' . PHP_INSTANCE_UNIQID . '"' . PHP_EOL;
?>
<?php
$unique_key = hash_hkdf('sha256', PHP_INSTANCE_UNIQID_BINARY, 16, 'my-context');
?>
См. также:
https://php-uniqueid.bitbucket.io/
Я все еще немного удивлен, что чего-то подобного еще не существует в PHP...
Обратите внимание, что он будет работать только с mod-php. В случае CGI новый ключ будет генерироваться для каждого процесса CGI.
@Olivier «классическая» компьютерная графика все еще актуальна? Я думаю, что сейчас это все FastCGI, когда у вас есть один длительный процесс, который взаимодействует с «основным» серверным процессом. Так что это тоже должно работать нормально.
FastCGI использует пул рабочих процессов. В вашем случае это означает, что у каждого работника будет свой ключ.
@Оливье, я думаю, это вопрос конфигурации? В любом случае, я думаю, что смогу с этим жить. В конце концов, если бы у нас было несколько отдельных рабочих процессов, то подход на основе APCu или любое подобное решение столкнулись бы с той же «проблемой», верно? И в моем реальном проекте я все равно не использую (Fast)CGI.
Правильно, в настройке FCGI память APCu также не используется совместно между экземплярами.
Кстати, PHP_INSTANCE_UNIQID теперь используется несколькими процессами (для каждого пользователя) 😉
Вы можете использовать встроенный кеш APCu PHP для постоянного хранения случайного значения в каждом работающем экземпляре сервера до перезапуска:
$instancekey = apcu_fetch('instancekey');
if (!$instancekey) {
$instancekey = uniqid(); // or use whatever RNG you like
apcu_store('instancekey', $instancekey, 0);
}
Спасибо! На данный момент это кажется наиболее многообещающим (простым) решением, не требующим дополнительного расширения PHP. Однако мне приходят на ум две проблемы: инициализация не является «атомарной», т. е. мы можем получить два вызова сценария с разными ключами (на начальном этапе). Кроме того, насколько я понимаю, APCu не гарантирует, что запись останется в кеше, пока работает сервер, даже если мы не установили явный TTL. Записи, к которым в последнее время не было доступа, могут «мягко» истечь и быть удалены...
Обновление: я думаю, что использование apcu_entry() можно использовать для обеспечения «атомарной» инициализации. Но проблема с истечением срока действия записей кэша все еще остается. Из документов APCu: «Запись имеет мягкий срок действия, если не установлен TTL для каждой записи [...] и время доступа к записи старше, чем глобальный TTL. Записи с мягким сроком действия доступны посредством операции поиска, но могут быть удалено из кэша в любое время».
@ Schraubstock1990 ну, по крайней мере, я попробовал. Честно говоря, я настоятельно рекомендую вам не делать то, что вы просили, а вместо этого сохранить ключ HMAC в пользователе _$_SESSION. Переменные сеанса не доступны для чтения пользователем, они хранятся локально на сервере: пользователю предоставляется только идентификатор сеанса, а не контент. Сеанс все еще существует после перезапуска службы и может даже быть сохранен таким образом, чтобы его значение существовало после перезагрузки. Таким образом, пользователь не заметит перезапуск вашего сервера между запросами. В вашем собственном решении второй запрос завершается неудачей, когда вы перезагружаете сервер между ними.
@ Schraubstock1990 Почему ключ не предназначен для каждого пользователя? Зачем использовать один и тот же ключ HMAC между пользователями в течение длительного периода времени? (до перезапуска) Я вижу здесь огромную проблему с безопасностью. Не делай этого. Используйте уникальный ключ для каждого сеанса, а не для каждого сервера. Я не вижу причин не делать этого.
Использование одного и того же MAC-ключа для всех пользователей не представляет угрозы безопасности, поскольку мы никогда не показываем этот ключ пользователям. Мы хотим гарантировать, что пользователь вернет «подлинное» сообщение на тот же экземпляр сервера, откуда он его получил. Если пользователь изменяет данное сообщение, мы обнаруживаем это при возврате, поскольку MAC-адрес больше не соответствует. То же самое, если сообщение было возвращено на «неправильный» сервер, поскольку на другом сервере есть другой ключ MAC, и поэтому MAC не будет совпадать. Использование отдельного ключа для каждого пользователя здесь не улучшает ситуацию, а добавляет новые проблемы (управление сессиями).
Поместите его в файл/базу данных/cookie.