Клиентское хранилище в браузере по своей природе не является защищённым. Любые данные, сохранённые через IndexedDB, WebSQL или localStorage, доступны через инструменты разработчика, расширения браузера и потенциально вредоносные скрипты, внедрённые в контекст страницы. Даже при использовании абстракции вроде localForage уровень защиты не меняется — библиотека лишь унифицирует API поверх разных движков хранения.
Шифрование перед сохранением решает одну ключевую задачу: превращает данные в форму, непригодную для чтения без знания ключа. Это особенно важно при хранении:
При этом важно учитывать, что шифрование на клиенте защищает не от всех угроз. Оно не предотвращает кражу ключа, XSS-атаки или выполнение вредоносного кода в контексте страницы, но существенно усложняет прямое чтение данных из хранилища.
localForage предоставляет асинхронный интерфейс:
setItem(key, value)getItem(key)removeItem(key)clear()Шифрование внедряется как промежуточный слой между приложением и хранилищем.
Типовая схема:
Абстракция может быть реализована как обёртка:
secureSetItemsecureGetItemили как расширенный сервис хранения.
В браузерной среде стандартом де-факто является Web Crypto API. Он обеспечивает аппаратно ускоренное шифрование и безопасную работу с ключами.
На практике применяются следующие алгоритмы:
Наиболее распространённый вариант:
Ключевая особенность — наличие IV (initialization vector), который должен быть уникальным для каждой операции шифрования.
Если ключ формируется из пароля пользователя, требуется деривация:
localForage может хранить строки, объекты, массивы и бинарные данные. При шифровании чаще всего используется сериализация в JSON-структуру:
{
"iv": "...",
"ciphertext": "...",
"salt": "...",
"version": 1
}
Каждое поле выполняет роль:
Шифрование через AES-GCM:
async function encryptData(key, data) {
const encoded = new TextEncoder().encode(JSON.stringify(data));
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv
},
key,
encoded
);
return {
iv: Array.from(iv),
ciphertext: Array.from(new Uint8Array(encrypted))
};
}
Дешифрование:
async function decryptData(key, payload) {
const iv = new Uint8Array(payload.iv);
const ciphertext = new Uint8Array(payload.ciphertext);
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv
},
key,
ciphertext
);
return JSON.parse(new TextDecoder().decode(decrypted));
}
Шифрующий слой оборачивает стандартные операции:
import localForage from "localforage";
class SecureStorage {
constructor(cryptoKey) {
this.key = cryptoKey;
}
async setItem(key, value) {
const encrypted = await encryptData(this.key, value);
return localForage.setItem(key, encrypted);
}
async getItem(key) {
const encrypted = await localForage.getItem(key);
if (!encrypted) return null;
return decryptData(this.key, encrypted);
}
async removeItem(key) {
return localForage.removeItem(key);
}
}
Такой подход сохраняет все преимущества localForage:
Наиболее сложный аспект — не само шифрование, а хранение ключа.
В памяти приложения
В derive-режиме (из пароля)
В IndexedDB без шифрования ключа
Использование WebAuthn
localForage поддерживает Blob и ArrayBuffer, но при шифровании чаще используется единый формат:
Важно учитывать:
Шифрование неизбежно влияет на скорость:
Типовые проблемы:
Оптимизации:
При изменении алгоритма или структуры данных требуется поддержка миграций.
Добавление поля version позволяет различать форматы:
if (payload.version === 1) {
return decryptV1(payload);
}
if (payload.version === 2) {
return decryptV2(payload);
}
Это предотвращает потерю данных при обновлении приложения.
Часто встречающиеся проблемы:
Особенно критична ошибка повторного IV: она полностью снижает стойкость AES-GCM.
localForage часто используется как слой кэша. При добавлении шифрования возникает компромисс:
Рациональная стратегия:
Даже при корректной реализации остаются фундаментальные ограничения:
Клиентское шифрование следует рассматривать как дополнительный слой защиты, а не как полноценную систему безопасности.
Сочетание подходов:
Такая модель позволяет строить устойчивые клиентские приложения, в которых локальное хранилище не превращается в источник утечки данных даже при доступе к DevTools.