@lacspace/crypto 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/index.cjs +81 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -3
- package/dist/index.d.ts +53 -3
- package/dist/index.js +79 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,27 @@ randomBytes(16); // CSPRNG bytes
|
|
|
93
93
|
| [`@lacspace/headers`](https://www.npmjs.com/package/@lacspace/headers) | Secure headers / CSP |
|
|
94
94
|
| [`@lacspace/redact`](https://www.npmjs.com/package/@lacspace/redact) | Log redaction |
|
|
95
95
|
|
|
96
|
+
## New in 1.1 — AAD, HKDF & key rotation
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { encrypt, decrypt, hkdf, Keyring } from "@lacspace/crypto";
|
|
100
|
+
|
|
101
|
+
// Bind ciphertext to a context so it can't be relocated to another row
|
|
102
|
+
const blob = await encrypt(secret, key, { aad: `user:${id}` });
|
|
103
|
+
await decrypt(blob, key, { aad: `user:${id}` }); // must match
|
|
104
|
+
|
|
105
|
+
// Derive many purpose-bound sub-keys from one master key
|
|
106
|
+
const encKey = await hkdf(master, { info: "field-encryption", length: 32 });
|
|
107
|
+
|
|
108
|
+
// Zero-downtime key rotation — new writes use the primary, old blobs still decrypt
|
|
109
|
+
const ring = new Keyring([{ id: "2025", key: oldKey }, { id: "2026", key: newKey }]);
|
|
110
|
+
const fresh = await ring.encrypt("secret"); // v2:2026:…
|
|
111
|
+
const text = await ring.decrypt(oldBlob); // finds the key by id
|
|
112
|
+
const migrated = await ring.reEncrypt(oldBlob); // re-key to primary
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Also `decryptBytes()` for binary-safe payloads (files, protobufs).
|
|
116
|
+
|
|
96
117
|
## Licensing
|
|
97
118
|
|
|
98
119
|
This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
|
package/dist/index.cjs
CHANGED
|
@@ -110,28 +110,35 @@ async function importAesKey(key) {
|
|
|
110
110
|
"decrypt"
|
|
111
111
|
]);
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
function gcmParams(iv, aad) {
|
|
114
|
+
const p = { name: "AES-GCM", iv };
|
|
115
|
+
if (aad !== void 0) p.additionalData = toBytes(aad);
|
|
116
|
+
return p;
|
|
117
|
+
}
|
|
118
|
+
async function encrypt(plaintext, key, opts = {}) {
|
|
114
119
|
const cryptoKey = await importAesKey(key);
|
|
115
120
|
const iv = randomBytes(12);
|
|
116
121
|
const ct = await getCrypto().subtle.encrypt(
|
|
117
|
-
|
|
122
|
+
gcmParams(iv, opts.aad),
|
|
118
123
|
cryptoKey,
|
|
119
124
|
toBytes(plaintext)
|
|
120
125
|
);
|
|
121
126
|
return `${AES_PREFIX}:${toBase64url(iv)}:${toBase64url(new Uint8Array(ct))}`;
|
|
122
127
|
}
|
|
123
|
-
async function
|
|
128
|
+
async function decryptToBytes(payload, key, opts) {
|
|
124
129
|
const parts = payload.split(":");
|
|
125
130
|
if (parts.length !== 3 || parts[0] !== AES_PREFIX) throw new Error("invalid ciphertext format");
|
|
126
131
|
const cryptoKey = await importAesKey(key);
|
|
127
132
|
const iv = fromBase64url(parts[1]);
|
|
128
133
|
const ct = fromBase64url(parts[2]);
|
|
129
|
-
const pt = await getCrypto().subtle.decrypt(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
);
|
|
134
|
-
|
|
134
|
+
const pt = await getCrypto().subtle.decrypt(gcmParams(iv, opts.aad), cryptoKey, ct);
|
|
135
|
+
return new Uint8Array(pt);
|
|
136
|
+
}
|
|
137
|
+
async function decrypt(payload, key, opts = {}) {
|
|
138
|
+
return dec.decode(await decryptToBytes(payload, key, opts));
|
|
139
|
+
}
|
|
140
|
+
async function decryptBytes(payload, key, opts = {}) {
|
|
141
|
+
return decryptToBytes(payload, key, opts);
|
|
135
142
|
}
|
|
136
143
|
async function encryptWithPassword(plaintext, password, opts = {}) {
|
|
137
144
|
const iterations = opts.iterations ?? DEFAULT_PW_ITERATIONS;
|
|
@@ -149,9 +156,73 @@ async function decryptWithPassword(payload, password) {
|
|
|
149
156
|
const key = await deriveBits(password, salt, { iterations, length: 32 });
|
|
150
157
|
return decrypt(`${AES_PREFIX}:${parts[3]}:${parts[4]}`, key);
|
|
151
158
|
}
|
|
159
|
+
async function hkdf(keyMaterial, opts = {}) {
|
|
160
|
+
const c = getCrypto();
|
|
161
|
+
const baseKey = await c.subtle.importKey("raw", toBytes(keyMaterial), "HKDF", false, [
|
|
162
|
+
"deriveBits"
|
|
163
|
+
]);
|
|
164
|
+
const bits = await c.subtle.deriveBits(
|
|
165
|
+
{
|
|
166
|
+
name: "HKDF",
|
|
167
|
+
hash: opts.hash ?? "SHA-256",
|
|
168
|
+
salt: opts.salt ?? new Uint8Array(0),
|
|
169
|
+
info: toBytes(opts.info ?? "")
|
|
170
|
+
},
|
|
171
|
+
baseKey,
|
|
172
|
+
(opts.length ?? 32) * 8
|
|
173
|
+
);
|
|
174
|
+
return new Uint8Array(bits);
|
|
175
|
+
}
|
|
176
|
+
var KEYRING_PREFIX = "v2";
|
|
177
|
+
var Keyring = class {
|
|
178
|
+
constructor(entries, primaryId) {
|
|
179
|
+
this.keys = /* @__PURE__ */ new Map();
|
|
180
|
+
if (!entries.length) throw new Error("Keyring needs at least one key");
|
|
181
|
+
for (const e of entries) {
|
|
182
|
+
if (e.id.includes(":")) throw new Error(`key id "${e.id}" must not contain ":"`);
|
|
183
|
+
this.keys.set(e.id, e.key);
|
|
184
|
+
}
|
|
185
|
+
this.primaryId = primaryId ?? entries[entries.length - 1].id;
|
|
186
|
+
if (!this.keys.has(this.primaryId)) throw new Error(`primary key "${this.primaryId}" is not in the keyring`);
|
|
187
|
+
}
|
|
188
|
+
/** Encrypt under the primary key. */
|
|
189
|
+
async encrypt(plaintext, opts = {}) {
|
|
190
|
+
const inner = await encrypt(plaintext, this.keys.get(this.primaryId), opts);
|
|
191
|
+
const [, iv, ct] = inner.split(":");
|
|
192
|
+
return `${KEYRING_PREFIX}:${this.primaryId}:${iv}:${ct}`;
|
|
193
|
+
}
|
|
194
|
+
resolve(payload) {
|
|
195
|
+
const parts = payload.split(":");
|
|
196
|
+
if (parts[0] === KEYRING_PREFIX) {
|
|
197
|
+
const kid = parts[1];
|
|
198
|
+
const key = this.keys.get(kid);
|
|
199
|
+
if (!key) throw new Error(`unknown key id "${kid}"`);
|
|
200
|
+
return { key, inner: `${AES_PREFIX}:${parts[2]}:${parts[3]}` };
|
|
201
|
+
}
|
|
202
|
+
if (parts[0] === AES_PREFIX) return { key: this.keys.get(this.primaryId), inner: payload };
|
|
203
|
+
throw new Error("invalid ciphertext format");
|
|
204
|
+
}
|
|
205
|
+
async decrypt(payload, opts = {}) {
|
|
206
|
+
const { key, inner } = this.resolve(payload);
|
|
207
|
+
return decrypt(inner, key, opts);
|
|
208
|
+
}
|
|
209
|
+
async decryptBytes(payload, opts = {}) {
|
|
210
|
+
const { key, inner } = this.resolve(payload);
|
|
211
|
+
return decryptBytes(inner, key, opts);
|
|
212
|
+
}
|
|
213
|
+
/** Re-encrypt under the primary key if it isn't already. Returns the (possibly new) payload. */
|
|
214
|
+
async reEncrypt(payload, opts = {}) {
|
|
215
|
+
const parts = payload.split(":");
|
|
216
|
+
if (parts[0] === KEYRING_PREFIX && parts[1] === this.primaryId) return payload;
|
|
217
|
+
const pt = await this.decryptBytes(payload, opts);
|
|
218
|
+
return this.encrypt(pt, opts);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
152
221
|
|
|
222
|
+
exports.Keyring = Keyring;
|
|
153
223
|
exports.constantTimeEqual = constantTimeEqual;
|
|
154
224
|
exports.decrypt = decrypt;
|
|
225
|
+
exports.decryptBytes = decryptBytes;
|
|
155
226
|
exports.decryptWithPassword = decryptWithPassword;
|
|
156
227
|
exports.deriveBits = deriveBits;
|
|
157
228
|
exports.digest = digest;
|
|
@@ -160,6 +231,7 @@ exports.encryptWithPassword = encryptWithPassword;
|
|
|
160
231
|
exports.fromBase64url = fromBase64url;
|
|
161
232
|
exports.fromHex = fromHex;
|
|
162
233
|
exports.generateKey = generateKey;
|
|
234
|
+
exports.hkdf = hkdf;
|
|
163
235
|
exports.hmac = hmac;
|
|
164
236
|
exports.hmacVerify = hmacVerify;
|
|
165
237
|
exports.randomBytes = randomBytes;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/crypto\n * Safe, boring cryptography — authenticated AES-256-GCM, key derivation, hashing.\n *\n * A thin, correct layer over the Web Crypto API (no hand-rolled crypto), so the\n * same code runs on Node 18+, edge runtimes, browsers and React Native. Encrypt\n * database fields, S3 object payloads, cookies and tokens with confidence.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nfunction getCrypto(): Crypto {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto unavailable — @lacspace/crypto needs Node 18+, an edge runtime or a browser\");\n }\n return c;\n}\n\n/* ------------------------------ encoding ------------------------------ */\n\nexport function randomBytes(length: number): Uint8Array {\n const buf = new Uint8Array(length);\n getCrypto().getRandomValues(buf);\n return buf;\n}\n\nexport function toHex(bytes: Uint8Array): string {\n let out = \"\";\n for (const b of bytes) out += b.toString(16).padStart(2, \"0\");\n return out;\n}\n\nexport function fromHex(hex: string): Uint8Array {\n const clean = hex.length % 2 ? \"0\" + hex : hex;\n const out = new Uint8Array(clean.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nexport function toBase64url(bytes: Uint8Array): string {\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n const b64 = typeof btoa !== \"undefined\" ? btoa(bin) : Buffer.from(bytes).toString(\"base64\");\n return b64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nexport function fromBase64url(s: string): Uint8Array {\n const b64 = s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4);\n if (typeof atob !== \"undefined\") {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n return new Uint8Array(Buffer.from(b64, \"base64\"));\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nfunction toBytes(data: string | Uint8Array): Uint8Array {\n return typeof data === \"string\" ? enc.encode(data) : data;\n}\n\n/** Constant-time comparison of two byte arrays or strings. */\nexport function constantTimeEqual(a: Uint8Array | string, b: Uint8Array | string): boolean {\n const x = toBytes(a);\n const y = toBytes(b);\n if (x.length !== y.length) return false;\n let diff = 0;\n for (let i = 0; i < x.length; i++) diff |= x[i]! ^ y[i]!;\n return diff === 0;\n}\n\n/* ------------------------------ hashing ------------------------------ */\n\nexport type HashAlgorithm = \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\nexport async function digest(\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const buf = await getCrypto().subtle.digest(algorithm, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(buf);\n}\n\n/** SHA-256 hex digest. */\nexport async function sha256(data: string | Uint8Array): Promise<string> {\n return toHex(await digest(data, \"SHA-256\"));\n}\n\n/** HMAC signature (bytes). */\nexport async function hmac(\n key: string | Uint8Array,\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const c = getCrypto();\n const cryptoKey = await c.subtle.importKey(\n \"raw\",\n toBytes(key) as unknown as BufferSource,\n { name: \"HMAC\", hash: algorithm },\n false,\n [\"sign\"],\n );\n const sig = await c.subtle.sign(\"HMAC\", cryptoKey, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(sig);\n}\n\n/** Verify an HMAC in constant time. */\nexport async function hmacVerify(\n key: string | Uint8Array,\n data: string | Uint8Array,\n signature: Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<boolean> {\n return constantTimeEqual(await hmac(key, data, algorithm), signature);\n}\n\n/* ------------------------------ key derivation ------------------------------ */\n\nexport interface DeriveOptions {\n iterations?: number;\n hash?: HashAlgorithm;\n /** Derived key length in bytes. Default 32. */\n length?: number;\n}\n\n/** Derive raw key bytes from a password with PBKDF2. */\nexport async function deriveBits(\n password: string | Uint8Array,\n salt: Uint8Array,\n opts: DeriveOptions = {},\n): Promise<Uint8Array> {\n const c = getCrypto();\n const baseKey = await c.subtle.importKey(\n \"raw\",\n toBytes(password) as unknown as BufferSource,\n \"PBKDF2\",\n false,\n [\"deriveBits\"],\n );\n const bits = await c.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: salt as unknown as BufferSource,\n iterations: opts.iterations ?? 210000,\n hash: opts.hash ?? \"SHA-256\",\n },\n baseKey,\n (opts.length ?? 32) * 8,\n );\n return new Uint8Array(bits);\n}\n\n/* ------------------------------ AES-256-GCM ------------------------------ */\n\nconst AES_PREFIX = \"v1\";\nconst AES_PW_PREFIX = \"v1p\";\nconst DEFAULT_PW_ITERATIONS = 210000;\n\n/** Generate a random 256-bit AES key as a base64url string. */\nexport function generateKey(): string {\n return toBase64url(randomBytes(32));\n}\n\nasync function importAesKey(key: string | Uint8Array): Promise<CryptoKey> {\n const raw = typeof key === \"string\" ? fromBase64url(key) : key;\n if (raw.length !== 32) throw new Error(\"AES key must be 32 bytes (256-bit)\");\n return getCrypto().subtle.importKey(\"raw\", raw as unknown as BufferSource, { name: \"AES-GCM\" }, false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\n/**\n * Encrypt with AES-256-GCM using a 32-byte key (base64url or bytes).\n * Returns a compact self-describing string: `v1:<iv>:<ciphertext>`.\n */\nexport async function encrypt(plaintext: string | Uint8Array, key: string | Uint8Array): Promise<string> {\n const cryptoKey = await importAesKey(key);\n const iv = randomBytes(12);\n const ct = await getCrypto().subtle.encrypt(\n { name: \"AES-GCM\", iv: iv as unknown as BufferSource },\n cryptoKey,\n toBytes(plaintext) as unknown as BufferSource,\n );\n return `${AES_PREFIX}:${toBase64url(iv)}:${toBase64url(new Uint8Array(ct))}`;\n}\n\n/** Decrypt a string produced by {@link encrypt}. Returns the UTF-8 plaintext. */\nexport async function decrypt(payload: string, key: string | Uint8Array): Promise<string> {\n const parts = payload.split(\":\");\n if (parts.length !== 3 || parts[0] !== AES_PREFIX) throw new Error(\"invalid ciphertext format\");\n const cryptoKey = await importAesKey(key);\n const iv = fromBase64url(parts[1]!);\n const ct = fromBase64url(parts[2]!);\n const pt = await getCrypto().subtle.decrypt(\n { name: \"AES-GCM\", iv: iv as unknown as BufferSource },\n cryptoKey,\n ct as unknown as BufferSource,\n );\n return dec.decode(pt);\n}\n\n/**\n * Encrypt with a passphrase (PBKDF2-derived key + AES-256-GCM).\n * Returns `v1p:<iterations>:<salt>:<iv>:<ciphertext>` — self-contained.\n */\nexport async function encryptWithPassword(\n plaintext: string | Uint8Array,\n password: string,\n opts: { iterations?: number } = {},\n): Promise<string> {\n const iterations = opts.iterations ?? DEFAULT_PW_ITERATIONS;\n const salt = randomBytes(16);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n const inner = await encrypt(plaintext, key);\n const [, iv, ct] = inner.split(\":\");\n return `${AES_PW_PREFIX}:${iterations}:${toBase64url(salt)}:${iv}:${ct}`;\n}\n\n/** Decrypt a string produced by {@link encryptWithPassword}. */\nexport async function decryptWithPassword(payload: string, password: string): Promise<string> {\n const parts = payload.split(\":\");\n if (parts.length !== 5 || parts[0] !== AES_PW_PREFIX) throw new Error(\"invalid ciphertext format\");\n const iterations = parseInt(parts[1]!, 10);\n const salt = fromBase64url(parts[2]!);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n return decrypt(`${AES_PREFIX}:${parts[3]}:${parts[4]}`, key);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/crypto\n * Safe, boring cryptography — authenticated AES-256-GCM, key derivation, hashing.\n *\n * A thin, correct layer over the Web Crypto API (no hand-rolled crypto), so the\n * same code runs on Node 18+, edge runtimes, browsers and React Native. Encrypt\n * database fields, S3 object payloads, cookies and tokens with confidence.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nfunction getCrypto(): Crypto {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto unavailable — @lacspace/crypto needs Node 18+, an edge runtime or a browser\");\n }\n return c;\n}\n\n/* ------------------------------ encoding ------------------------------ */\n\nexport function randomBytes(length: number): Uint8Array {\n const buf = new Uint8Array(length);\n getCrypto().getRandomValues(buf);\n return buf;\n}\n\nexport function toHex(bytes: Uint8Array): string {\n let out = \"\";\n for (const b of bytes) out += b.toString(16).padStart(2, \"0\");\n return out;\n}\n\nexport function fromHex(hex: string): Uint8Array {\n const clean = hex.length % 2 ? \"0\" + hex : hex;\n const out = new Uint8Array(clean.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nexport function toBase64url(bytes: Uint8Array): string {\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n const b64 = typeof btoa !== \"undefined\" ? btoa(bin) : Buffer.from(bytes).toString(\"base64\");\n return b64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nexport function fromBase64url(s: string): Uint8Array {\n const b64 = s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4);\n if (typeof atob !== \"undefined\") {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n return new Uint8Array(Buffer.from(b64, \"base64\"));\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nfunction toBytes(data: string | Uint8Array): Uint8Array {\n return typeof data === \"string\" ? enc.encode(data) : data;\n}\n\n/** Constant-time comparison of two byte arrays or strings. */\nexport function constantTimeEqual(a: Uint8Array | string, b: Uint8Array | string): boolean {\n const x = toBytes(a);\n const y = toBytes(b);\n if (x.length !== y.length) return false;\n let diff = 0;\n for (let i = 0; i < x.length; i++) diff |= x[i]! ^ y[i]!;\n return diff === 0;\n}\n\n/* ------------------------------ hashing ------------------------------ */\n\nexport type HashAlgorithm = \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\nexport async function digest(\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const buf = await getCrypto().subtle.digest(algorithm, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(buf);\n}\n\n/** SHA-256 hex digest. */\nexport async function sha256(data: string | Uint8Array): Promise<string> {\n return toHex(await digest(data, \"SHA-256\"));\n}\n\n/** HMAC signature (bytes). */\nexport async function hmac(\n key: string | Uint8Array,\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const c = getCrypto();\n const cryptoKey = await c.subtle.importKey(\n \"raw\",\n toBytes(key) as unknown as BufferSource,\n { name: \"HMAC\", hash: algorithm },\n false,\n [\"sign\"],\n );\n const sig = await c.subtle.sign(\"HMAC\", cryptoKey, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(sig);\n}\n\n/** Verify an HMAC in constant time. */\nexport async function hmacVerify(\n key: string | Uint8Array,\n data: string | Uint8Array,\n signature: Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<boolean> {\n return constantTimeEqual(await hmac(key, data, algorithm), signature);\n}\n\n/* ------------------------------ key derivation ------------------------------ */\n\nexport interface DeriveOptions {\n iterations?: number;\n hash?: HashAlgorithm;\n /** Derived key length in bytes. Default 32. */\n length?: number;\n}\n\n/** Derive raw key bytes from a password with PBKDF2. */\nexport async function deriveBits(\n password: string | Uint8Array,\n salt: Uint8Array,\n opts: DeriveOptions = {},\n): Promise<Uint8Array> {\n const c = getCrypto();\n const baseKey = await c.subtle.importKey(\n \"raw\",\n toBytes(password) as unknown as BufferSource,\n \"PBKDF2\",\n false,\n [\"deriveBits\"],\n );\n const bits = await c.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: salt as unknown as BufferSource,\n iterations: opts.iterations ?? 210000,\n hash: opts.hash ?? \"SHA-256\",\n },\n baseKey,\n (opts.length ?? 32) * 8,\n );\n return new Uint8Array(bits);\n}\n\n/* ------------------------------ AES-256-GCM ------------------------------ */\n\nconst AES_PREFIX = \"v1\";\nconst AES_PW_PREFIX = \"v1p\";\nconst DEFAULT_PW_ITERATIONS = 210000;\n\n/** Generate a random 256-bit AES key as a base64url string. */\nexport function generateKey(): string {\n return toBase64url(randomBytes(32));\n}\n\nasync function importAesKey(key: string | Uint8Array): Promise<CryptoKey> {\n const raw = typeof key === \"string\" ? fromBase64url(key) : key;\n if (raw.length !== 32) throw new Error(\"AES key must be 32 bytes (256-bit)\");\n return getCrypto().subtle.importKey(\"raw\", raw as unknown as BufferSource, { name: \"AES-GCM\" }, false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\nexport interface AesOptions {\n /** Additional Authenticated Data — bound to the ciphertext (must match on decrypt). */\n aad?: string | Uint8Array;\n}\n\nfunction gcmParams(iv: Uint8Array, aad?: string | Uint8Array): AesGcmParams {\n const p: AesGcmParams = { name: \"AES-GCM\", iv: iv as unknown as BufferSource };\n if (aad !== undefined) p.additionalData = toBytes(aad) as unknown as BufferSource;\n return p;\n}\n\n/**\n * Encrypt with AES-256-GCM using a 32-byte key (base64url or bytes).\n * Returns a compact self-describing string: `v1:<iv>:<ciphertext>`.\n * Pass `opts.aad` to bind the ciphertext to a context (row id, tenant…).\n */\nexport async function encrypt(\n plaintext: string | Uint8Array,\n key: string | Uint8Array,\n opts: AesOptions = {},\n): Promise<string> {\n const cryptoKey = await importAesKey(key);\n const iv = randomBytes(12);\n const ct = await getCrypto().subtle.encrypt(\n gcmParams(iv, opts.aad),\n cryptoKey,\n toBytes(plaintext) as unknown as BufferSource,\n );\n return `${AES_PREFIX}:${toBase64url(iv)}:${toBase64url(new Uint8Array(ct))}`;\n}\n\nasync function decryptToBytes(payload: string, key: string | Uint8Array, opts: AesOptions): Promise<Uint8Array> {\n const parts = payload.split(\":\");\n if (parts.length !== 3 || parts[0] !== AES_PREFIX) throw new Error(\"invalid ciphertext format\");\n const cryptoKey = await importAesKey(key);\n const iv = fromBase64url(parts[1]!);\n const ct = fromBase64url(parts[2]!);\n const pt = await getCrypto().subtle.decrypt(gcmParams(iv, opts.aad), cryptoKey, ct as unknown as BufferSource);\n return new Uint8Array(pt);\n}\n\n/** Decrypt a string produced by {@link encrypt}. Returns the UTF-8 plaintext. */\nexport async function decrypt(payload: string, key: string | Uint8Array, opts: AesOptions = {}): Promise<string> {\n return dec.decode(await decryptToBytes(payload, key, opts));\n}\n\n/** Decrypt to raw bytes — binary-safe (files, protobufs, images). */\nexport async function decryptBytes(\n payload: string,\n key: string | Uint8Array,\n opts: AesOptions = {},\n): Promise<Uint8Array> {\n return decryptToBytes(payload, key, opts);\n}\n\n/**\n * Encrypt with a passphrase (PBKDF2-derived key + AES-256-GCM).\n * Returns `v1p:<iterations>:<salt>:<iv>:<ciphertext>` — self-contained.\n */\nexport async function encryptWithPassword(\n plaintext: string | Uint8Array,\n password: string,\n opts: { iterations?: number } = {},\n): Promise<string> {\n const iterations = opts.iterations ?? DEFAULT_PW_ITERATIONS;\n const salt = randomBytes(16);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n const inner = await encrypt(plaintext, key);\n const [, iv, ct] = inner.split(\":\");\n return `${AES_PW_PREFIX}:${iterations}:${toBase64url(salt)}:${iv}:${ct}`;\n}\n\n/** Decrypt a string produced by {@link encryptWithPassword}. */\nexport async function decryptWithPassword(payload: string, password: string): Promise<string> {\n const parts = payload.split(\":\");\n if (parts.length !== 5 || parts[0] !== AES_PW_PREFIX) throw new Error(\"invalid ciphertext format\");\n const iterations = parseInt(parts[1]!, 10);\n const salt = fromBase64url(parts[2]!);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n return decrypt(`${AES_PREFIX}:${parts[3]}:${parts[4]}`, key);\n}\n\n/* ------------------------------ HKDF ------------------------------ */\n\nexport interface HkdfOptions {\n /** Optional salt (recommended). */\n salt?: Uint8Array;\n /** Context/label so the same master key yields different sub-keys per purpose. */\n info?: string | Uint8Array;\n /** Output length in bytes. Default 32. */\n length?: number;\n hash?: HashAlgorithm;\n}\n\n/**\n * HKDF: derive one or many purpose-bound sub-keys from a single master key.\n * @example const encKey = await hkdf(master, { info: \"field-encryption\", length: 32 });\n */\nexport async function hkdf(keyMaterial: string | Uint8Array, opts: HkdfOptions = {}): Promise<Uint8Array> {\n const c = getCrypto();\n const baseKey = await c.subtle.importKey(\"raw\", toBytes(keyMaterial) as unknown as BufferSource, \"HKDF\", false, [\n \"deriveBits\",\n ]);\n const bits = await c.subtle.deriveBits(\n {\n name: \"HKDF\",\n hash: opts.hash ?? \"SHA-256\",\n salt: (opts.salt ?? new Uint8Array(0)) as unknown as BufferSource,\n info: toBytes(opts.info ?? \"\") as unknown as BufferSource,\n },\n baseKey,\n (opts.length ?? 32) * 8,\n );\n return new Uint8Array(bits);\n}\n\n/* ------------------------------ key rotation ------------------------------ */\n\nconst KEYRING_PREFIX = \"v2\";\n\nexport interface KeyringEntry {\n /** Stable key id (embedded in ciphertext; must not contain \":\"). */\n id: string;\n /** 32-byte AES key (base64url string or bytes). */\n key: string | Uint8Array;\n}\n\n/**\n * A set of versioned AES keys for zero-downtime rotation. New data is encrypted\n * under the primary key; old ciphertext is decrypted by the key its `id` names.\n * Envelope: `v2:<keyId>:<iv>:<ciphertext>`.\n *\n * @example\n * const ring = new Keyring([{ id: \"2025\", key: oldKey }, { id: \"2026\", key: newKey }]);\n * const blob = await ring.encrypt(\"secret\"); // uses \"2026\" (primary = last)\n * const text = await ring.decrypt(oldBlob); // finds the right key by id\n * const fresh = await ring.reEncrypt(oldBlob); // migrate to the primary key\n */\nexport class Keyring {\n private readonly keys = new Map<string, string | Uint8Array>();\n readonly primaryId: string;\n\n constructor(entries: KeyringEntry[], primaryId?: string) {\n if (!entries.length) throw new Error(\"Keyring needs at least one key\");\n for (const e of entries) {\n if (e.id.includes(\":\")) throw new Error(`key id \"${e.id}\" must not contain \":\"`);\n this.keys.set(e.id, e.key);\n }\n this.primaryId = primaryId ?? entries[entries.length - 1]!.id;\n if (!this.keys.has(this.primaryId)) throw new Error(`primary key \"${this.primaryId}\" is not in the keyring`);\n }\n\n /** Encrypt under the primary key. */\n async encrypt(plaintext: string | Uint8Array, opts: AesOptions = {}): Promise<string> {\n const inner = await encrypt(plaintext, this.keys.get(this.primaryId)!, opts);\n const [, iv, ct] = inner.split(\":\");\n return `${KEYRING_PREFIX}:${this.primaryId}:${iv}:${ct}`;\n }\n\n private resolve(payload: string): { key: string | Uint8Array; inner: string } {\n const parts = payload.split(\":\");\n if (parts[0] === KEYRING_PREFIX) {\n const kid = parts[1]!;\n const key = this.keys.get(kid);\n if (!key) throw new Error(`unknown key id \"${kid}\"`);\n return { key, inner: `${AES_PREFIX}:${parts[2]}:${parts[3]}` };\n }\n if (parts[0] === AES_PREFIX) return { key: this.keys.get(this.primaryId)!, inner: payload }; // legacy v1\n throw new Error(\"invalid ciphertext format\");\n }\n\n async decrypt(payload: string, opts: AesOptions = {}): Promise<string> {\n const { key, inner } = this.resolve(payload);\n return decrypt(inner, key, opts);\n }\n\n async decryptBytes(payload: string, opts: AesOptions = {}): Promise<Uint8Array> {\n const { key, inner } = this.resolve(payload);\n return decryptBytes(inner, key, opts);\n }\n\n /** Re-encrypt under the primary key if it isn't already. Returns the (possibly new) payload. */\n async reEncrypt(payload: string, opts: AesOptions = {}): Promise<string> {\n const parts = payload.split(\":\");\n if (parts[0] === KEYRING_PREFIX && parts[1] === this.primaryId) return payload;\n const pt = await this.decryptBytes(payload, opts);\n return this.encrypt(pt, opts);\n }\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -33,13 +33,20 @@ interface DeriveOptions {
|
|
|
33
33
|
declare function deriveBits(password: string | Uint8Array, salt: Uint8Array, opts?: DeriveOptions): Promise<Uint8Array>;
|
|
34
34
|
/** Generate a random 256-bit AES key as a base64url string. */
|
|
35
35
|
declare function generateKey(): string;
|
|
36
|
+
interface AesOptions {
|
|
37
|
+
/** Additional Authenticated Data — bound to the ciphertext (must match on decrypt). */
|
|
38
|
+
aad?: string | Uint8Array;
|
|
39
|
+
}
|
|
36
40
|
/**
|
|
37
41
|
* Encrypt with AES-256-GCM using a 32-byte key (base64url or bytes).
|
|
38
42
|
* Returns a compact self-describing string: `v1:<iv>:<ciphertext>`.
|
|
43
|
+
* Pass `opts.aad` to bind the ciphertext to a context (row id, tenant…).
|
|
39
44
|
*/
|
|
40
|
-
declare function encrypt(plaintext: string | Uint8Array, key: string | Uint8Array): Promise<string>;
|
|
45
|
+
declare function encrypt(plaintext: string | Uint8Array, key: string | Uint8Array, opts?: AesOptions): Promise<string>;
|
|
41
46
|
/** Decrypt a string produced by {@link encrypt}. Returns the UTF-8 plaintext. */
|
|
42
|
-
declare function decrypt(payload: string, key: string | Uint8Array): Promise<string>;
|
|
47
|
+
declare function decrypt(payload: string, key: string | Uint8Array, opts?: AesOptions): Promise<string>;
|
|
48
|
+
/** Decrypt to raw bytes — binary-safe (files, protobufs, images). */
|
|
49
|
+
declare function decryptBytes(payload: string, key: string | Uint8Array, opts?: AesOptions): Promise<Uint8Array>;
|
|
43
50
|
/**
|
|
44
51
|
* Encrypt with a passphrase (PBKDF2-derived key + AES-256-GCM).
|
|
45
52
|
* Returns `v1p:<iterations>:<salt>:<iv>:<ciphertext>` — self-contained.
|
|
@@ -49,5 +56,48 @@ declare function encryptWithPassword(plaintext: string | Uint8Array, password: s
|
|
|
49
56
|
}): Promise<string>;
|
|
50
57
|
/** Decrypt a string produced by {@link encryptWithPassword}. */
|
|
51
58
|
declare function decryptWithPassword(payload: string, password: string): Promise<string>;
|
|
59
|
+
interface HkdfOptions {
|
|
60
|
+
/** Optional salt (recommended). */
|
|
61
|
+
salt?: Uint8Array;
|
|
62
|
+
/** Context/label so the same master key yields different sub-keys per purpose. */
|
|
63
|
+
info?: string | Uint8Array;
|
|
64
|
+
/** Output length in bytes. Default 32. */
|
|
65
|
+
length?: number;
|
|
66
|
+
hash?: HashAlgorithm;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* HKDF: derive one or many purpose-bound sub-keys from a single master key.
|
|
70
|
+
* @example const encKey = await hkdf(master, { info: "field-encryption", length: 32 });
|
|
71
|
+
*/
|
|
72
|
+
declare function hkdf(keyMaterial: string | Uint8Array, opts?: HkdfOptions): Promise<Uint8Array>;
|
|
73
|
+
interface KeyringEntry {
|
|
74
|
+
/** Stable key id (embedded in ciphertext; must not contain ":"). */
|
|
75
|
+
id: string;
|
|
76
|
+
/** 32-byte AES key (base64url string or bytes). */
|
|
77
|
+
key: string | Uint8Array;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A set of versioned AES keys for zero-downtime rotation. New data is encrypted
|
|
81
|
+
* under the primary key; old ciphertext is decrypted by the key its `id` names.
|
|
82
|
+
* Envelope: `v2:<keyId>:<iv>:<ciphertext>`.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* const ring = new Keyring([{ id: "2025", key: oldKey }, { id: "2026", key: newKey }]);
|
|
86
|
+
* const blob = await ring.encrypt("secret"); // uses "2026" (primary = last)
|
|
87
|
+
* const text = await ring.decrypt(oldBlob); // finds the right key by id
|
|
88
|
+
* const fresh = await ring.reEncrypt(oldBlob); // migrate to the primary key
|
|
89
|
+
*/
|
|
90
|
+
declare class Keyring {
|
|
91
|
+
private readonly keys;
|
|
92
|
+
readonly primaryId: string;
|
|
93
|
+
constructor(entries: KeyringEntry[], primaryId?: string);
|
|
94
|
+
/** Encrypt under the primary key. */
|
|
95
|
+
encrypt(plaintext: string | Uint8Array, opts?: AesOptions): Promise<string>;
|
|
96
|
+
private resolve;
|
|
97
|
+
decrypt(payload: string, opts?: AesOptions): Promise<string>;
|
|
98
|
+
decryptBytes(payload: string, opts?: AesOptions): Promise<Uint8Array>;
|
|
99
|
+
/** Re-encrypt under the primary key if it isn't already. Returns the (possibly new) payload. */
|
|
100
|
+
reEncrypt(payload: string, opts?: AesOptions): Promise<string>;
|
|
101
|
+
}
|
|
52
102
|
|
|
53
|
-
export { type DeriveOptions, type HashAlgorithm, constantTimeEqual, decrypt, decryptWithPassword, deriveBits, digest, encrypt, encryptWithPassword, fromBase64url, fromHex, generateKey, hmac, hmacVerify, randomBytes, sha256, toBase64url, toHex };
|
|
103
|
+
export { type AesOptions, type DeriveOptions, type HashAlgorithm, type HkdfOptions, Keyring, type KeyringEntry, constantTimeEqual, decrypt, decryptBytes, decryptWithPassword, deriveBits, digest, encrypt, encryptWithPassword, fromBase64url, fromHex, generateKey, hkdf, hmac, hmacVerify, randomBytes, sha256, toBase64url, toHex };
|
package/dist/index.d.ts
CHANGED
|
@@ -33,13 +33,20 @@ interface DeriveOptions {
|
|
|
33
33
|
declare function deriveBits(password: string | Uint8Array, salt: Uint8Array, opts?: DeriveOptions): Promise<Uint8Array>;
|
|
34
34
|
/** Generate a random 256-bit AES key as a base64url string. */
|
|
35
35
|
declare function generateKey(): string;
|
|
36
|
+
interface AesOptions {
|
|
37
|
+
/** Additional Authenticated Data — bound to the ciphertext (must match on decrypt). */
|
|
38
|
+
aad?: string | Uint8Array;
|
|
39
|
+
}
|
|
36
40
|
/**
|
|
37
41
|
* Encrypt with AES-256-GCM using a 32-byte key (base64url or bytes).
|
|
38
42
|
* Returns a compact self-describing string: `v1:<iv>:<ciphertext>`.
|
|
43
|
+
* Pass `opts.aad` to bind the ciphertext to a context (row id, tenant…).
|
|
39
44
|
*/
|
|
40
|
-
declare function encrypt(plaintext: string | Uint8Array, key: string | Uint8Array): Promise<string>;
|
|
45
|
+
declare function encrypt(plaintext: string | Uint8Array, key: string | Uint8Array, opts?: AesOptions): Promise<string>;
|
|
41
46
|
/** Decrypt a string produced by {@link encrypt}. Returns the UTF-8 plaintext. */
|
|
42
|
-
declare function decrypt(payload: string, key: string | Uint8Array): Promise<string>;
|
|
47
|
+
declare function decrypt(payload: string, key: string | Uint8Array, opts?: AesOptions): Promise<string>;
|
|
48
|
+
/** Decrypt to raw bytes — binary-safe (files, protobufs, images). */
|
|
49
|
+
declare function decryptBytes(payload: string, key: string | Uint8Array, opts?: AesOptions): Promise<Uint8Array>;
|
|
43
50
|
/**
|
|
44
51
|
* Encrypt with a passphrase (PBKDF2-derived key + AES-256-GCM).
|
|
45
52
|
* Returns `v1p:<iterations>:<salt>:<iv>:<ciphertext>` — self-contained.
|
|
@@ -49,5 +56,48 @@ declare function encryptWithPassword(plaintext: string | Uint8Array, password: s
|
|
|
49
56
|
}): Promise<string>;
|
|
50
57
|
/** Decrypt a string produced by {@link encryptWithPassword}. */
|
|
51
58
|
declare function decryptWithPassword(payload: string, password: string): Promise<string>;
|
|
59
|
+
interface HkdfOptions {
|
|
60
|
+
/** Optional salt (recommended). */
|
|
61
|
+
salt?: Uint8Array;
|
|
62
|
+
/** Context/label so the same master key yields different sub-keys per purpose. */
|
|
63
|
+
info?: string | Uint8Array;
|
|
64
|
+
/** Output length in bytes. Default 32. */
|
|
65
|
+
length?: number;
|
|
66
|
+
hash?: HashAlgorithm;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* HKDF: derive one or many purpose-bound sub-keys from a single master key.
|
|
70
|
+
* @example const encKey = await hkdf(master, { info: "field-encryption", length: 32 });
|
|
71
|
+
*/
|
|
72
|
+
declare function hkdf(keyMaterial: string | Uint8Array, opts?: HkdfOptions): Promise<Uint8Array>;
|
|
73
|
+
interface KeyringEntry {
|
|
74
|
+
/** Stable key id (embedded in ciphertext; must not contain ":"). */
|
|
75
|
+
id: string;
|
|
76
|
+
/** 32-byte AES key (base64url string or bytes). */
|
|
77
|
+
key: string | Uint8Array;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A set of versioned AES keys for zero-downtime rotation. New data is encrypted
|
|
81
|
+
* under the primary key; old ciphertext is decrypted by the key its `id` names.
|
|
82
|
+
* Envelope: `v2:<keyId>:<iv>:<ciphertext>`.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* const ring = new Keyring([{ id: "2025", key: oldKey }, { id: "2026", key: newKey }]);
|
|
86
|
+
* const blob = await ring.encrypt("secret"); // uses "2026" (primary = last)
|
|
87
|
+
* const text = await ring.decrypt(oldBlob); // finds the right key by id
|
|
88
|
+
* const fresh = await ring.reEncrypt(oldBlob); // migrate to the primary key
|
|
89
|
+
*/
|
|
90
|
+
declare class Keyring {
|
|
91
|
+
private readonly keys;
|
|
92
|
+
readonly primaryId: string;
|
|
93
|
+
constructor(entries: KeyringEntry[], primaryId?: string);
|
|
94
|
+
/** Encrypt under the primary key. */
|
|
95
|
+
encrypt(plaintext: string | Uint8Array, opts?: AesOptions): Promise<string>;
|
|
96
|
+
private resolve;
|
|
97
|
+
decrypt(payload: string, opts?: AesOptions): Promise<string>;
|
|
98
|
+
decryptBytes(payload: string, opts?: AesOptions): Promise<Uint8Array>;
|
|
99
|
+
/** Re-encrypt under the primary key if it isn't already. Returns the (possibly new) payload. */
|
|
100
|
+
reEncrypt(payload: string, opts?: AesOptions): Promise<string>;
|
|
101
|
+
}
|
|
52
102
|
|
|
53
|
-
export { type DeriveOptions, type HashAlgorithm, constantTimeEqual, decrypt, decryptWithPassword, deriveBits, digest, encrypt, encryptWithPassword, fromBase64url, fromHex, generateKey, hmac, hmacVerify, randomBytes, sha256, toBase64url, toHex };
|
|
103
|
+
export { type AesOptions, type DeriveOptions, type HashAlgorithm, type HkdfOptions, Keyring, type KeyringEntry, constantTimeEqual, decrypt, decryptBytes, decryptWithPassword, deriveBits, digest, encrypt, encryptWithPassword, fromBase64url, fromHex, generateKey, hkdf, hmac, hmacVerify, randomBytes, sha256, toBase64url, toHex };
|
package/dist/index.js
CHANGED
|
@@ -108,28 +108,35 @@ async function importAesKey(key) {
|
|
|
108
108
|
"decrypt"
|
|
109
109
|
]);
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
function gcmParams(iv, aad) {
|
|
112
|
+
const p = { name: "AES-GCM", iv };
|
|
113
|
+
if (aad !== void 0) p.additionalData = toBytes(aad);
|
|
114
|
+
return p;
|
|
115
|
+
}
|
|
116
|
+
async function encrypt(plaintext, key, opts = {}) {
|
|
112
117
|
const cryptoKey = await importAesKey(key);
|
|
113
118
|
const iv = randomBytes(12);
|
|
114
119
|
const ct = await getCrypto().subtle.encrypt(
|
|
115
|
-
|
|
120
|
+
gcmParams(iv, opts.aad),
|
|
116
121
|
cryptoKey,
|
|
117
122
|
toBytes(plaintext)
|
|
118
123
|
);
|
|
119
124
|
return `${AES_PREFIX}:${toBase64url(iv)}:${toBase64url(new Uint8Array(ct))}`;
|
|
120
125
|
}
|
|
121
|
-
async function
|
|
126
|
+
async function decryptToBytes(payload, key, opts) {
|
|
122
127
|
const parts = payload.split(":");
|
|
123
128
|
if (parts.length !== 3 || parts[0] !== AES_PREFIX) throw new Error("invalid ciphertext format");
|
|
124
129
|
const cryptoKey = await importAesKey(key);
|
|
125
130
|
const iv = fromBase64url(parts[1]);
|
|
126
131
|
const ct = fromBase64url(parts[2]);
|
|
127
|
-
const pt = await getCrypto().subtle.decrypt(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
);
|
|
132
|
-
|
|
132
|
+
const pt = await getCrypto().subtle.decrypt(gcmParams(iv, opts.aad), cryptoKey, ct);
|
|
133
|
+
return new Uint8Array(pt);
|
|
134
|
+
}
|
|
135
|
+
async function decrypt(payload, key, opts = {}) {
|
|
136
|
+
return dec.decode(await decryptToBytes(payload, key, opts));
|
|
137
|
+
}
|
|
138
|
+
async function decryptBytes(payload, key, opts = {}) {
|
|
139
|
+
return decryptToBytes(payload, key, opts);
|
|
133
140
|
}
|
|
134
141
|
async function encryptWithPassword(plaintext, password, opts = {}) {
|
|
135
142
|
const iterations = opts.iterations ?? DEFAULT_PW_ITERATIONS;
|
|
@@ -147,7 +154,69 @@ async function decryptWithPassword(payload, password) {
|
|
|
147
154
|
const key = await deriveBits(password, salt, { iterations, length: 32 });
|
|
148
155
|
return decrypt(`${AES_PREFIX}:${parts[3]}:${parts[4]}`, key);
|
|
149
156
|
}
|
|
157
|
+
async function hkdf(keyMaterial, opts = {}) {
|
|
158
|
+
const c = getCrypto();
|
|
159
|
+
const baseKey = await c.subtle.importKey("raw", toBytes(keyMaterial), "HKDF", false, [
|
|
160
|
+
"deriveBits"
|
|
161
|
+
]);
|
|
162
|
+
const bits = await c.subtle.deriveBits(
|
|
163
|
+
{
|
|
164
|
+
name: "HKDF",
|
|
165
|
+
hash: opts.hash ?? "SHA-256",
|
|
166
|
+
salt: opts.salt ?? new Uint8Array(0),
|
|
167
|
+
info: toBytes(opts.info ?? "")
|
|
168
|
+
},
|
|
169
|
+
baseKey,
|
|
170
|
+
(opts.length ?? 32) * 8
|
|
171
|
+
);
|
|
172
|
+
return new Uint8Array(bits);
|
|
173
|
+
}
|
|
174
|
+
var KEYRING_PREFIX = "v2";
|
|
175
|
+
var Keyring = class {
|
|
176
|
+
constructor(entries, primaryId) {
|
|
177
|
+
this.keys = /* @__PURE__ */ new Map();
|
|
178
|
+
if (!entries.length) throw new Error("Keyring needs at least one key");
|
|
179
|
+
for (const e of entries) {
|
|
180
|
+
if (e.id.includes(":")) throw new Error(`key id "${e.id}" must not contain ":"`);
|
|
181
|
+
this.keys.set(e.id, e.key);
|
|
182
|
+
}
|
|
183
|
+
this.primaryId = primaryId ?? entries[entries.length - 1].id;
|
|
184
|
+
if (!this.keys.has(this.primaryId)) throw new Error(`primary key "${this.primaryId}" is not in the keyring`);
|
|
185
|
+
}
|
|
186
|
+
/** Encrypt under the primary key. */
|
|
187
|
+
async encrypt(plaintext, opts = {}) {
|
|
188
|
+
const inner = await encrypt(plaintext, this.keys.get(this.primaryId), opts);
|
|
189
|
+
const [, iv, ct] = inner.split(":");
|
|
190
|
+
return `${KEYRING_PREFIX}:${this.primaryId}:${iv}:${ct}`;
|
|
191
|
+
}
|
|
192
|
+
resolve(payload) {
|
|
193
|
+
const parts = payload.split(":");
|
|
194
|
+
if (parts[0] === KEYRING_PREFIX) {
|
|
195
|
+
const kid = parts[1];
|
|
196
|
+
const key = this.keys.get(kid);
|
|
197
|
+
if (!key) throw new Error(`unknown key id "${kid}"`);
|
|
198
|
+
return { key, inner: `${AES_PREFIX}:${parts[2]}:${parts[3]}` };
|
|
199
|
+
}
|
|
200
|
+
if (parts[0] === AES_PREFIX) return { key: this.keys.get(this.primaryId), inner: payload };
|
|
201
|
+
throw new Error("invalid ciphertext format");
|
|
202
|
+
}
|
|
203
|
+
async decrypt(payload, opts = {}) {
|
|
204
|
+
const { key, inner } = this.resolve(payload);
|
|
205
|
+
return decrypt(inner, key, opts);
|
|
206
|
+
}
|
|
207
|
+
async decryptBytes(payload, opts = {}) {
|
|
208
|
+
const { key, inner } = this.resolve(payload);
|
|
209
|
+
return decryptBytes(inner, key, opts);
|
|
210
|
+
}
|
|
211
|
+
/** Re-encrypt under the primary key if it isn't already. Returns the (possibly new) payload. */
|
|
212
|
+
async reEncrypt(payload, opts = {}) {
|
|
213
|
+
const parts = payload.split(":");
|
|
214
|
+
if (parts[0] === KEYRING_PREFIX && parts[1] === this.primaryId) return payload;
|
|
215
|
+
const pt = await this.decryptBytes(payload, opts);
|
|
216
|
+
return this.encrypt(pt, opts);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
150
219
|
|
|
151
|
-
export { constantTimeEqual, decrypt, decryptWithPassword, deriveBits, digest, encrypt, encryptWithPassword, fromBase64url, fromHex, generateKey, hmac, hmacVerify, randomBytes, sha256, toBase64url, toHex };
|
|
220
|
+
export { Keyring, constantTimeEqual, decrypt, decryptBytes, decryptWithPassword, deriveBits, digest, encrypt, encryptWithPassword, fromBase64url, fromHex, generateKey, hkdf, hmac, hmacVerify, randomBytes, sha256, toBase64url, toHex };
|
|
152
221
|
//# sourceMappingURL=index.js.map
|
|
153
222
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/crypto\n * Safe, boring cryptography — authenticated AES-256-GCM, key derivation, hashing.\n *\n * A thin, correct layer over the Web Crypto API (no hand-rolled crypto), so the\n * same code runs on Node 18+, edge runtimes, browsers and React Native. Encrypt\n * database fields, S3 object payloads, cookies and tokens with confidence.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nfunction getCrypto(): Crypto {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto unavailable — @lacspace/crypto needs Node 18+, an edge runtime or a browser\");\n }\n return c;\n}\n\n/* ------------------------------ encoding ------------------------------ */\n\nexport function randomBytes(length: number): Uint8Array {\n const buf = new Uint8Array(length);\n getCrypto().getRandomValues(buf);\n return buf;\n}\n\nexport function toHex(bytes: Uint8Array): string {\n let out = \"\";\n for (const b of bytes) out += b.toString(16).padStart(2, \"0\");\n return out;\n}\n\nexport function fromHex(hex: string): Uint8Array {\n const clean = hex.length % 2 ? \"0\" + hex : hex;\n const out = new Uint8Array(clean.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nexport function toBase64url(bytes: Uint8Array): string {\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n const b64 = typeof btoa !== \"undefined\" ? btoa(bin) : Buffer.from(bytes).toString(\"base64\");\n return b64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nexport function fromBase64url(s: string): Uint8Array {\n const b64 = s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4);\n if (typeof atob !== \"undefined\") {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n return new Uint8Array(Buffer.from(b64, \"base64\"));\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nfunction toBytes(data: string | Uint8Array): Uint8Array {\n return typeof data === \"string\" ? enc.encode(data) : data;\n}\n\n/** Constant-time comparison of two byte arrays or strings. */\nexport function constantTimeEqual(a: Uint8Array | string, b: Uint8Array | string): boolean {\n const x = toBytes(a);\n const y = toBytes(b);\n if (x.length !== y.length) return false;\n let diff = 0;\n for (let i = 0; i < x.length; i++) diff |= x[i]! ^ y[i]!;\n return diff === 0;\n}\n\n/* ------------------------------ hashing ------------------------------ */\n\nexport type HashAlgorithm = \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\nexport async function digest(\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const buf = await getCrypto().subtle.digest(algorithm, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(buf);\n}\n\n/** SHA-256 hex digest. */\nexport async function sha256(data: string | Uint8Array): Promise<string> {\n return toHex(await digest(data, \"SHA-256\"));\n}\n\n/** HMAC signature (bytes). */\nexport async function hmac(\n key: string | Uint8Array,\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const c = getCrypto();\n const cryptoKey = await c.subtle.importKey(\n \"raw\",\n toBytes(key) as unknown as BufferSource,\n { name: \"HMAC\", hash: algorithm },\n false,\n [\"sign\"],\n );\n const sig = await c.subtle.sign(\"HMAC\", cryptoKey, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(sig);\n}\n\n/** Verify an HMAC in constant time. */\nexport async function hmacVerify(\n key: string | Uint8Array,\n data: string | Uint8Array,\n signature: Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<boolean> {\n return constantTimeEqual(await hmac(key, data, algorithm), signature);\n}\n\n/* ------------------------------ key derivation ------------------------------ */\n\nexport interface DeriveOptions {\n iterations?: number;\n hash?: HashAlgorithm;\n /** Derived key length in bytes. Default 32. */\n length?: number;\n}\n\n/** Derive raw key bytes from a password with PBKDF2. */\nexport async function deriveBits(\n password: string | Uint8Array,\n salt: Uint8Array,\n opts: DeriveOptions = {},\n): Promise<Uint8Array> {\n const c = getCrypto();\n const baseKey = await c.subtle.importKey(\n \"raw\",\n toBytes(password) as unknown as BufferSource,\n \"PBKDF2\",\n false,\n [\"deriveBits\"],\n );\n const bits = await c.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: salt as unknown as BufferSource,\n iterations: opts.iterations ?? 210000,\n hash: opts.hash ?? \"SHA-256\",\n },\n baseKey,\n (opts.length ?? 32) * 8,\n );\n return new Uint8Array(bits);\n}\n\n/* ------------------------------ AES-256-GCM ------------------------------ */\n\nconst AES_PREFIX = \"v1\";\nconst AES_PW_PREFIX = \"v1p\";\nconst DEFAULT_PW_ITERATIONS = 210000;\n\n/** Generate a random 256-bit AES key as a base64url string. */\nexport function generateKey(): string {\n return toBase64url(randomBytes(32));\n}\n\nasync function importAesKey(key: string | Uint8Array): Promise<CryptoKey> {\n const raw = typeof key === \"string\" ? fromBase64url(key) : key;\n if (raw.length !== 32) throw new Error(\"AES key must be 32 bytes (256-bit)\");\n return getCrypto().subtle.importKey(\"raw\", raw as unknown as BufferSource, { name: \"AES-GCM\" }, false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\n/**\n * Encrypt with AES-256-GCM using a 32-byte key (base64url or bytes).\n * Returns a compact self-describing string: `v1:<iv>:<ciphertext>`.\n */\nexport async function encrypt(plaintext: string | Uint8Array, key: string | Uint8Array): Promise<string> {\n const cryptoKey = await importAesKey(key);\n const iv = randomBytes(12);\n const ct = await getCrypto().subtle.encrypt(\n { name: \"AES-GCM\", iv: iv as unknown as BufferSource },\n cryptoKey,\n toBytes(plaintext) as unknown as BufferSource,\n );\n return `${AES_PREFIX}:${toBase64url(iv)}:${toBase64url(new Uint8Array(ct))}`;\n}\n\n/** Decrypt a string produced by {@link encrypt}. Returns the UTF-8 plaintext. */\nexport async function decrypt(payload: string, key: string | Uint8Array): Promise<string> {\n const parts = payload.split(\":\");\n if (parts.length !== 3 || parts[0] !== AES_PREFIX) throw new Error(\"invalid ciphertext format\");\n const cryptoKey = await importAesKey(key);\n const iv = fromBase64url(parts[1]!);\n const ct = fromBase64url(parts[2]!);\n const pt = await getCrypto().subtle.decrypt(\n { name: \"AES-GCM\", iv: iv as unknown as BufferSource },\n cryptoKey,\n ct as unknown as BufferSource,\n );\n return dec.decode(pt);\n}\n\n/**\n * Encrypt with a passphrase (PBKDF2-derived key + AES-256-GCM).\n * Returns `v1p:<iterations>:<salt>:<iv>:<ciphertext>` — self-contained.\n */\nexport async function encryptWithPassword(\n plaintext: string | Uint8Array,\n password: string,\n opts: { iterations?: number } = {},\n): Promise<string> {\n const iterations = opts.iterations ?? DEFAULT_PW_ITERATIONS;\n const salt = randomBytes(16);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n const inner = await encrypt(plaintext, key);\n const [, iv, ct] = inner.split(\":\");\n return `${AES_PW_PREFIX}:${iterations}:${toBase64url(salt)}:${iv}:${ct}`;\n}\n\n/** Decrypt a string produced by {@link encryptWithPassword}. */\nexport async function decryptWithPassword(payload: string, password: string): Promise<string> {\n const parts = payload.split(\":\");\n if (parts.length !== 5 || parts[0] !== AES_PW_PREFIX) throw new Error(\"invalid ciphertext format\");\n const iterations = parseInt(parts[1]!, 10);\n const salt = fromBase64url(parts[2]!);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n return decrypt(`${AES_PREFIX}:${parts[3]}:${parts[4]}`, key);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AAWA,SAAS,SAAA,GAAoB;AAC3B,EAAA,MAAM,IAAK,UAAA,CAAmC,MAAA;AAC9C,EAAA,IAAI,CAAC,CAAA,IAAK,CAAC,CAAA,CAAE,MAAA,EAAQ;AACnB,IAAA,MAAM,IAAI,MAAM,6FAAwF,CAAA;AAAA,EAC1G;AACA,EAAA,OAAO,CAAA;AACT;AAIO,SAAS,YAAY,MAAA,EAA4B;AACtD,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,MAAM,CAAA;AACjC,EAAA,SAAA,EAAU,CAAE,gBAAgB,GAAG,CAAA;AAC/B,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,MAAM,KAAA,EAA2B;AAC/C,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,OAAO,GAAA,IAAO,CAAA,CAAE,SAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAA;AAC5D,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,QAAQ,GAAA,EAAyB;AAC/C,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,MAAA,GAAS,CAAA,GAAI,MAAM,GAAA,GAAM,GAAA;AAC3C,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,KAAA,CAAM,SAAS,CAAC,CAAA;AAC3C,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAI,MAAA,EAAQ,CAAA,EAAA,MAAS,CAAC,CAAA,GAAI,QAAA,CAAS,KAAA,CAAM,MAAM,CAAA,GAAI,CAAA,EAAG,IAAI,CAAA,GAAI,CAAC,GAAG,EAAE,CAAA;AACxF,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,YAAY,KAAA,EAA2B;AACrD,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,GAAA,IAAO,MAAA,CAAO,aAAa,CAAC,CAAA;AACnD,EAAA,MAAM,GAAA,GAAM,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAA;AAC1F,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACtE;AAEO,SAAS,cAAc,CAAA,EAAuB;AACnD,EAAA,MAAM,GAAA,GAAM,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,GAAG,IAAI,KAAA,CAAM,KAAA,CAAA,CAAO,CAAA,CAAE,MAAA,GAAS,KAAK,CAAC,CAAA;AACpF,EAAA,IAAI,OAAO,SAAS,WAAA,EAAa;AAC/B,IAAA,MAAM,GAAA,GAAM,KAAK,GAAG,CAAA;AACpB,IAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,GAAA,CAAI,MAAM,CAAA;AACrC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,CAAC,CAAA,GAAI,GAAA,CAAI,UAAA,CAAW,CAAC,CAAA;AAC9D,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAI,UAAA,CAAW,MAAA,CAAO,IAAA,CAAK,GAAA,EAAK,QAAQ,CAAC,CAAA;AAClD;AAEA,IAAM,GAAA,GAAM,IAAI,WAAA,EAAY;AAC5B,IAAM,GAAA,GAAM,IAAI,WAAA,EAAY;AAE5B,SAAS,QAAQ,IAAA,EAAuC;AACtD,EAAA,OAAO,OAAO,IAAA,KAAS,QAAA,GAAW,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,GAAI,IAAA;AACvD;AAGO,SAAS,iBAAA,CAAkB,GAAwB,CAAA,EAAiC;AACzF,EAAA,MAAM,CAAA,GAAI,QAAQ,CAAC,CAAA;AACnB,EAAA,MAAM,CAAA,GAAI,QAAQ,CAAC,CAAA;AACnB,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAA,IAAQ,CAAA,CAAE,CAAC,CAAA,GAAK,CAAA,CAAE,CAAC,CAAA;AACtD,EAAA,OAAO,IAAA,KAAS,CAAA;AAClB;AAMA,eAAsB,MAAA,CACpB,IAAA,EACA,SAAA,GAA2B,SAAA,EACN;AACrB,EAAA,MAAM,GAAA,GAAM,MAAM,SAAA,EAAU,CAAE,OAAO,MAAA,CAAO,SAAA,EAAW,OAAA,CAAQ,IAAI,CAA4B,CAAA;AAC/F,EAAA,OAAO,IAAI,WAAW,GAAG,CAAA;AAC3B;AAGA,eAAsB,OAAO,IAAA,EAA4C;AACvE,EAAA,OAAO,KAAA,CAAM,MAAM,MAAA,CAAO,IAAA,EAAM,SAAS,CAAC,CAAA;AAC5C;AAGA,eAAsB,IAAA,CACpB,GAAA,EACA,IAAA,EACA,SAAA,GAA2B,SAAA,EACN;AACrB,EAAA,MAAM,IAAI,SAAA,EAAU;AACpB,EAAA,MAAM,SAAA,GAAY,MAAM,CAAA,CAAE,MAAA,CAAO,SAAA;AAAA,IAC/B,KAAA;AAAA,IACA,QAAQ,GAAG,CAAA;AAAA,IACX,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAU;AAAA,IAChC,KAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AACA,EAAA,MAAM,GAAA,GAAM,MAAM,CAAA,CAAE,MAAA,CAAO,KAAK,MAAA,EAAQ,SAAA,EAAW,OAAA,CAAQ,IAAI,CAA4B,CAAA;AAC3F,EAAA,OAAO,IAAI,WAAW,GAAG,CAAA;AAC3B;AAGA,eAAsB,UAAA,CACpB,GAAA,EACA,IAAA,EACA,SAAA,EACA,YAA2B,SAAA,EACT;AAClB,EAAA,OAAO,kBAAkB,MAAM,IAAA,CAAK,KAAK,IAAA,EAAM,SAAS,GAAG,SAAS,CAAA;AACtE;AAYA,eAAsB,UAAA,CACpB,QAAA,EACA,IAAA,EACA,IAAA,GAAsB,EAAC,EACF;AACrB,EAAA,MAAM,IAAI,SAAA,EAAU;AACpB,EAAA,MAAM,OAAA,GAAU,MAAM,CAAA,CAAE,MAAA,CAAO,SAAA;AAAA,IAC7B,KAAA;AAAA,IACA,QAAQ,QAAQ,CAAA;AAAA,IAChB,QAAA;AAAA,IACA,KAAA;AAAA,IACA,CAAC,YAAY;AAAA,GACf;AACA,EAAA,MAAM,IAAA,GAAO,MAAM,CAAA,CAAE,MAAA,CAAO,UAAA;AAAA,IAC1B;AAAA,MACE,IAAA,EAAM,QAAA;AAAA,MACN,IAAA;AAAA,MACA,UAAA,EAAY,KAAK,UAAA,IAAc,IAAA;AAAA,MAC/B,IAAA,EAAM,KAAK,IAAA,IAAQ;AAAA,KACrB;AAAA,IACA,OAAA;AAAA,IAAA,CACC,IAAA,CAAK,UAAU,EAAA,IAAM;AAAA,GACxB;AACA,EAAA,OAAO,IAAI,WAAW,IAAI,CAAA;AAC5B;AAIA,IAAM,UAAA,GAAa,IAAA;AACnB,IAAM,aAAA,GAAgB,KAAA;AACtB,IAAM,qBAAA,GAAwB,IAAA;AAGvB,SAAS,WAAA,GAAsB;AACpC,EAAA,OAAO,WAAA,CAAY,WAAA,CAAY,EAAE,CAAC,CAAA;AACpC;AAEA,eAAe,aAAa,GAAA,EAA8C;AACxE,EAAA,MAAM,MAAM,OAAO,GAAA,KAAQ,QAAA,GAAW,aAAA,CAAc,GAAG,CAAA,GAAI,GAAA;AAC3D,EAAA,IAAI,IAAI,MAAA,KAAW,EAAA,EAAI,MAAM,IAAI,MAAM,oCAAoC,CAAA;AAC3E,EAAA,OAAO,SAAA,EAAU,CAAE,MAAA,CAAO,SAAA,CAAU,KAAA,EAAO,KAAgC,EAAE,IAAA,EAAM,SAAA,EAAU,EAAG,KAAA,EAAO;AAAA,IACrG,SAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH;AAOA,SAAS,SAAA,CAAU,IAAgB,GAAA,EAAyC;AAC1E,EAAA,MAAM,CAAA,GAAkB,EAAE,IAAA,EAAM,SAAA,EAAW,EAAA,EAAkC;AAC7E,EAAA,IAAI,GAAA,KAAQ,MAAA,EAAW,CAAA,CAAE,cAAA,GAAiB,QAAQ,GAAG,CAAA;AACrD,EAAA,OAAO,CAAA;AACT;AAOA,eAAsB,OAAA,CACpB,SAAA,EACA,GAAA,EACA,IAAA,GAAmB,EAAC,EACH;AACjB,EAAA,MAAM,SAAA,GAAY,MAAM,YAAA,CAAa,GAAG,CAAA;AACxC,EAAA,MAAM,EAAA,GAAK,YAAY,EAAE,CAAA;AACzB,EAAA,MAAM,EAAA,GAAK,MAAM,SAAA,EAAU,CAAE,MAAA,CAAO,OAAA;AAAA,IAClC,SAAA,CAAU,EAAA,EAAI,IAAA,CAAK,GAAG,CAAA;AAAA,IACtB,SAAA;AAAA,IACA,QAAQ,SAAS;AAAA,GACnB;AACA,EAAA,OAAO,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,WAAA,CAAY,EAAE,CAAC,CAAA,CAAA,EAAI,WAAA,CAAY,IAAI,UAAA,CAAW,EAAE,CAAC,CAAC,CAAA,CAAA;AAC5E;AAEA,eAAe,cAAA,CAAe,OAAA,EAAiB,GAAA,EAA0B,IAAA,EAAuC;AAC9G,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAC/B,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,KAAA,CAAM,CAAC,MAAM,UAAA,EAAY,MAAM,IAAI,KAAA,CAAM,2BAA2B,CAAA;AAC9F,EAAA,MAAM,SAAA,GAAY,MAAM,YAAA,CAAa,GAAG,CAAA;AACxC,EAAA,MAAM,EAAA,GAAK,aAAA,CAAc,KAAA,CAAM,CAAC,CAAE,CAAA;AAClC,EAAA,MAAM,EAAA,GAAK,aAAA,CAAc,KAAA,CAAM,CAAC,CAAE,CAAA;AAClC,EAAA,MAAM,EAAA,GAAK,MAAM,SAAA,EAAU,CAAE,MAAA,CAAO,OAAA,CAAQ,SAAA,CAAU,EAAA,EAAI,IAAA,CAAK,GAAG,CAAA,EAAG,SAAA,EAAW,EAA6B,CAAA;AAC7G,EAAA,OAAO,IAAI,WAAW,EAAE,CAAA;AAC1B;AAGA,eAAsB,OAAA,CAAQ,OAAA,EAAiB,GAAA,EAA0B,IAAA,GAAmB,EAAC,EAAoB;AAC/G,EAAA,OAAO,IAAI,MAAA,CAAO,MAAM,eAAe,OAAA,EAAS,GAAA,EAAK,IAAI,CAAC,CAAA;AAC5D;AAGA,eAAsB,YAAA,CACpB,OAAA,EACA,GAAA,EACA,IAAA,GAAmB,EAAC,EACC;AACrB,EAAA,OAAO,cAAA,CAAe,OAAA,EAAS,GAAA,EAAK,IAAI,CAAA;AAC1C;AAMA,eAAsB,mBAAA,CACpB,SAAA,EACA,QAAA,EACA,IAAA,GAAgC,EAAC,EAChB;AACjB,EAAA,MAAM,UAAA,GAAa,KAAK,UAAA,IAAc,qBAAA;AACtC,EAAA,MAAM,IAAA,GAAO,YAAY,EAAE,CAAA;AAC3B,EAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,QAAA,EAAU,MAAM,EAAE,UAAA,EAAY,MAAA,EAAQ,EAAA,EAAI,CAAA;AACvE,EAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA;AAC1C,EAAA,MAAM,GAAG,EAAA,EAAI,EAAE,CAAA,GAAI,KAAA,CAAM,MAAM,GAAG,CAAA;AAClC,EAAA,OAAO,CAAA,EAAG,aAAa,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA,EAAI,WAAA,CAAY,IAAI,CAAC,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA;AACxE;AAGA,eAAsB,mBAAA,CAAoB,SAAiB,QAAA,EAAmC;AAC5F,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAC/B,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,KAAA,CAAM,CAAC,MAAM,aAAA,EAAe,MAAM,IAAI,KAAA,CAAM,2BAA2B,CAAA;AACjG,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,CAAC,GAAI,EAAE,CAAA;AACzC,EAAA,MAAM,IAAA,GAAO,aAAA,CAAc,KAAA,CAAM,CAAC,CAAE,CAAA;AACpC,EAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,QAAA,EAAU,MAAM,EAAE,UAAA,EAAY,MAAA,EAAQ,EAAA,EAAI,CAAA;AACvE,EAAA,OAAO,OAAA,CAAQ,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA;AAC7D;AAkBA,eAAsB,IAAA,CAAK,WAAA,EAAkC,IAAA,GAAoB,EAAC,EAAwB;AACxG,EAAA,MAAM,IAAI,SAAA,EAAU;AACpB,EAAA,MAAM,OAAA,GAAU,MAAM,CAAA,CAAE,MAAA,CAAO,SAAA,CAAU,OAAO,OAAA,CAAQ,WAAW,CAAA,EAA8B,MAAA,EAAQ,KAAA,EAAO;AAAA,IAC9G;AAAA,GACD,CAAA;AACD,EAAA,MAAM,IAAA,GAAO,MAAM,CAAA,CAAE,MAAA,CAAO,UAAA;AAAA,IAC1B;AAAA,MACE,IAAA,EAAM,MAAA;AAAA,MACN,IAAA,EAAM,KAAK,IAAA,IAAQ,SAAA;AAAA,MACnB,IAAA,EAAO,IAAA,CAAK,IAAA,IAAQ,IAAI,WAAW,CAAC,CAAA;AAAA,MACpC,IAAA,EAAM,OAAA,CAAQ,IAAA,CAAK,IAAA,IAAQ,EAAE;AAAA,KAC/B;AAAA,IACA,OAAA;AAAA,IAAA,CACC,IAAA,CAAK,UAAU,EAAA,IAAM;AAAA,GACxB;AACA,EAAA,OAAO,IAAI,WAAW,IAAI,CAAA;AAC5B;AAIA,IAAM,cAAA,GAAiB,IAAA;AAoBhB,IAAM,UAAN,MAAc;AAAA,EAInB,WAAA,CAAY,SAAyB,SAAA,EAAoB;AAHzD,IAAA,IAAA,CAAiB,IAAA,uBAAW,GAAA,EAAiC;AAI3D,IAAA,IAAI,CAAC,OAAA,CAAQ,MAAA,EAAQ,MAAM,IAAI,MAAM,gCAAgC,CAAA;AACrE,IAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,MAAA,IAAI,CAAA,CAAE,EAAA,CAAG,QAAA,CAAS,GAAG,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,CAAA,CAAE,EAAE,CAAA,sBAAA,CAAwB,CAAA;AAC/E,MAAA,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,EAAA,EAAI,EAAE,GAAG,CAAA;AAAA,IAC3B;AACA,IAAA,IAAA,CAAK,YAAY,SAAA,IAAa,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,CAAG,EAAA;AAC3D,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,SAAS,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,CAAA,aAAA,EAAgB,IAAA,CAAK,SAAS,CAAA,uBAAA,CAAyB,CAAA;AAAA,EAC7G;AAAA;AAAA,EAGA,MAAM,OAAA,CAAQ,SAAA,EAAgC,IAAA,GAAmB,EAAC,EAAoB;AACpF,IAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,SAAA,EAAW,IAAA,CAAK,KAAK,GAAA,CAAI,IAAA,CAAK,SAAS,CAAA,EAAI,IAAI,CAAA;AAC3E,IAAA,MAAM,GAAG,EAAA,EAAI,EAAE,CAAA,GAAI,KAAA,CAAM,MAAM,GAAG,CAAA;AAClC,IAAA,OAAO,CAAA,EAAG,cAAc,CAAA,CAAA,EAAI,IAAA,CAAK,SAAS,CAAA,CAAA,EAAI,EAAE,IAAI,EAAE,CAAA,CAAA;AAAA,EACxD;AAAA,EAEQ,QAAQ,OAAA,EAA8D;AAC5E,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAC/B,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,KAAM,cAAA,EAAgB;AAC/B,MAAA,MAAM,GAAA,GAAM,MAAM,CAAC,CAAA;AACnB,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA;AAC7B,MAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,GAAG,CAAA,CAAA,CAAG,CAAA;AACnD,MAAA,OAAO,EAAE,GAAA,EAAK,KAAA,EAAO,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,EAAG;AAAA,IAC/D;AACA,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,KAAM,UAAA,SAAmB,EAAE,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,SAAS,CAAA,EAAI,OAAO,OAAA,EAAQ;AAC1F,IAAA,MAAM,IAAI,MAAM,2BAA2B,CAAA;AAAA,EAC7C;AAAA,EAEA,MAAM,OAAA,CAAQ,OAAA,EAAiB,IAAA,GAAmB,EAAC,EAAoB;AACrE,IAAA,MAAM,EAAE,GAAA,EAAK,KAAA,EAAM,GAAI,IAAA,CAAK,QAAQ,OAAO,CAAA;AAC3C,IAAA,OAAO,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,IAAI,CAAA;AAAA,EACjC;AAAA,EAEA,MAAM,YAAA,CAAa,OAAA,EAAiB,IAAA,GAAmB,EAAC,EAAwB;AAC9E,IAAA,MAAM,EAAE,GAAA,EAAK,KAAA,EAAM,GAAI,IAAA,CAAK,QAAQ,OAAO,CAAA;AAC3C,IAAA,OAAO,YAAA,CAAa,KAAA,EAAO,GAAA,EAAK,IAAI,CAAA;AAAA,EACtC;AAAA;AAAA,EAGA,MAAM,SAAA,CAAU,OAAA,EAAiB,IAAA,GAAmB,EAAC,EAAoB;AACvE,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAC/B,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,KAAM,cAAA,IAAkB,MAAM,CAAC,CAAA,KAAM,IAAA,CAAK,SAAA,EAAW,OAAO,OAAA;AACvE,IAAA,MAAM,EAAA,GAAK,MAAM,IAAA,CAAK,YAAA,CAAa,SAAS,IAAI,CAAA;AAChD,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,EAAA,EAAI,IAAI,CAAA;AAAA,EAC9B;AACF","file":"index.js","sourcesContent":["/**\n * @lacspace/crypto\n * Safe, boring cryptography — authenticated AES-256-GCM, key derivation, hashing.\n *\n * A thin, correct layer over the Web Crypto API (no hand-rolled crypto), so the\n * same code runs on Node 18+, edge runtimes, browsers and React Native. Encrypt\n * database fields, S3 object payloads, cookies and tokens with confidence.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\nfunction getCrypto(): Crypto {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto unavailable — @lacspace/crypto needs Node 18+, an edge runtime or a browser\");\n }\n return c;\n}\n\n/* ------------------------------ encoding ------------------------------ */\n\nexport function randomBytes(length: number): Uint8Array {\n const buf = new Uint8Array(length);\n getCrypto().getRandomValues(buf);\n return buf;\n}\n\nexport function toHex(bytes: Uint8Array): string {\n let out = \"\";\n for (const b of bytes) out += b.toString(16).padStart(2, \"0\");\n return out;\n}\n\nexport function fromHex(hex: string): Uint8Array {\n const clean = hex.length % 2 ? \"0\" + hex : hex;\n const out = new Uint8Array(clean.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nexport function toBase64url(bytes: Uint8Array): string {\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n const b64 = typeof btoa !== \"undefined\" ? btoa(bin) : Buffer.from(bytes).toString(\"base64\");\n return b64.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nexport function fromBase64url(s: string): Uint8Array {\n const b64 = s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4);\n if (typeof atob !== \"undefined\") {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n return new Uint8Array(Buffer.from(b64, \"base64\"));\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nfunction toBytes(data: string | Uint8Array): Uint8Array {\n return typeof data === \"string\" ? enc.encode(data) : data;\n}\n\n/** Constant-time comparison of two byte arrays or strings. */\nexport function constantTimeEqual(a: Uint8Array | string, b: Uint8Array | string): boolean {\n const x = toBytes(a);\n const y = toBytes(b);\n if (x.length !== y.length) return false;\n let diff = 0;\n for (let i = 0; i < x.length; i++) diff |= x[i]! ^ y[i]!;\n return diff === 0;\n}\n\n/* ------------------------------ hashing ------------------------------ */\n\nexport type HashAlgorithm = \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\nexport async function digest(\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const buf = await getCrypto().subtle.digest(algorithm, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(buf);\n}\n\n/** SHA-256 hex digest. */\nexport async function sha256(data: string | Uint8Array): Promise<string> {\n return toHex(await digest(data, \"SHA-256\"));\n}\n\n/** HMAC signature (bytes). */\nexport async function hmac(\n key: string | Uint8Array,\n data: string | Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<Uint8Array> {\n const c = getCrypto();\n const cryptoKey = await c.subtle.importKey(\n \"raw\",\n toBytes(key) as unknown as BufferSource,\n { name: \"HMAC\", hash: algorithm },\n false,\n [\"sign\"],\n );\n const sig = await c.subtle.sign(\"HMAC\", cryptoKey, toBytes(data) as unknown as BufferSource);\n return new Uint8Array(sig);\n}\n\n/** Verify an HMAC in constant time. */\nexport async function hmacVerify(\n key: string | Uint8Array,\n data: string | Uint8Array,\n signature: Uint8Array,\n algorithm: HashAlgorithm = \"SHA-256\",\n): Promise<boolean> {\n return constantTimeEqual(await hmac(key, data, algorithm), signature);\n}\n\n/* ------------------------------ key derivation ------------------------------ */\n\nexport interface DeriveOptions {\n iterations?: number;\n hash?: HashAlgorithm;\n /** Derived key length in bytes. Default 32. */\n length?: number;\n}\n\n/** Derive raw key bytes from a password with PBKDF2. */\nexport async function deriveBits(\n password: string | Uint8Array,\n salt: Uint8Array,\n opts: DeriveOptions = {},\n): Promise<Uint8Array> {\n const c = getCrypto();\n const baseKey = await c.subtle.importKey(\n \"raw\",\n toBytes(password) as unknown as BufferSource,\n \"PBKDF2\",\n false,\n [\"deriveBits\"],\n );\n const bits = await c.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt: salt as unknown as BufferSource,\n iterations: opts.iterations ?? 210000,\n hash: opts.hash ?? \"SHA-256\",\n },\n baseKey,\n (opts.length ?? 32) * 8,\n );\n return new Uint8Array(bits);\n}\n\n/* ------------------------------ AES-256-GCM ------------------------------ */\n\nconst AES_PREFIX = \"v1\";\nconst AES_PW_PREFIX = \"v1p\";\nconst DEFAULT_PW_ITERATIONS = 210000;\n\n/** Generate a random 256-bit AES key as a base64url string. */\nexport function generateKey(): string {\n return toBase64url(randomBytes(32));\n}\n\nasync function importAesKey(key: string | Uint8Array): Promise<CryptoKey> {\n const raw = typeof key === \"string\" ? fromBase64url(key) : key;\n if (raw.length !== 32) throw new Error(\"AES key must be 32 bytes (256-bit)\");\n return getCrypto().subtle.importKey(\"raw\", raw as unknown as BufferSource, { name: \"AES-GCM\" }, false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\nexport interface AesOptions {\n /** Additional Authenticated Data — bound to the ciphertext (must match on decrypt). */\n aad?: string | Uint8Array;\n}\n\nfunction gcmParams(iv: Uint8Array, aad?: string | Uint8Array): AesGcmParams {\n const p: AesGcmParams = { name: \"AES-GCM\", iv: iv as unknown as BufferSource };\n if (aad !== undefined) p.additionalData = toBytes(aad) as unknown as BufferSource;\n return p;\n}\n\n/**\n * Encrypt with AES-256-GCM using a 32-byte key (base64url or bytes).\n * Returns a compact self-describing string: `v1:<iv>:<ciphertext>`.\n * Pass `opts.aad` to bind the ciphertext to a context (row id, tenant…).\n */\nexport async function encrypt(\n plaintext: string | Uint8Array,\n key: string | Uint8Array,\n opts: AesOptions = {},\n): Promise<string> {\n const cryptoKey = await importAesKey(key);\n const iv = randomBytes(12);\n const ct = await getCrypto().subtle.encrypt(\n gcmParams(iv, opts.aad),\n cryptoKey,\n toBytes(plaintext) as unknown as BufferSource,\n );\n return `${AES_PREFIX}:${toBase64url(iv)}:${toBase64url(new Uint8Array(ct))}`;\n}\n\nasync function decryptToBytes(payload: string, key: string | Uint8Array, opts: AesOptions): Promise<Uint8Array> {\n const parts = payload.split(\":\");\n if (parts.length !== 3 || parts[0] !== AES_PREFIX) throw new Error(\"invalid ciphertext format\");\n const cryptoKey = await importAesKey(key);\n const iv = fromBase64url(parts[1]!);\n const ct = fromBase64url(parts[2]!);\n const pt = await getCrypto().subtle.decrypt(gcmParams(iv, opts.aad), cryptoKey, ct as unknown as BufferSource);\n return new Uint8Array(pt);\n}\n\n/** Decrypt a string produced by {@link encrypt}. Returns the UTF-8 plaintext. */\nexport async function decrypt(payload: string, key: string | Uint8Array, opts: AesOptions = {}): Promise<string> {\n return dec.decode(await decryptToBytes(payload, key, opts));\n}\n\n/** Decrypt to raw bytes — binary-safe (files, protobufs, images). */\nexport async function decryptBytes(\n payload: string,\n key: string | Uint8Array,\n opts: AesOptions = {},\n): Promise<Uint8Array> {\n return decryptToBytes(payload, key, opts);\n}\n\n/**\n * Encrypt with a passphrase (PBKDF2-derived key + AES-256-GCM).\n * Returns `v1p:<iterations>:<salt>:<iv>:<ciphertext>` — self-contained.\n */\nexport async function encryptWithPassword(\n plaintext: string | Uint8Array,\n password: string,\n opts: { iterations?: number } = {},\n): Promise<string> {\n const iterations = opts.iterations ?? DEFAULT_PW_ITERATIONS;\n const salt = randomBytes(16);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n const inner = await encrypt(plaintext, key);\n const [, iv, ct] = inner.split(\":\");\n return `${AES_PW_PREFIX}:${iterations}:${toBase64url(salt)}:${iv}:${ct}`;\n}\n\n/** Decrypt a string produced by {@link encryptWithPassword}. */\nexport async function decryptWithPassword(payload: string, password: string): Promise<string> {\n const parts = payload.split(\":\");\n if (parts.length !== 5 || parts[0] !== AES_PW_PREFIX) throw new Error(\"invalid ciphertext format\");\n const iterations = parseInt(parts[1]!, 10);\n const salt = fromBase64url(parts[2]!);\n const key = await deriveBits(password, salt, { iterations, length: 32 });\n return decrypt(`${AES_PREFIX}:${parts[3]}:${parts[4]}`, key);\n}\n\n/* ------------------------------ HKDF ------------------------------ */\n\nexport interface HkdfOptions {\n /** Optional salt (recommended). */\n salt?: Uint8Array;\n /** Context/label so the same master key yields different sub-keys per purpose. */\n info?: string | Uint8Array;\n /** Output length in bytes. Default 32. */\n length?: number;\n hash?: HashAlgorithm;\n}\n\n/**\n * HKDF: derive one or many purpose-bound sub-keys from a single master key.\n * @example const encKey = await hkdf(master, { info: \"field-encryption\", length: 32 });\n */\nexport async function hkdf(keyMaterial: string | Uint8Array, opts: HkdfOptions = {}): Promise<Uint8Array> {\n const c = getCrypto();\n const baseKey = await c.subtle.importKey(\"raw\", toBytes(keyMaterial) as unknown as BufferSource, \"HKDF\", false, [\n \"deriveBits\",\n ]);\n const bits = await c.subtle.deriveBits(\n {\n name: \"HKDF\",\n hash: opts.hash ?? \"SHA-256\",\n salt: (opts.salt ?? new Uint8Array(0)) as unknown as BufferSource,\n info: toBytes(opts.info ?? \"\") as unknown as BufferSource,\n },\n baseKey,\n (opts.length ?? 32) * 8,\n );\n return new Uint8Array(bits);\n}\n\n/* ------------------------------ key rotation ------------------------------ */\n\nconst KEYRING_PREFIX = \"v2\";\n\nexport interface KeyringEntry {\n /** Stable key id (embedded in ciphertext; must not contain \":\"). */\n id: string;\n /** 32-byte AES key (base64url string or bytes). */\n key: string | Uint8Array;\n}\n\n/**\n * A set of versioned AES keys for zero-downtime rotation. New data is encrypted\n * under the primary key; old ciphertext is decrypted by the key its `id` names.\n * Envelope: `v2:<keyId>:<iv>:<ciphertext>`.\n *\n * @example\n * const ring = new Keyring([{ id: \"2025\", key: oldKey }, { id: \"2026\", key: newKey }]);\n * const blob = await ring.encrypt(\"secret\"); // uses \"2026\" (primary = last)\n * const text = await ring.decrypt(oldBlob); // finds the right key by id\n * const fresh = await ring.reEncrypt(oldBlob); // migrate to the primary key\n */\nexport class Keyring {\n private readonly keys = new Map<string, string | Uint8Array>();\n readonly primaryId: string;\n\n constructor(entries: KeyringEntry[], primaryId?: string) {\n if (!entries.length) throw new Error(\"Keyring needs at least one key\");\n for (const e of entries) {\n if (e.id.includes(\":\")) throw new Error(`key id \"${e.id}\" must not contain \":\"`);\n this.keys.set(e.id, e.key);\n }\n this.primaryId = primaryId ?? entries[entries.length - 1]!.id;\n if (!this.keys.has(this.primaryId)) throw new Error(`primary key \"${this.primaryId}\" is not in the keyring`);\n }\n\n /** Encrypt under the primary key. */\n async encrypt(plaintext: string | Uint8Array, opts: AesOptions = {}): Promise<string> {\n const inner = await encrypt(plaintext, this.keys.get(this.primaryId)!, opts);\n const [, iv, ct] = inner.split(\":\");\n return `${KEYRING_PREFIX}:${this.primaryId}:${iv}:${ct}`;\n }\n\n private resolve(payload: string): { key: string | Uint8Array; inner: string } {\n const parts = payload.split(\":\");\n if (parts[0] === KEYRING_PREFIX) {\n const kid = parts[1]!;\n const key = this.keys.get(kid);\n if (!key) throw new Error(`unknown key id \"${kid}\"`);\n return { key, inner: `${AES_PREFIX}:${parts[2]}:${parts[3]}` };\n }\n if (parts[0] === AES_PREFIX) return { key: this.keys.get(this.primaryId)!, inner: payload }; // legacy v1\n throw new Error(\"invalid ciphertext format\");\n }\n\n async decrypt(payload: string, opts: AesOptions = {}): Promise<string> {\n const { key, inner } = this.resolve(payload);\n return decrypt(inner, key, opts);\n }\n\n async decryptBytes(payload: string, opts: AesOptions = {}): Promise<Uint8Array> {\n const { key, inner } = this.resolve(payload);\n return decryptBytes(inner, key, opts);\n }\n\n /** Re-encrypt under the primary key if it isn't already. Returns the (possibly new) payload. */\n async reEncrypt(payload: string, opts: AesOptions = {}): Promise<string> {\n const parts = payload.split(\":\");\n if (parts[0] === KEYRING_PREFIX && parts[1] === this.primaryId) return payload;\n const pt = await this.decryptBytes(payload, opts);\n return this.encrypt(pt, opts);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/crypto",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Safe, boring cryptography over Web Crypto — authenticated AES-256-GCM, PBKDF2 key derivation, SHA-256, HMAC, secure random and constant-time compare. Isomorphic (Node, edge, browser, RN).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|