Copied to clipboard
Conseal logo Conseal logo CONSEAL
v0.3.3  ·  TypeScript  ·  Web Crypto API  ·  Zero dependencies

Browser-side zero-knowledge
cryptography.

AES-256-GCM, ECDH, ECDSA, PBKDF2, and BIP-39 mnemonic recovery — all built on the native Web Crypto API. No native addons. No heavy dependencies. Works in any modern browser or Node 18+.

Open-source  ·  Auditable  ·  No server required  ·  ESM only

Everything your app needs
to encrypt client-side. Nothing else.

🔒
Zero-knowledge by design

Keys never leave the browser. Plaintext never touches your server. The architecture enforces privacy — it is not a policy you can accidentally break.

⚙️
Web Crypto native

Every cipher is a thin wrapper around SubtleCrypto. Hardware-accelerated, zero-dependency, same API in browser and Node 18+.

🔑
Key management built in

Generate, wrap, store, and recover keys. PBKDF2 passphrase wrapping, IndexedDB storage, and BIP-39 mnemonic recovery form a complete lifecycle.

🧾
TypeScript first

Full typings included. Every function signature is precise — CryptoKey, Uint8Array, SealedEnvelope. No implicit any.

📦
Minimal footprint

One runtime dependency: @scure/bip39 for mnemonic wordlists. Everything else is the platform. No OpenSSL, no polyfills, no wasm blobs.

🔌
Composable, not prescriptive

Each module is independent and tree-shakeable. Use only what you need. Bring your own storage, your own transport, your own UI.

Encrypt in three calls.

Initialise once per device, then seal and unseal data anywhere in your app.

1
Initialise (once per device)
import * as conseal from 'conseal'
 
// Generates + persists an AEK in IndexedDB.
// No-op on subsequent calls — idempotent.
const { aek } = await conseal.init()
2
Seal & unseal
import * as conseal from 'conseal'
 
// AES-256-GCM. Returns Uint8Array (IV + ciphertext).
const ct = await conseal.seal(plaintext, aek)
 
// Decrypts. Throws if tampered.
const pt = await conseal.unseal(ct, aek)
3
Share without a shared key
import * as conseal from 'conseal'
 
// Passcode-protected envelope (PBKDF2 + AES-GCM).
const env = await conseal.sealEnvelope(data, 'passphrase')
 
// Recipient decrypts with the same passphrase.
const plain = await conseal.unsealEnvelope(env, 'passphrase')

Everything exported from conseal

All functions are async, return typed values, and throw on authentication failure.

core
FunctionDescription
seal(data, key)Encrypt a Uint8Array with an AES-GCM key. Returns IV + ciphertext.
unseal(ct, key)Decrypt. Throws DOMException if the ciphertext has been tampered with.
generateAesKey()Generate a fresh extractable AES-256-GCM CryptoKey.
importAesKey(raw)Import 32 raw bytes as an AES-GCM key.
FunctionDescription
wrapKey(passphrase, key, secretKey?)Derive a wrapping key from a passphrase (PBKDF2 + 600,000 iterations) and AES-KW wrap the AES key. Optional 128-bit Secret Key hardens the KDF input.
unwrapKey(passphrase, wrapped, salt, secretKey?)Reverse of wrapKey. Returns extractable: false. Throws on wrong passphrase or Secret Key.
rekey(oldPass, newPass, wrapped, salt, secretKey?)Change the passphrase without re-encrypting content. Secret Key stays the same.
rekeySecretKey(pass, oldSK, newSK, wrapped, salt)Rotate the Secret Key while keeping the passphrase. Use only on compromise — requires re-enrolling all devices.
generateSecretKey()Generate a random 128-bit Secret Key. Call once at setup; store device-side and keep an offline copy.
combinePassphraseAndSecretKey(pass, sk)SHA-256 hash of passphrase + Secret Key into a single opaque PBKDF2 input. Used internally by wrap/unwrap/rekey.
FunctionDescription
generateECDHKeyPair()Generate an ECDH P-256 key pair for message encryption.
sealMessage(msg, recipientPub)Encrypt a message for a recipient's public key. Ephemeral ECDH + AES-GCM.
unsealMessage(sealed, privateKey)Decrypt using the recipient's private key.
FunctionDescription
generateECDSAKeyPair()Generate an ECDSA P-256 key pair for signing.
sign(data, privateKey)Sign arbitrary bytes. Returns a DER-encoded signature.
verify(data, sig, publicKey)Verify a signature. Returns boolean.
FunctionDescription
sealEnvelope(data, passphrase)Encrypt data under a passphrase. Derives key via PBKDF2, encrypts with AES-GCM. Returns a SealedEnvelope.
unsealEnvelope(env, passphrase)Decrypt a SealedEnvelope. Throws on wrong passphrase.
encodeEnvelope(env)Serialise a SealedEnvelope to a URL-safe Base64 string for transport.
decodeEnvelope(str)Deserialise from a URL-safe Base64 string back to a SealedEnvelope.
FunctionModuleDescription
init(wrapped, salt, pass, secretKey?)consealUnwrap the AEK with passphrase (+ optional Secret Key) and persist it to IndexedDB under AEK_KEY_ID.
exportPublicKeyAsJwk(key)consealSerialise a public CryptoKey to a JWK object for distribution.
importPublicKeyFromJwk(jwk)consealRestore a CryptoKey from a JWK object.
generateMnemonic(aek)consealDerive a BIP-39 mnemonic from an AES key for human-readable backup.
recoverWithMnemonic(phrase)consealReconstruct an AES key from a BIP-39 mnemonic and persist it.
digest(data)consealSHA-256 hash. Returns Uint8Array.
toBase64 / fromBase64consealStandard Base64 encode / decode.
toBase64Url / fromBase64UrlconsealURL-safe Base64 (no +/=) encode / decode.
saveCryptoKey / loadCryptoKey / deleteCryptoKeyconsealRead/write/delete a CryptoKey from IndexedDB by string id. Used internally by init().
Conseal logo Conseal logo

AEK, envelopes,
and the key lifecycle.

Account Encryption Key (AEK)

The root symmetric key for a device. Generated by init(), stored in IndexedDB as a non-exportable CryptoKey. Can be re-exported via mnemonic for cross-device recovery.

Envelope

A self-contained encrypted parcel with embedded salt and IV. Designed for passcode-based sharing where no pre-shared key exists — the passphrase is the only credential.

ECDH vs Envelope

sealMessage encrypts to a known recipient's public key — ideal for registered users. sealEnvelope encrypts to a passphrase — ideal for anonymous or one-time recipients.

// ── Full key lifecycle example ───────
 
import * as conseal from 'conseal'
 
// 1. Setup — generate AEK + 128-bit Secret Key
const aek = await crypto.subtle.generateKey(...)
const sk = conseal.generateSecretKey()
 
// 2. Wrap for server storage
const { wrappedKey, salt } = await conseal.wrapKey('password', aek, sk)
 
// 3. New device — unwrap into IndexedDB
await conseal.init(wrappedKey, salt, 'password', sk)
 
// 4. Change password (Secret Key unchanged)
const next = await conseal.rekey('password', 'new', wrappedKey, salt, sk)
 
// 5. Recover on new device via mnemonic
await conseal.recoverWithMnemonic(words)

See every API run
in your browser.

The interactive demo covers all seven modules. No server, no sign-up — just open your DevTools and watch the encryption happen.

Acronyms explained.

Cryptography is dense with abbreviations. Here is what every acronym in conseal means, how it is used, and why it was chosen.

AES-256-GCM symmetric

Advanced Encryption Standard · 256-bit key · Galois/Counter Mode

The symmetric cipher used for all data encryption in conseal. AES operates on 128-bit blocks; a 256-bit key makes exhaustive-search attacks computationally infeasible. GCM is an authenticated mode that simultaneously encrypts and produces an integrity tag — any tampering causes decryption to fail with an explicit error rather than silently returning corrupt data.

Used by: seal, unseal, sealMessage, sealEnvelope

PBKDF2 key derivation

Password-Based Key Derivation Function 2 (RFC 8018)

Stretches a human passphrase into a fixed-length cryptographic key by running HMAC-SHA-256 many times in sequence. conseal uses 600,000 iterations — deliberately slow so that an attacker who captures a wrapped key must spend enormous computation to guess the passphrase. A random salt ensures identical passphrases produce different keys.

Used by: wrapKey, unwrapKey, sealEnvelope

ECDH P-256 asymmetric

Elliptic Curve Diffie-Hellman · NIST P-256 curve

A key-agreement protocol that lets two parties independently derive the same shared secret using only their public keys — no prior shared secret required. conseal generates an ephemeral key pair per message, derives a one-time AES key from it, then discards the ephemeral private key, providing forward secrecy: compromising a long-term key does not expose past messages.

Used by: sealMessage, unsealMessage

ECDSA P-256 asymmetric

Elliptic Curve Digital Signature Algorithm · NIST P-256 curve

A digital signature scheme that proves data was produced by the holder of a specific private key — without revealing that key. Anyone with the corresponding public key can verify the signature. P-256 gives strong security in a compact 64-byte signature and is natively supported by the Web Crypto API in all modern browsers.

Used by: sign, verify

BIP-39 key recovery

Bitcoin Improvement Proposal 39

A standard that encodes binary entropy as a sequence of common English words drawn from a fixed 2,048-word list. The resulting phrase is far easier to write down and transcribe accurately than a raw hex key. conseal uses 24-word phrases (256 bits of entropy + 8-bit checksum) to let users back up and restore their AEK across devices.

Used by: generateMnemonic, recoverWithMnemonic

JWK serialisation

JSON Web Key (RFC 7517)

A standardised JSON representation for cryptographic keys. It encodes the key type, algorithm parameters, and key material in a portable, human-readable object. conseal uses JWK to distribute ECDH and ECDSA public keys so that any Web Crypto-capable environment can import them without custom serialisation logic.

Used by: exportPublicKeyAsJwk, importPublicKeyFromJwk

AEK core

Account Encryption Key

The root AES-256-GCM key for a device, generated by init() and stored in IndexedDB as a non-extractable CryptoKey. All application data is encrypted with the AEK. It never leaves the device in plaintext — it is always transported wrapped under a PBKDF2-derived key, so the server sees only ciphertext it cannot read.

Used by: init, wrapKey, generateMnemonic

IV / Nonce randomness

Initialisation Vector · Number Used Once

A random byte sequence prepended to every ciphertext. Encrypting the same plaintext twice with the same key but different IVs produces completely different ciphertexts, preventing pattern analysis. In GCM, the IV must never be reused under the same key; conseal generates a fresh cryptographically secure random IV per operation using crypto.getRandomValues.

Format: 12-byte random prefix embedded in every seal output; stripped automatically on decrypt.

ESM module format

ECMAScript Modules

The native JavaScript module system, using import / export syntax. conseal is ESM-only, which enables tree-shaking: bundlers can statically determine which functions are actually used and drop everything else, keeping your production bundle as small as possible.

Requires: Node 18+, a modern bundler (Vite, esbuild, webpack 5), or a browser supporting native ESM with type="module".