Использование SHA-1: когда допустимо, а когда нет

SHA-1 (Secure Hash Algorithm 1) — криптографическая хеш-функция, преобразующая произвольный входной набор данных в 160-битное (20 байт) хеш-значение. В контексте Web Crypto API используется через интерфейс SubtleCrypto, предоставляющий асинхронные методы для криптографических операций.


Получение SHA-1 хеша

Метод digest позволяет вычислить хеш:

const data = new TextEncoder().encode("example");

const hashBuffer = await crypto.subtle.digest("SHA-1", data);

const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");

console.log(hashHex);

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

  • Входные данные должны быть представлены в виде ArrayBuffer или TypedArray
  • Результат — ArrayBuffer
  • Операция асинхронная (возвращает Promise)

Криптографические свойства SHA-1

SHA-1 исторически считалась безопасной благодаря следующим свойствам:

  • Детерминированность: одинаковый вход → одинаковый хеш
  • Устойчивость к предобразу (preimage resistance)
  • Устойчивость ко второму предобразу
  • Коллизионная устойчивость

Однако ключевая проблема — нарушенная коллизионная устойчивость.


Коллизии и их практическое значение

Коллизия — ситуация, при которой два различных входа дают одинаковый хеш.

В 2017 году была продемонстрирована практическая атака (SHAttered), доказавшая возможность генерации коллизий для SHA-1 с разумными вычислительными затратами.

Это означает:

  • злоумышленник может создать два разных файла с одинаковым SHA-1 хешем
  • подпись одного файла может быть использована для другого

Поддержка SHA-1 в Web Crypto API

Несмотря на уязвимости, SHA-1 остаётся доступной:

crypto.subtle.digest("SHA-1", data);

Поддерживается во всех современных браузерах, но с ограничениями в некоторых сценариях (например, сертификаты TLS).


Когда использование SHA-1 допустимо

1. Некритичные задачи

  • контроль целостности без угрозы атак
  • дедупликация данных
  • кеширование

Пример:

// генерация ключа для кеша
const key = await crypto.subtle.digest("SHA-1", data);

2. Совместимость с устаревшими системами

Некоторые протоколы и API требуют SHA-1:

  • старые системы аутентификации
  • legacy API
  • устаревшие форматы подписи

3. Внутренние системы без внешнего доступа

При отсутствии атакующей модели (например, локальные инструменты разработки)


Когда использование SHA-1 недопустимо

1. Цифровые подписи

Использование SHA-1 в подписях (например, RSA-PKCS1-v1_5 с SHA-1):

crypto.subtle.sign(
  { name: "RSASSA-PKCS1-v1_5" },
  privateKey,
  data
);

Недопустимо, если используется SHA-1:

  • возможна подмена подписанного сообщения через коллизию

2. Проверка целостности при наличии злоумышленника

Любые случаи, где данные могут быть подменены:

  • загрузка файлов
  • обновления ПО
  • сетевые протоколы

3. Хеширование паролей

SHA-1 категорически не подходит:

  • слишком быстрая → уязвимость к brute-force
  • отсутствие соли и адаптивности

Вместо этого:

  • PBKDF2 (доступен в Web Crypto)
  • bcrypt / scrypt / Argon2 (вне Web Crypto)

4. Сертификаты и TLS

Современные браузеры:

  • блокируют сертификаты с SHA-1
  • считают их небезопасными

Альтернативы SHA-1 в Web Crypto API

SHA-256

await crypto.subtle.digest("SHA-256", data);
  • 256 бит
  • устойчива к коллизиям (на текущий момент)
  • стандарт де-факто

SHA-384 / SHA-512

Используются при повышенных требованиях безопасности:

await crypto.subtle.digest("SHA-512", data);

Сравнение SHA-1 и SHA-256

Свойство SHA-1 SHA-256
Размер хеша 160 бит 256 бит
Коллизионная стойкость Нарушена Надёжна
Производительность Быстрее Немного медленнее
Безопасность Устарела Современная

Типичные ошибки при использовании SHA-1

Использование по привычке

crypto.subtle.digest("SHA-1", data);

Причина — историческое распространение, а не актуальная безопасность.


Игнорирование модели угроз

SHA-1 может быть безопасна в одном контексте и критически уязвима в другом.


Использование в новых проектах

Любой новый код с SHA-1 — потенциальный технический долг.


Проверка алгоритма в коде

Рекомендуется явно задавать алгоритм и избегать неявных значений:

const algorithm = "SHA-256"; // вместо SHA-1

Пример безопасной замены

Было:

await crypto.subtle.digest("SHA-1", data);

Стало:

await crypto.subtle.digest("SHA-256", data);

Влияние на производительность

SHA-1 немного быстрее, но:

  • разница незначительна в большинстве веб-приложений
  • безопасность важнее микрооптимизаций

Использование в HMAC

SHA-1 может применяться в HMAC, но также нежелательна:

await crypto.subtle.importKey(
  "raw",
  keyData,
  { name: "HMAC", hash: "SHA-1" },
  false,
  ["sign"]
);

Рекомендуется:

{ name: "HMAC", hash: "SHA-256" }

Практические рекомендации

  • избегать SHA-1 в новом коде
  • использовать только при строгой необходимости совместимости
  • не применять в криптографически значимых задачах
  • постепенно мигрировать существующие системы

Миграция с SHA-1

Шаги:

  1. Найти все использования SHA-1
  2. Определить критичность
  3. Заменить на SHA-256 или выше
  4. Обновить зависимые системы

Итоговое правило

SHA-1 — инструмент прошлого, допустимый только в условиях строгой необходимости совместимости и отсутствия угроз. В любых сценариях, связанных с безопасностью, должен быть полностью исключён.