Атака за часом - зловмисник вимірює, скільки часу сервер обробляє запит, і з різниці робить висновки про секрет.
Класичний приклад - порівняння рядків:
if ($providedToken === $storedToken) { ... }
Звичайне порівняння зупиняється на першому символі, що не збігся. Токен, у якого правильні перші 10 символів, перевіряється трохи довше, ніж токен з неправильним першим символом. Вимірюючи час тисяч запитів і усереднюючи шум, теоретично можна підбирати токен посимвольно: замість 62^32 варіантів - 62 × 32 спроби.
hash_equals($known, $user) порівнює рядки за постійний час - незалежно від того, де саме відрізняються символи:
if (hash_equals($storedSignature, $providedSignature)) { ... }
Порядок аргументів важливий: перший - відоме (секретне) значення, другий - від користувача.
Де потрібне порівняння в постійному часі:
- підписи вебхуків і запитів (HMAC);
- API-ключі й токени, якщо порівнюються як рядки;
- коди підтвердження, CSRF-токени;
- підписані URL.
Laravel використовує hash_equals для перевірки CSRF-токенів, підписаних маршрутів, токенів Sanctum.
Інші джерела витоку через час:
- перевірка пароля лише для існуючих користувачів: для неіснуючого email сервер відповідає миттєво, для існуючого - після bcrypt (сотні мілісекунд). Це дає перелік облікових записів. Захист - виконувати хешування завжди;
- різна робота залежно від результату: відправка листа лише якщо обліковий запис існує, запит до бази лише для певних випадків;
- пошук у базі за секретом:
WHERE token = ?- час пошуку за індексом теоретично теж може залежати від значення. Тому надійніше шукати за ідентифікатором, а секрет порівнювати окремо черезhash_equals(так зроблено в Sanctum: токен має виглядid|секрет).
Наскільки це реально в інтернеті: мережевий шум у мілісекунди набагато більший за наносекундні різниці порівняння рядків, тож атака на сирі рядкові порівняння через інтернет складна. Але з тисячами вимірювань і статистикою вона можлива, а в межах одного хмарного регіону шум значно менший. Порівняння в постійному часі коштує нічого - немає причин ризикувати.
Не лише рядки: криптографічні реалізації мають бути «constant-time» загалом - тому використовують перевірені бібліотеки (sodium_*, openssl_*), а не власні реалізації алгоритмів.