Timing attack: как работает и как защититься

Атаки по времени выполнения относятся к классу побочных каналов (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;
}

На первый взгляд логика корректна, однако присутствует критическая уязвимость: функция завершает выполнение раньше при первом несовпадении. Это приводит к тому, что:

  • чем больше совпадающих символов в начале строки, тем дольше выполняется сравнение;
  • злоумышленник может измерять задержки и восстанавливать данные по частям.

Механизм атаки по времени

Атака строится на статистическом анализе задержек:

  1. Отправляется большое количество запросов с различными вариантами пароля.

  2. Измеряется среднее время ответа сервера.

  3. Выявляются закономерности:

    • совпадение первых символов увеличивает время ответа;
    • несовпадение на ранних позициях сокращает выполнение;
  4. Постепенно восстанавливается корректный хэш или пароль.

Даже микросекундные различия становятся значимыми при миллионах запросов.


Почему обычные сравнения в JavaScript уязвимы

JavaScript-операции сравнения не гарантируют постоянное время выполнения. В частности:

  • === и == могут завершаться раньше при несовпадении;
  • строковые операции оптимизируются движком V8;
  • JIT-компиляция создаёт непредсказуемые ветвления по времени;
  • ранний выход из циклов усиливает утечку.

В результате даже “простая” проверка хэша становится источником информации.


Проблема в контексте password-hash библиотек

Библиотеки для хэширования паролей, такие как password-hash в JavaScript, часто используются для:

  • создания хэша пароля;
  • проверки введённого значения;
  • хранения соли и параметров алгоритма.

Типичная ошибка возникает не в самом хэшировании, а в сравнении результата:

const passwordHash = require('password-hash');

const hashed = passwordHash.generate('secret');

passwordHash.verify('guess', hashed);

Если внутренняя реализация сравнения не защищена от timing attack, уязвимость сохраняется на уровне приложения.


Безопасное сравнение: принцип constant-time

Для защиты используется сравнение с постоянным временем выполнения (constant-time comparison). Его цель — гарантировать, что время работы функции зависит только от длины входных данных, но не от их содержимого.

Принцип:

  • нельзя использовать ранний выход из функции;
  • необходимо обработать все символы;
  • результат вычисляется через побитовые операции.

Защита в Node.js: crypto.timingSafeEqual

В стандартной библиотеке 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);
}

Особенности:

  • сравнение выполняется за фиксированное время;
  • отсутствует ранний выход при несовпадении;
  • используется низкоуровневая реализация на C.

Типичная небезопасная реализация

Следующий код остаётся уязвимым:

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 чаще направлена не на сам пароль, а на:

  • сравнение хэшей;
  • проверку корректности входа;
  • промежуточные представления данных.

Современные алгоритмы:

  • bcrypt;
  • scrypt;
  • Argon2

спроектированы так, чтобы быть медленными и усложнять перебор, но они не решают проблему сравнения сами по себе.


Частые ошибки при реализации защиты

Использование строк вместо буферов

Строковые сравнения не обеспечивают constant-time свойства.

Ранний возврат из функции

Любая логика вида if (...) return false создаёт различимое поведение.

Частичное сравнение

Сравнение только части данных (например, первых байтов) усиливает утечку.

Игнорирование длины

Даже проверка длины может быть источником информации, если выполняется неаккуратно.


Усиление защиты при работе с password-hash

При использовании библиотек хэширования необходимо:

  • выполнять сравнение через crypto.timingSafeEqual;
  • нормализовать входные данные до Buffer;
  • избегать пользовательских реализаций compare;
  • исключать ветвления, зависящие от содержимого хэша.

Дополнительно применяются:

  • ограничение числа попыток входа;
  • задержки между запросами;
  • мониторинг аномальной активности.

Поведение V8 и влияние оптимизаций

Движок V8 может непредсказуемо оптимизировать код:

  • инлайнить функции сравнения;
  • перестраивать циклы;
  • менять порядок инструкций;
  • использовать SIMD-оптимизации.

Это делает невозможным полагаться на “кажется одинаковое время выполнения” в JavaScript без специальных примитивов.


Итоговая модель угрозы

Timing attack становится реальной угрозой в ситуациях, где:

  • сравниваются секретные значения;
  • присутствует сетевой доступ к проверке;
  • отсутствует constant-time сравнение;
  • система обрабатывает большое число запросов.

В связке с password hashing уязвимость проявляется не на уровне криптографического алгоритма, а на уровне реализации проверки, где даже незначительные различия времени выполнения превращаются в канал утечки информации.