Access keys from browser store using webcrypto api
Categories:
Securely Storing and Retrieving Cryptographic Keys in the Browser with Web Crypto API

Learn how to leverage the Web Crypto API and IndexedDB to securely store and retrieve cryptographic keys directly within the browser, enhancing web application security and user experience.
Modern web applications often require cryptographic operations, such as encryption, decryption, signing, and verification. While performing these operations, managing cryptographic keys securely is paramount. Storing keys on a remote server introduces latency and potential attack vectors. The Web Crypto API, combined with browser-side storage mechanisms like IndexedDB, offers a robust solution for managing keys directly within the user's browser. This approach enhances performance, reduces reliance on server-side key management for certain operations, and improves user privacy by keeping sensitive data client-side.
Understanding the Web Crypto API for Key Management
The Web Crypto API provides a set of JavaScript interfaces for performing cryptographic operations. It's designed to be secure and efficient, operating within the browser's security sandbox. For key management, the API offers methods to generate, import, export, and store cryptographic keys. The crypto.subtle
interface is the core component for these operations. When storing keys, it's crucial to understand that the Web Crypto API itself doesn't provide persistent storage; it relies on other browser storage mechanisms, most commonly IndexedDB, to save keys across sessions.
flowchart TD A[Web Application] --> B{Generate/Import Key?} B -->|Generate| C[crypto.subtle.generateKey()] B -->|Import| D[crypto.subtle.importKey()] C --> E[Key Object] D --> E E --> F{Store Key?} F -->|Yes| G[IndexedDB] F -->|No| H[Key in Memory Only] G --> I[Persistent Key Storage] I --> J{Retrieve Key?} J -->|Yes| G G --> E E --> K[Perform Crypto Operations] K --> L[Result]
Flowchart of Web Crypto API Key Generation, Import, and Storage
Integrating with IndexedDB for Persistent Key Storage
IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs. It's asynchronous, which means it won't block the UI thread, making it ideal for storing cryptographic keys. The Web Crypto API has built-in support for storing CryptoKey
objects directly into IndexedDB. This is achieved by specifying extractable: false
during key generation or import, which marks the key as non-exportable, and then using indexedDB.open()
and objectStore.add()
or objectStore.put()
to save the key. Retrieving the key involves opening the database and object store, then using objectStore.get()
.
extractable: false
when generating or importing keys that you intend to store persistently and not export. This prevents the raw key material from being easily extracted from the browser, significantly enhancing security.async function generateAndStoreKey() {
const key = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true, // extractable
["encrypt", "decrypt"]
);
const dbName = "CryptoKeyStore";
const storeName = "keys";
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore(storeName);
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(storeName, "readwrite");
const store = transaction.objectStore(storeName);
const putRequest = store.put(key, "myEncryptionKey");
putRequest.onsuccess = () => {
console.log("Key stored successfully!");
};
putRequest.onerror = (e) => {
console.error("Error storing key:", e);
};
};
request.onerror = (e) => {
console.error("Error opening IndexedDB:", e);
};
}
async function retrieveKey() {
const dbName = "CryptoKeyStore";
const storeName = "keys";
const request = indexedDB.open(dbName, 1);
return new Promise((resolve, reject) => {
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(storeName, "readonly");
const store = transaction.objectStore(storeName);
const getRequest = store.get("myEncryptionKey");
getRequest.onsuccess = () => {
console.log("Key retrieved successfully!");
resolve(getRequest.result);
};
getRequest.onerror = (e) => {
console.error("Error retrieving key:", e);
reject(e);
};
};
request.onerror = (e) => {
console.error("Error opening IndexedDB:", e);
reject(e);
};
});
}
generateAndStoreKey();
// Later, in another session or page load:
// retrieveKey().then(key => console.log('Retrieved key:', key));
Security Considerations and Best Practices
While storing keys client-side offers benefits, it also introduces specific security considerations. Keys stored in IndexedDB are protected by the browser's same-origin policy, meaning only scripts from the same origin can access them. However, a successful Cross-Site Scripting (XSS) attack could potentially compromise these keys. Therefore, robust XSS prevention is critical. Additionally, consider the lifecycle of the keys: when should they be generated, when should they be deleted, and how should they be revoked? For highly sensitive operations, consider wrapping the client-side key with a master key derived from a user password or a server-provided key, adding an extra layer of protection.
CryptoKey
objects or encrypted data. Always assume that a compromised browser environment could expose client-side stored data.1. Define Key Parameters
Determine the cryptographic algorithm (e.g., AES-GCM, RSA-OAEP), key length, and usage (e.g., encrypt, decrypt, sign) required for your application. Decide if the key needs to be extractable (generally false
for stored keys).
2. Generate or Import Key
Use crypto.subtle.generateKey()
to create a new key or crypto.subtle.importKey()
to import an existing key from raw material (e.g., a password-derived key).
3. Open IndexedDB
Initiate an IndexedDB connection using indexedDB.open()
. Handle the onupgradeneeded
event to create your object store if it doesn't exist.
4. Store the CryptoKey
Within a transaction, access the object store and use objectStore.put(key, 'keyIdentifier')
to save the CryptoKey
object. Use a meaningful identifier to retrieve it later.
5. Retrieve the CryptoKey
To use the key, open IndexedDB again, start a read-only transaction, and use objectStore.get('keyIdentifier')
to fetch the CryptoKey
object.
6. Perform Cryptographic Operations
Once retrieved, use the CryptoKey
object with crypto.subtle.encrypt()
, decrypt()
, sign()
, or verify()
as needed.