Атаки по времени выполнения относятся к классу побочных каналов (side-channel attacks), при которых злоумышленник не взламывает алгоритм напрямую, а анализирует косвенные признаки его работы. Основной наблюдаемый параметр — время выполнения операций при разных входных данных.
В контексте проверки паролей это означает: даже если пароль надёжно захэширован, различия во времени сравнения могут раскрыть информацию о правильности отдельных символов или структуры строки.
Классическая проблема возникает при сравнении строк или хэшей с использованием стандартных операторов:
if (userHash === storedHash) {
// доступ разрешён
}
или при ручной проверке:
function compare(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
На первый взгляд логика корректна, однако присутствует критическая уязвимость: функция завершает выполнение раньше при первом несовпадении. Это приводит к тому, что:
Атака строится на статистическом анализе задержек:
Отправляется большое количество запросов с различными вариантами пароля.
Измеряется среднее время ответа сервера.
Выявляются закономерности:
Постепенно восстанавливается корректный хэш или пароль.
Даже микросекундные различия становятся значимыми при миллионах запросов.
JavaScript-операции сравнения не гарантируют постоянное время выполнения. В частности:
=== и == могут завершаться раньше при
несовпадении;В результате даже “простая” проверка хэша становится источником информации.
Библиотеки для хэширования паролей, такие как
password-hash в JavaScript, часто используются для:
Типичная ошибка возникает не в самом хэшировании, а в сравнении результата:
const passwordHash = require('password-hash');
const hashed = passwordHash.generate('secret');
passwordHash.verify('guess', hashed);
Если внутренняя реализация сравнения не защищена от timing attack, уязвимость сохраняется на уровне приложения.
Для защиты используется сравнение с постоянным временем выполнения (constant-time comparison). Его цель — гарантировать, что время работы функции зависит только от длины входных данных, но не от их содержимого.
Принцип:
В стандартной библиотеке Node.js существует защищённая функция:
const crypto = require('crypto');
function safeCompare(a, b) {
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
if (bufA.length !== bufB.length) {
return false;
}
return crypto.timingSafeEqual(bufA, bufB);
}
Особенности:
Следующий код остаётся уязвимым:
function insecureCompare(a, b) {
return a === b;
}
или:
function insecureCompare(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
Проблема не в синтаксисе, а в поведении ветвления и различиях времени выполнения.
Практическая реализация timing attack осложняется шумом:
Однако злоумышленник компенсирует это:
Даже при высокой погрешности утечки сохраняются.
Timing attack чаще направлена не на сам пароль, а на:
Современные алгоритмы:
спроектированы так, чтобы быть медленными и усложнять перебор, но они не решают проблему сравнения сами по себе.
Строковые сравнения не обеспечивают constant-time свойства.
Любая логика вида if (...) return false создаёт
различимое поведение.
Сравнение только части данных (например, первых байтов) усиливает утечку.
Даже проверка длины может быть источником информации, если выполняется неаккуратно.
При использовании библиотек хэширования необходимо:
crypto.timingSafeEqual;Дополнительно применяются:
Движок V8 может непредсказуемо оптимизировать код:
Это делает невозможным полагаться на “кажется одинаковое время выполнения” в JavaScript без специальных примитивов.
Timing attack становится реальной угрозой в ситуациях, где:
В связке с password hashing уязвимость проявляется не на уровне криптографического алгоритма, а на уровне реализации проверки, где даже незначительные различия времени выполнения превращаются в канал утечки информации.