Шифрование значений перед сохранением

Idb-keyval — это легковесная обертка для работы с IndexedDB в браузере, обеспечивающая простое API для хранения ключ-значение. Одним из важных аспектов работы с данными является безопасность, особенно когда необходимо хранить конфиденциальную информацию. В этом контексте шифрование значений перед сохранением становится критически важным.

Использование криптографии Web Crypto API

Для шифрования данных можно использовать встроенный в браузеры Web Crypto API. Он предоставляет безопасные методы для генерации ключей, шифрования и расшифровки данных. Основные операции:

  • Генерация ключа: crypto.subtle.generateKey()
  • Шифрование: crypto.subtle.encrypt()
  • Расшифровка: crypto.subtle.decrypt()
  • Импорт/экспорт ключей: crypto.subtle.importKey(), crypto.subtle.exportKey()

Пример генерации симметричного ключа AES-GCM:

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

Шифрование данных перед сохранением в Idb-keyval

Idb-keyval предоставляет методы set, get, del, update для работы с хранилищем. Для безопасного хранения данных необходимо предварительно зашифровать их, а затем сохранить.

import { set, get } from 'idb-keyval';

async function encryptAndStore(keyName, value, cryptoKey) {
  const encoder = new TextEncoder();
  const encodedValue = encoder.encode(JSON.stringify(value));
  
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encryptedValue = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    cryptoKey,
    encodedValue
  );

  // Сохраняем объект с IV и зашифрованными данными
  await set(keyName, { iv: Array.from(iv), data: Array.from(new Uint8Array(encryptedValue)) });
}

В этом примере:

  • TextEncoder используется для преобразования строки в байтовый массив.
  • IV (Initialization Vector) нужен для AES-GCM и должен быть случайным.
  • Данные сохраняются в виде объекта { iv, data }, чтобы их можно было корректно расшифровать.

Расшифровка данных при извлечении

Для восстановления исходного значения из хранилища выполняется обратный процесс:

async function decryptFromStore(keyName, cryptoKey) {
  const stored = await get(keyName);
  if (!stored) return null;

  const { iv, data } = stored;
  const decryptedValue = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv: new Uint8Array(iv) },
    cryptoKey,
    new Uint8Array(data)
  );

  const decoder = new TextDecoder();
  return JSON.parse(decoder.decode(decryptedValue));
}

Ключевые моменты:

  • TextDecoder преобразует байтовый массив обратно в строку.
  • Обязательно использовать тот же ключ и IV, что были при шифровании.

Управление ключами шифрования

Хранение ключей — критический момент безопасности. Основные подходы:

  • Генерация ключа один раз и хранение в IndexedDB с использованием механизма защиты (например, через crypto.subtle.wrapKey и сохранение зашифрованного ключа).
  • Использование пароля пользователя для генерации ключа через PBKDF2:
const passwordKey = await crypto.subtle.importKey(
  "raw",
  new TextEncoder().encode(userPassword),
  { name: "PBKDF2" },
  false,
  ["deriveKey"]
);

const derivedKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt: crypto.getRandomValues(new Uint8Array(16)),
    iterations: 100000,
    hash: "SHA-256"
  },
  passwordKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

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

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

  1. Всегда использовать случайный IV для каждого шифрования. Повторное использование снижает безопасность.
  2. Не сохранять ключи в открытом виде в коде или локальном хранилище.
  3. Сериализация объектов перед шифрованием обязательна. JSON.stringify обеспечивает совместимость типов данных.
  4. Проверка целостности: AES-GCM обеспечивает встроенную аутентификацию, предотвращая подделку данных.
  5. Обработка ошибок: операции шифрования и расшифровки асинхронны и могут выбрасывать исключения, которые необходимо обрабатывать.

Шифрование значений перед сохранением в Idb-keyval позволяет безопасно хранить конфиденциальные данные в браузере, объединяя простоту API библиотеки и современные возможности Web Crypto API.