Природа атак по времени
выполнения
Timing attack возникает, когда злоумышленник получает информацию о
секретных данных через различия во времени выполнения операций. В
криптографическом контексте даже микроскопические различия — условные
ветвления, доступ к памяти, ранний выход из функции — способны раскрыть
ключ или часть секретной информации.
В JavaScript эта проблема усиливается особенностями среды
выполнения:
- JIT-компиляция (V8, SpiderMonkey, JavaScriptCore)
- оптимизации горячих веток кода
- динамическая типизация
- кэширование объектов и массивов
- непредсказуемость работы сборщика мусора
Даже при отсутствии явных ошибок логики, реализация может утечь через
косвенные признаки.
Криптографические
библиотеки и модель постоянного времени
Библиотеки вроде TweetNaCl.js и nacl.js основаны на оригинальной NaCl
(Networking and Cryptography library), разработанной с жёстким
требованием:
все операции над секретными данными должны выполняться в
постоянное время (constant-time).
Это означает:
- отсутствие ветвлений, зависящих от секретных данных
- отсутствие ранних выходов (early return)
- одинаковый паттерн доступа к памяти независимо от ключа или
сообщения
- использование битовых операций вместо условных переходов
Однако перенос этой модели в JavaScript имеет ограничения, связанные
с абстракцией виртуальной машины.
Особенности
JavaScript-реализаций и границы защиты
TweetNaCl.js — порт оригинальной C-реализации, транслированный в
JavaScript с сохранением логики constant-time. При этом возникает ряд
проблем:
1.
JIT-оптимизации могут разрушать предполагаемую модель времени
JavaScript-движок может:
- инлайнить функции
- изменять порядок инструкций
- оптимизировать условные выражения
- заменять циклы на более эффективные конструкции
Даже если исходный код написан как constant-time, итоговое поведение
может зависеть от движка.
2. Неравномерное
время операций над массивами
Работа с Uint8Array и ArrayBuffer обычно
более предсказуема, чем с обычными массивами, но:
- доступ к памяти может кэшироваться
- выход за пределы может приводить к деградации
производительности
- разные CPU и браузеры имеют различную латентность операций
3. Различия в обработке чисел
JavaScript использует IEEE-754 double precision, что приводит к:
- дополнительным преобразованиям типов
- различной стоимости арифметических операций
- возможным ветвлениям внутри движка при оптимизации
Классические
источники утечек в криптографическом JS-коде
Условные конструкции на
основе секретов
Наиболее опасный класс ошибок:
if (secretByte === 0) {
// выполнение зависит от секретного значения
}
Даже если условие кажется безобидным, различие во времени исполнения
фиксируется внешним наблюдателем.
Сравнение строк и массивов
Типичная уязвимость:
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;
}
Проблема: ранний выход создаёт корреляцию между временем выполнения и
позицией первого несовпадения.
В криптографии используется:
- побитовое сравнение без ветвлений
- аккумуляция результата через XOR
Побочные каналы через память
Даже при constant-time логике возможны утечки через:
- кэш процессора (cache timing)
- различия в доступе к памяти (cache hits/misses)
- предсказатель ветвлений CPU
- garbage collector паузы
Подход NaCl: дисциплина
constant-time
NaCl-подход включает несколько принципов:
Арифметика вместо ветвлений
Условные операции заменяются на:
- маски
- побитовые операции AND/OR/XOR
- арифметические комбинации
Пример идеи:
- вместо
if (x) y = a else y = b
- используется
y = (mask & a) | (~mask & b)
Равномерный доступ к памяти
Алгоритмы проектируются так, чтобы:
- каждый шаг цикла обращался к памяти одинаково
- отсутствовали данные-зависимые индексы массивов
Отказ от секретных условий
Любые проверки:
- длины ключа
- корректности подписи
- валидности сообщения
выполняются так, чтобы не раскрывать информацию о структуре входа
через время выполнения.
TweetNaCl.js стремится сохранить свойства оригинальной
библиотеки:
- использование
Uint8Array
- реализация операций над 32-битными словами через битовую
арифметику
- отсутствие условных переходов на основе секретных данных
Однако существуют ограничения JS-уровня:
1. Невозможность
полного контроля над движком
Даже идеально написанный код не гарантирует:
- одинаковую машинную оптимизацию на всех платформах
- стабильное время выполнения на разных браузерах
2. Абстракция памяти
В отличие от C:
- нет прямого управления кэшем
- отсутствует контроль над выравниванием памяти
- JIT может реорганизовать доступ к данным
3. Влияние окружения
На timing могут влиять:
- загрузка CPU
- работа других вкладок браузера
- энергосберегающие режимы
- особенности мобильных процессоров
Side-channel beyond timing
Timing attacks — лишь один класс побочных каналов. В
криптографических реализациях также учитываются:
Cache-based attacks
Различия в:
- попадании в L1/L2/L3 кэш
- промахах кэша при доступе к данным
Branch prediction leakage
CPU предсказывает ветвления, и:
- неверные предсказания создают измеримые задержки
- это может коррелировать с секретами
Memory access patterns
Даже при одинаковом времени исполнения:
- разные адреса памяти могут оставлять следы в подсистеме кэша
- доступ к различным областям массива может быть измерим
Практика защиты в
JavaScript-криптографии
Использование Typed Arrays
Uint8Array, Uint32Array:
- уменьшают накладные расходы
- обеспечивают более предсказуемый доступ к памяти
Constant-time primitives
Критические операции должны:
- избегать
if на основе секретов
- использовать побитовые маски
- всегда выполнять одинаковое количество операций
Изоляция секретов
Секретные данные:
- не должны покидать область криптографических функций
- не должны использоваться в UI-логике
- не должны участвовать в форматировании или логировании
Минимизация
взаимодействия с JS-рантаймом
Опасные источники утечек:
console.log с секретами
- конкатенация строк с ключами
- сериализация объектов
- неявные преобразования типов
Ограниченность гарантий в
браузере
Даже при использовании TweetNaCl.js:
- браузер не является безопасной моделью против продвинутых
side-channel атак
- постоянное время в JS — это приближение, а не строгая гарантия
- атаки уровня Spectre/Meltdown концептуально обходят многие
абстракции
Криптография в JavaScript рассматривается как:
- практическая защита от сетевых атак
- но не как защита против локального высокоточного наблюдателя
выполнения
Контроль поведения
оптимизатора
Некоторые реализации применяют техники:
- «разогрева» функций (warm-up) для стабилизации JIT
- предотвращения деоптимизации горячих путей
- принудительного использования
Uint8Array вместо generic
arrays
- минимизации динамических свойств объектов
В контексте side-channel атак модель угроз включает:
- наблюдение за временем выполнения функций
- анализ различий между корректными и некорректными входами
- статистическую корреляцию задержек
- эксплуатацию особенностей JS-движка
Основной принцип остаётся неизменным:
любая зависимость управления потоком или доступом к памяти от
секретных данных является потенциальной утечкой, независимо от языка
высокого уровня.