Одноразовые номера - это банка червей.
Нет, действительно, одна из причин для нескольких записей CAESAR заключалась в разработке аутентифицированной схемы шифрования, предпочтительно на основе потокового шифра, чтоустойчив к повторному использованию.(Повторное использование одноразового номера с AES-CTR, например, нарушает конфиденциальность вашего сообщения до такой степени, что студент первого курса может его расшифровать.)
Существует три основных направления мысли с одноразовыми номерами:
- В криптографии с симметричным ключом: используйте увеличивающийся счетчик, стараясь никогда не использовать его повторно.(Это также означает использование отдельного счетчика для отправителя и получателя.) Для этого требуется программирование с сохранением состояния (т. Е. Сохранение одноразового номера где-то, чтобы каждый запрос не начинался с
1
). - Случайные одноразовые выражения с состоянием.Генерация случайного одноразового номера с последующим запоминанием для проверки позже. Это стратегия, используемая для победы над атаками CSRF, , которая звучит ближе к тому, о чем здесь просят.
- Большие случайные одноразовые числа без состояния.Имея безопасный генератор случайных чисел, вы почти гарантируете, что никогда не будете повторять одноразовый номер дважды в своей жизни.Это стратегия, используемая NaCl для шифрования.
Итак, имея в виду, основные вопросы:
- Какой изВышеуказанные школы мысли наиболее актуальны для проблемы, которую вы пытаетесь решить?
- Как вы генерируете одноразовый номер?
- Как вы проверяете одноразовый номер?
Генерация одноразового номера
Ответом на вопрос 2 для любого случайного одноразового номера является использование CSPRNG.Для проектов PHP это означает одно из:
random_bytes()
для проектов PHP 7+ - paragonie / random_compat , PHP5 polyfill для
random_bytes()
- ircmaxell / RandomLib , который представляет собой швейцарский армейский нож утилит случайности, который большинство проектов, которые имеют дело со случайностью (например, сброс паролей пихты), должны рассмотреть использование вместо броскаих собственные
Эти два морально эквивалентны:
$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$_SESSION['nonce'] [] = $generator->generate(32);
и
$_SESSION['nonce'] []= random_bytes(32);
Проверка одноразового номера
С учетом состояния
Одноразовые выражения с состоянием просты и рекомендуются:
$found = array_search($nonce, $_SESSION['nonces']);
if (!$found) {
throw new Exception("Nonce not found! Handle this or the app crashes");
}
// Yay, now delete it.
unset($_SESSION['nonce'][$found]);
Не стесняйтесь заменить array_search()
базой данных или поиском в memcached и т. Д.
Без сохранения состояния (здесь будут драконы)
Это сложная проблема, которую нужно решить: вам нужен какой-то способ предотвратить атаки воспроизведения, но ваш сервер имеет полную амнезию после каждого HTTP-запроса.
Единственным разумным решением будет аутентификация даты / времени истеченияминимизировать полезность повторных атак.Например:
// Generating a message bearing a nonce
$nonce = random_bytes(32);
$expires = new DateTime('now')
->add(new DateInterval('PT01H'));
$message = json_encode([
'nonce' => base64_encode($nonce),
'expires' => $expires->format('Y-m-d\TH:i:s')
]);
$publishThis = base64_encode(
hash_hmac('sha256', $message, $authenticationKey, true) . $message
);
// Validating a message and retrieving the nonce
$decoded = base64_decode($input);
if ($decoded === false) {
throw new Exception("Encoding error");
}
$mac = mb_substr($decoded, 0, 32, '8bit'); // stored
$message = mb_substr($decoded, 32, null, '8bit');
$calc = hash_hmac('sha256', $message, $authenticationKey, true); // calcuated
if (!hash_equals($calc, $mac)) {
throw new Exception("Invalid MAC");
}
$message = json_decode($message);
$currTime = new DateTime('NOW');
$expireTime = new DateTime($message->expires);
if ($currTime > $expireTime) {
throw new Exception("Expired token");
}
$nonce = $message->nonce; // Valid (for one hour)
Внимательный наблюдатель заметит, что это в основном нестандартный вариант JSON Web Tokens .