Хранение криптографических ключей в Web Crypto API тесно связано с
особенностями объекта CryptoKey и механизмом сериализации,
который используется в браузере. В отличие от обычных
JavaScript-объектов, ключи не могут быть произвольно преобразованы в
JSON или сохранены через localStorage без потери структуры
и безопасности. Именно IndexedDB становится стандартным способом
долговременного хранения ключевого материала на стороне клиента.
CryptoKey представляет собой непрозрачную ссылку на
ключ, управляемый браузером. Он не содержит «сырых» байтов ключа в
доступном виде и не подлежит прямой сериализации через
JSON.stringify.
Ключевые свойства CryptoKey:
type: public, private или
secretextractable: определяет возможность экспорта ключаalgorithm: описание криптографического алгоритмаusages: допустимые операции (например,
encrypt, decrypt, sign,
verify)Важно учитывать, что даже при наличии объекта в памяти его содержимое
может быть недоступно для извлечения, если extractable
установлено в false.
IndexedDB использует structured clone algorithm, который поддерживает
хранение CryptoKey без необходимости ручной сериализации.
Это делает его единственным штатным механизмом браузера для
долговременного хранения ключей Web Crypto API.
Поддерживаемые сущности:
CryptoKeyArrayBufferTypedArrayТаким образом, ключ может быть записан напрямую в хранилище IndexedDB и восстановлен без преобразований.
Пример генерации симметричного ключа AES и его сохранения:
const dbName = "crypto-db";
const storeName = "keys";
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, 1);
request.onupgradenee ded = () => {
const db = request.result;
db.createObjectStore(storeName);
};
request.onsucc ess = () => resolve(request.result);
request.oner ror = () => reject(request.error);
});
}
async function saveKey(name, key) {
const db = await openDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readwrite");
const store = tx.objectStore(storeName);
store.put(key, name);
tx.oncompl ete = () => resolve(true);
tx.oner ror = () => reject(tx.error);
});
}
async function generateAndStoreKey() {
const key = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
await saveKey("aes-key-1", key);
}
В этом примере ключ сохраняется напрямую как значение IndexedDB без дополнительной сериализации.
Извлечение ключа происходит так же, как и чтение любого объекта:
async function getKey(name) {
const db = await openDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readonly");
const store = tx.objectStore(storeName);
const request = store.get(name);
request.onsucc ess = () => resolve(request.result);
request.oner ror = () => reject(request.error);
});
}
async function useKey() {
const key = await getKey("aes-key-1");
const data = new TextEncoder().encode("secret message");
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv
},
key,
data
);
return encrypted;
}
Ключ восстанавливается в виде полноценного CryptoKey,
готового к использованию в операциях Web Crypto API.
Свойство extractable определяет возможность экспорта
ключа через crypto.subtle.exportKey.
extractable: true — ключ можно экспортировать
(например, в JWK, PKCS#8)extractable: false — ключ остаётся исключительно внутри
Web CryptoПри хранении в IndexedDB это свойство сохраняется, но не влияет на сам факт сохранения. Однако оно критично для безопасности: даже при наличии доступа к IndexedDB экспорт невозможен, если ключ не extractable.
Типичная стратегия:
extractable: falseextractable: trueextractable: falseГенерация RSA-пары:
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-PSS",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["sign", "verify"]
);
await saveKey("rsa-private", keyPair.privateKey);
await saveKey("rsa-public", keyPair.publicKey);
При необходимости можно хранить ключи раздельно, используя разные записи IndexedDB.
При работе с долговременными приложениями возникает необходимость обновления алгоритмов и ключей. IndexedDB не накладывает ограничений на структуру хранения, поэтому версионирование реализуется на уровне логики:
key-v1,
key-v2)CryptoKeyПример структуры:
keys_v1keys_v2{ key, version, createdAt }Часто требуется хранить не только сам CryptoKey, но и
сопутствующую информацию:
const record = {
key,
algorithm: "AES-GCM",
createdAt: Date.now(),
usages: ["encrypt", "decrypt"]
};
IndexedDB позволяет хранить такие структуры без ограничений, что делает возможным построение полноценного хранилища ключевого менеджера на клиенте.
При необходимости переноса ключа между устройствами используется экспорт:
const raw = await crypto.subtle.exportKey("jwk", key);
Импорт:
const importedKey = await crypto.subtle.importKey(
"jwk",
raw,
{ name: "AES-GCM" },
true,
["encrypt", "decrypt"]
);
После импорта ключ снова может быть сохранён в IndexedDB как обычный
CryptoKey.
IndexedDB изолирован по origin, что означает:
Однако существуют важные ограничения:
Поэтому критические системы часто добавляют дополнительное шифрование данных перед сохранением.
CryptoKey через JSON — приводит к
потере данныхlocalStorage вместо IndexedDB —
невозможность хранения объектовextractable: falseРабота с IndexedDB и Web Crypto полностью асинхронна. Это влияет на архитектуру:
async/awaitВ результате часто используется инициализация уровня приложения:
let cachedKey;
async function init() {
cachedKey = await getKey("aes-key-1");
}
IndexedDB требует использования транзакций для каждой операции:
readonly для чтения ключейreadwrite для записи и обновленияКаждая транзакция атомарна, что гарантирует целостность ключевого хранилища при конкурентном доступе.
Для сложных приложений часто выделяются отдельные objectStore:
crypto-keys — для CryptoKeykey-metadata — для описанийkey-index — для быстрых поисковТакое разделение снижает нагрузку и упрощает миграции структуры данных.
Несмотря на поддержку хранения CryptoKey, существуют
ограничения:
Эти ограничения делают IndexedDB удобным, но не абсолютным решением для долговременного хранения криптографических материалов.