@ursalock/crypto 0.3.0 → 0.4.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/dist/index.d.ts CHANGED
@@ -322,6 +322,59 @@ declare function computeHmac(data: Uint8Array, key: Uint8Array): Promise<string>
322
322
  */
323
323
  declare function verifyHmac(data: Uint8Array, key: Uint8Array, expectedHmac: string): Promise<boolean>;
324
324
 
325
+ /**
326
+ * HKDF-SHA256 key derivation (RFC 5869)
327
+ *
328
+ * Uses Web Crypto's native HKDF implementation for deriving sub-keys
329
+ * from a master key. This is used to generate vault-specific keys for
330
+ * encryption, HMAC, and indexing from a single master key.
331
+ *
332
+ * Design rationale:
333
+ * - Key separation: Each purpose gets its own derived key
334
+ * - Context binding: Vault UID prevents key reuse across vaults
335
+ * - Versioned info strings: "ursalock:v1:..." allows future changes
336
+ * - Domain separation: Different info → different keys (collision-free)
337
+ */
338
+ /** Vault-specific derived keys */
339
+ interface VaultKeys {
340
+ /** AES-256-GCM encryption key for vault documents */
341
+ encryptionKey: Uint8Array;
342
+ /** HMAC-SHA256 key for integrity verification */
343
+ hmacKey: Uint8Array;
344
+ /** Key for encrypted search indexes (future use) */
345
+ indexKey: Uint8Array;
346
+ }
347
+ /**
348
+ * Derive a sub-key using HKDF-SHA256 (RFC 5869)
349
+ *
350
+ * Uses Web Crypto's native HKDF implementation.
351
+ *
352
+ * @param ikm - Input keying material (master key)
353
+ * @param info - Context string (e.g., "ursalock:vault:abc123:encrypt") or raw bytes
354
+ * @param salt - Optional salt (default: empty buffer)
355
+ * @param length - Output key length in bytes (default: 32 for AES-256)
356
+ * @returns Derived key material
357
+ */
358
+ declare function hkdf(ikm: Uint8Array, info: string | Uint8Array, salt?: Uint8Array, length?: number): Promise<Uint8Array>;
359
+ /**
360
+ * Derive a complete set of vault keys from a master key
361
+ *
362
+ * Uses HKDF with vault-specific context strings to derive:
363
+ * - encryptionKey: for AES-256-GCM document encryption
364
+ * - hmacKey: for integrity verification (Encrypt-then-MAC)
365
+ * - indexKey: for encrypted search indexes (future)
366
+ *
367
+ * Context format: "ursalock:v1:<purpose>:<vaultUid>"
368
+ * - Versioned to allow future changes
369
+ * - Vault UID prevents key reuse across vaults
370
+ * - Purpose string provides domain separation
371
+ *
372
+ * @param masterKey - Master key (from Argon2id or ZKC PRF)
373
+ * @param vaultUid - Vault unique identifier (used as context)
374
+ * @returns Set of derived vault keys
375
+ */
376
+ declare function deriveVaultKeys(masterKey: Uint8Array, vaultUid: string): Promise<VaultKeys>;
377
+
325
378
  /**
326
379
  * JWK-based encryption using @z-base/cryptosuite
327
380
  * For use with ZKCredentials PRF-derived keys
@@ -361,4 +414,4 @@ declare function encryptStringWithJwk(plaintext: string, cipherJwk: CipherJWK):
361
414
  */
362
415
  declare function decryptStringWithJwk(encrypted: Uint8Array | JwkEncryptedPayload, cipherJwk: CipherJWK): Promise<string>;
363
416
 
364
- export { DEFAULT_ARGON2_PARAMS, type DeriveKeyOptions, type DerivedKey, type EncryptedPayload, type ICryptoProvider, type IEncryptedPayload, type IKeyDerivationProvider, type JwkEncryptedPayload, LEGACY_ARGON2_PARAMS, type RecoveryKey, WebCryptoProvider, bytesToRecoveryKey, computeHmac, constantTimeEqual, decrypt, decryptString, decryptStringWithJwk, decryptWithJwk, deriveKey, encrypt, encryptString, encryptStringWithJwk, encryptWithJwk, formatRecoveryKey, generateRecoveryKey, getCryptoProvider, randomBytes, recoveryKeyToBytes, setCryptoProvider, validateRecoveryKey, verifyHmac };
417
+ export { DEFAULT_ARGON2_PARAMS, type DeriveKeyOptions, type DerivedKey, type EncryptedPayload, type ICryptoProvider, type IEncryptedPayload, type IKeyDerivationProvider, type JwkEncryptedPayload, LEGACY_ARGON2_PARAMS, type RecoveryKey, type VaultKeys, WebCryptoProvider, bytesToRecoveryKey, computeHmac, constantTimeEqual, decrypt, decryptString, decryptStringWithJwk, decryptWithJwk, deriveKey, deriveVaultKeys, encrypt, encryptString, encryptStringWithJwk, encryptWithJwk, formatRecoveryKey, generateRecoveryKey, getCryptoProvider, hkdf, randomBytes, recoveryKeyToBytes, setCryptoProvider, validateRecoveryKey, verifyHmac };
package/dist/index.js CHANGED
@@ -271,6 +271,39 @@ function hexToBytes(hex) {
271
271
  return bytes;
272
272
  }
273
273
 
274
+ // src/hkdf.ts
275
+ async function hkdf(ikm, info, salt, length = 32) {
276
+ const infoBytes = typeof info === "string" ? new TextEncoder().encode(info) : info;
277
+ const actualSalt = salt ?? new Uint8Array(0);
278
+ const baseKey = await crypto.subtle.importKey(
279
+ "raw",
280
+ ikm.buffer.slice(ikm.byteOffset, ikm.byteOffset + ikm.byteLength),
281
+ { name: "HKDF" },
282
+ false,
283
+ ["deriveBits"]
284
+ );
285
+ const derivedBits = await crypto.subtle.deriveBits(
286
+ {
287
+ name: "HKDF",
288
+ hash: "SHA-256",
289
+ salt: actualSalt.buffer.slice(actualSalt.byteOffset, actualSalt.byteOffset + actualSalt.byteLength),
290
+ info: infoBytes.buffer.slice(infoBytes.byteOffset, infoBytes.byteOffset + infoBytes.byteLength)
291
+ },
292
+ baseKey,
293
+ length * 8
294
+ // Convert bytes to bits
295
+ );
296
+ return new Uint8Array(derivedBits);
297
+ }
298
+ async function deriveVaultKeys(masterKey, vaultUid) {
299
+ const [encryptionKey, hmacKey, indexKey] = await Promise.all([
300
+ hkdf(masterKey, `ursalock:v1:encrypt:${vaultUid}`),
301
+ hkdf(masterKey, `ursalock:v1:hmac:${vaultUid}`),
302
+ hkdf(masterKey, `ursalock:v1:index:${vaultUid}`)
303
+ ]);
304
+ return { encryptionKey, hmacKey, indexKey };
305
+ }
306
+
274
307
  // src/jwk.ts
275
308
  import { CipherCluster } from "@z-base/cryptosuite";
276
309
  async function encryptWithJwk(plaintext, cipherJwk) {
@@ -326,6 +359,7 @@ export {
326
359
  decryptStringWithJwk,
327
360
  decryptWithJwk,
328
361
  deriveKey,
362
+ deriveVaultKeys,
329
363
  encrypt,
330
364
  encryptString,
331
365
  encryptStringWithJwk,
@@ -333,6 +367,7 @@ export {
333
367
  formatRecoveryKey,
334
368
  generateRecoveryKey,
335
369
  getCryptoProvider,
370
+ hkdf,
336
371
  randomBytes,
337
372
  recoveryKeyToBytes,
338
373
  setCryptoProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ursalock/crypto",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "E2EE crypto primitives for ursalock",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",