@nexwage/crypto 0.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.
Files changed (38) hide show
  1. package/README.md +171 -0
  2. package/dist/crypto/file-encryption.d.ts +59 -0
  3. package/dist/crypto/file-encryption.d.ts.map +1 -0
  4. package/dist/crypto/file-encryption.js +118 -0
  5. package/dist/crypto/file-encryption.js.map +1 -0
  6. package/dist/crypto/master-key.d.ts +68 -0
  7. package/dist/crypto/master-key.d.ts.map +1 -0
  8. package/dist/crypto/master-key.js +79 -0
  9. package/dist/crypto/master-key.js.map +1 -0
  10. package/dist/crypto/password-kdf.d.ts +49 -0
  11. package/dist/crypto/password-kdf.d.ts.map +1 -0
  12. package/dist/crypto/password-kdf.js +93 -0
  13. package/dist/crypto/password-kdf.js.map +1 -0
  14. package/dist/crypto/recovery-key.d.ts +59 -0
  15. package/dist/crypto/recovery-key.d.ts.map +1 -0
  16. package/dist/crypto/recovery-key.js +121 -0
  17. package/dist/crypto/recovery-key.js.map +1 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +7 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/sodium.d.ts +115 -0
  23. package/dist/sodium.d.ts.map +1 -0
  24. package/dist/sodium.js +141 -0
  25. package/dist/sodium.js.map +1 -0
  26. package/dist/types.d.ts +18 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +2 -0
  29. package/dist/types.js.map +1 -0
  30. package/dist/utils/bytes.d.ts +12 -0
  31. package/dist/utils/bytes.d.ts.map +1 -0
  32. package/dist/utils/bytes.js +16 -0
  33. package/dist/utils/bytes.js.map +1 -0
  34. package/dist/utils/encoding.d.ts +21 -0
  35. package/dist/utils/encoding.d.ts.map +1 -0
  36. package/dist/utils/encoding.js +27 -0
  37. package/dist/utils/encoding.js.map +1 -0
  38. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # @nexwage/crypto
2
+
3
+ Minimal, opinionated crypto building blocks for password-based master keys, recovery keys, and file encryption. Built on libsodium with XChaCha20-Poly1305 and Argon2.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm i @nexwage/crypto
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import {
15
+ generateMasterKeyWithPassword,
16
+ unwrapMasterKeyWithPassword,
17
+ generateRecoveryKey,
18
+ wrapMasterKeyWithRecoveryKey,
19
+ generateFileKey,
20
+ wrapFileKeyWithMasterKey,
21
+ encryptFileData,
22
+ decryptFileData,
23
+ } from "@nexwage/crypto";
24
+
25
+ const password = "strong-password";
26
+ const aad = new TextEncoder().encode("uid:user_123|app:v1|slot:password");
27
+
28
+ const { masterKey, wrapped } = await generateMasterKeyWithPassword(
29
+ password,
30
+ undefined,
31
+ aad
32
+ );
33
+
34
+ const recoveryKey = await generateRecoveryKey();
35
+ const wrappedByRecovery = await wrapMasterKeyWithRecoveryKey(
36
+ masterKey,
37
+ recoveryKey,
38
+ aad
39
+ );
40
+
41
+ const fileKey = await generateFileKey();
42
+ const wrappedFileKey = await wrapFileKeyWithMasterKey(fileKey, masterKey, aad);
43
+
44
+ const fileBytes = new TextEncoder().encode("file rahasia");
45
+ const encryptedFile = await encryptFileData(fileKey, fileBytes, aad);
46
+
47
+ const masterKey2 = await unwrapMasterKeyWithPassword(password, wrapped, aad);
48
+ const fileKey2 = await unwrapFileKeyWithMasterKey(
49
+ masterKey2,
50
+ wrappedFileKey,
51
+ aad
52
+ );
53
+ const decryptedFile = await decryptFileData(fileKey2, encryptedFile, aad);
54
+ ```
55
+
56
+ ## Project Structure
57
+
58
+ | Path | Purpose |
59
+ | --- | --- |
60
+ | `src/index.ts` | Public exports. |
61
+ | `src/types.ts` | Payload types + versioning. |
62
+ | `src/crypto/password-kdf.ts` | Password KDF + key wrapping. |
63
+ | `src/crypto/master-key.ts` | Master key lifecycle. |
64
+ | `src/crypto/recovery-key.ts` | Recovery key wrapping. |
65
+ | `src/crypto/file-encryption.ts` | File key + file encryption. |
66
+ | `src/utils/encoding.ts` | Base64 helpers. |
67
+ | `src/utils/bytes.ts` | Byte length assertions. |
68
+ | `src/sodium.ts` | libsodium wrapper exports. |
69
+ | `examples/basic-flow.ts` | End-to-end flow. |
70
+ | `examples/master-key-example.ts` | Master key example. |
71
+
72
+ ## AAD and Versioning
73
+
74
+ - AAD (Additional Authenticated Data) binds ciphertext to context (userId, slot, fileId).
75
+ - Versioning (`v`) keeps payloads forward-compatible when formats or algorithms change.
76
+
77
+ ## Manual Test
78
+
79
+ See `examples/basic-flow.ts` for the full 3-flow example.
80
+
81
+ ## Glossary
82
+
83
+ Panduan ringkas istilah dan konsep yang dipakai di modul ini.
84
+
85
+ | Term | Meaning |
86
+ | --- | --- |
87
+ | Master Key (MK) | Kunci utama untuk membuka kunci lain (file key, recovery key). |
88
+ | Recovery Key | Kunci cadangan jika lupa password. |
89
+ | File Key | Kunci khusus per file. |
90
+ | Wrap/Unwrap | Membungkus dan membuka kunci menggunakan enkripsi. |
91
+ | AEAD | Enkripsi + integritas dalam satu skema. |
92
+ | AAD | Metadata yang diikat ke ciphertext tanpa dienkripsi. |
93
+ | KDF | Mengubah password menjadi key yang kuat. |
94
+ | Nonce | Angka acak sekali pakai untuk enkripsi. |
95
+ | Ciphertext (ct) | Hasil enkripsi. |
96
+ | Envelope | Struktur payload berisi `nonce`, `ct`, dan `v`. |
97
+ | Versioning (v) | Versi format payload. |
98
+
99
+ ## libsodium Functions Used
100
+
101
+ | Function/Property | Description |
102
+ | --- | --- |
103
+ | `sodium.ready` | Menunggu libsodium siap dipakai. |
104
+ | `sodium.randombytes_buf` | Generate bytes acak (kunci, nonce). |
105
+ | `sodium.crypto_pwhash` | Derivasi key dari password (Argon2). |
106
+ | `sodium.crypto_pwhash_SALTBYTES` | Panjang salt untuk KDF. |
107
+ | `sodium.crypto_pwhash_OPSLIMIT_MODERATE` | Default opslimit KDF. |
108
+ | `sodium.crypto_pwhash_MEMLIMIT_MODERATE` | Default memlimit KDF. |
109
+ | `sodium.crypto_aead_xchacha20poly1305_ietf_encrypt` | Enkripsi AEAD XChaCha20-Poly1305. |
110
+ | `sodium.crypto_aead_xchacha20poly1305_ietf_decrypt` | Dekripsi AEAD XChaCha20-Poly1305. |
111
+ | `sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES` | Panjang nonce AEAD XChaCha20-Poly1305. |
112
+ | `sodium.to_base64` | Encode bytes ke base64. |
113
+ | `sodium.from_base64` | Decode base64 ke bytes. |
114
+ | `sodium.base64_variants.ORIGINAL` | Variant base64 yang dipakai. |
115
+ | `sodium.from_string` | Encode string ke bytes (UTF-8). |
116
+ | `sodium.to_string` | Decode bytes ke string (UTF-8). |
117
+ | `sodium.memcmp` | Bandingkan bytes secara aman (constant-time). |
118
+
119
+ ## Planned libsodium Functions
120
+
121
+ | Function/Property | Planned use |
122
+ | --- | --- |
123
+ | `sodium.crypto_generichash` | Hash konteks/AAD jika perlu bentuk stabil dan ringkas. |
124
+ | `sodium.crypto_kdf_derive_from_key` | Derivasi sub-key per domain dari satu kunci root. |
125
+ | `sodium.crypto_aead_aes256gcm_encrypt` | Alternatif AEAD dengan akselerasi hardware. |
126
+ | `sodium.crypto_aead_aes256gcm_decrypt` | Pasangan decrypt AES-GCM. |
127
+ | `sodium.crypto_secretbox_easy` | Enkripsi simetris sederhana untuk payload non-AEAD. |
128
+ | `sodium.crypto_secretbox_open_easy` | Dekripsi secretbox. |
129
+
130
+ ## AAD Example
131
+
132
+ ```ts
133
+ import { fromString } from "@nexwage/crypto";
134
+
135
+ const aad = fromString("uid:user_123|app:v1|slot:password");
136
+ ```
137
+
138
+ ## KDF Parameters
139
+
140
+ | Parameter | Purpose |
141
+ | --- | --- |
142
+ | opslimit | Jumlah operasi KDF (lebih besar = lebih lambat, lebih tahan brute-force). |
143
+ | memlimit | Batas memori KDF (lebih besar = lebih tahan GPU/ASIC). |
144
+ | salt | Salt acak 32-byte untuk mencegah rainbow table. |
145
+
146
+ ## Envelope Format
147
+
148
+ ```json
149
+ {
150
+ "v": 1,
151
+ "nonce": "base64",
152
+ "ct": "base64"
153
+ }
154
+ ```
155
+
156
+ ## Versioning Rules
157
+
158
+ 1. Saat encrypt, isi `v` dengan versi terbaru.
159
+ 2. Saat decrypt, cek `v` lalu pilih logika yang sesuai.
160
+ 3. Jika `v` tidak didukung, tolak dekripsi atau lakukan migrasi.
161
+
162
+ Contoh payload:
163
+
164
+ ```json
165
+ {
166
+ "v": 1,
167
+ "kdf": { "v": 1, "salt": "...", "opslimit": 3, "memlimit": 268435456 },
168
+ "nonce": "....",
169
+ "ct": "...."
170
+ }
171
+ ```
@@ -0,0 +1,59 @@
1
+ import type { AeadEnvelopeV1 } from "../types.js";
2
+ /**
3
+ * Generate file key 32-byte secara acak.
4
+ *
5
+ * @returns Bytes file key.
6
+ *
7
+ * @example
8
+ * const fileKey = await generateFileKey();
9
+ */
10
+ export declare function generateFileKey(): Promise<Uint8Array>;
11
+ /**
12
+ * Bungkus file key menggunakan master key.
13
+ *
14
+ * @param fileKey - File key 32-byte.
15
+ * @param masterKey - Master key 32-byte.
16
+ * @param aad - Additional authenticated data (opsional).
17
+ * @returns Envelope hasil wrap.
18
+ *
19
+ * @example
20
+ * const wrapped = await wrapFileKeyWithMasterKey(fileKey, masterKey);
21
+ */
22
+ export declare function wrapFileKeyWithMasterKey(fileKey: Uint8Array, masterKey: Uint8Array, aad?: Uint8Array): Promise<AeadEnvelopeV1>;
23
+ /**
24
+ * Buka file key menggunakan master key.
25
+ *
26
+ * @param masterKey - Master key 32-byte.
27
+ * @param wrapped - Envelope hasil wrap file key.
28
+ * @param aad - Additional authenticated data (opsional).
29
+ * @returns Bytes file key.
30
+ *
31
+ * @example
32
+ * const fileKey = await unwrapFileKeyWithMasterKey(masterKey, wrapped);
33
+ */
34
+ export declare function unwrapFileKeyWithMasterKey(masterKey: Uint8Array, wrapped: AeadEnvelopeV1, aad?: Uint8Array): Promise<Uint8Array>;
35
+ /**
36
+ * Enkripsi isi file menggunakan file key.
37
+ *
38
+ * @param fileKey - File key 32-byte.
39
+ * @param plaintext - Isi file (bytes).
40
+ * @param aad - Additional authenticated data (opsional).
41
+ * @returns Envelope hasil enkripsi file.
42
+ *
43
+ * @example
44
+ * const encrypted = await encryptFileData(fileKey, fileBytes);
45
+ */
46
+ export declare function encryptFileData(fileKey: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Promise<AeadEnvelopeV1>;
47
+ /**
48
+ * Dekripsi isi file menggunakan file key.
49
+ *
50
+ * @param fileKey - File key 32-byte.
51
+ * @param wrapped - Envelope hasil enkripsi file.
52
+ * @param aad - Additional authenticated data (opsional).
53
+ * @returns Bytes isi file.
54
+ *
55
+ * @example
56
+ * const fileBytes = await decryptFileData(fileKey, encrypted);
57
+ */
58
+ export declare function decryptFileData(fileKey: Uint8Array, wrapped: AeadEnvelopeV1, aad?: Uint8Array): Promise<Uint8Array>;
59
+ //# sourceMappingURL=file-encryption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-encryption.d.ts","sourceRoot":"","sources":["../../src/crypto/file-encryption.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAwElD;;;;;;;GAOG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAG3D;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,UAAU,EACrB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,cAAc,CAAC,CAIzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,cAAc,EACvB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CAKrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,UAAU,EACrB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,cAAc,CAAC,CAGzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,UAAU,EACnB,OAAO,EAAE,cAAc,EACvB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CAGrB"}
@@ -0,0 +1,118 @@
1
+ import sodium from "libsodium-wrappers-sumo";
2
+ import { assertByteLength } from "../utils/bytes.js";
3
+ import { decodeBase64, encodeBase64 } from "../utils/encoding.js";
4
+ // Domain: file keys + file data encryption/decryption.
5
+ /**
6
+ * Enkripsi payload dengan key menggunakan AEAD.
7
+ *
8
+ * @param key - Key 32-byte.
9
+ * @param plaintext - Data plaintext.
10
+ * @param aad - Additional authenticated data (opsional).
11
+ * @returns Envelope hasil enkripsi.
12
+ */
13
+ async function encryptWithKey(key, plaintext, aad) {
14
+ await sodium.ready;
15
+ const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
16
+ const ct = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, aad ?? null, null, nonce, key);
17
+ return {
18
+ v: 1,
19
+ nonce: encodeBase64(nonce),
20
+ ct: encodeBase64(ct),
21
+ };
22
+ }
23
+ /**
24
+ * Dekripsi payload dengan key menggunakan AEAD.
25
+ *
26
+ * @param key - Key 32-byte.
27
+ * @param wrapped - Envelope hasil enkripsi.
28
+ * @param aad - Additional authenticated data (opsional).
29
+ * @returns Bytes hasil dekripsi.
30
+ */
31
+ async function decryptWithKey(key, wrapped, aad) {
32
+ await sodium.ready;
33
+ const nonce = decodeBase64(wrapped.nonce);
34
+ assertByteLength("nonce", nonce, sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
35
+ const ct = decodeBase64(wrapped.ct);
36
+ try {
37
+ return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ct, aad ?? null, nonce, key);
38
+ }
39
+ catch {
40
+ throw new Error("Gagal membuka file: ciphertext/AAD/keys tidak valid");
41
+ }
42
+ }
43
+ /**
44
+ * Generate file key 32-byte secara acak.
45
+ *
46
+ * @returns Bytes file key.
47
+ *
48
+ * @example
49
+ * const fileKey = await generateFileKey();
50
+ */
51
+ export async function generateFileKey() {
52
+ await sodium.ready;
53
+ return sodium.randombytes_buf(32);
54
+ }
55
+ /**
56
+ * Bungkus file key menggunakan master key.
57
+ *
58
+ * @param fileKey - File key 32-byte.
59
+ * @param masterKey - Master key 32-byte.
60
+ * @param aad - Additional authenticated data (opsional).
61
+ * @returns Envelope hasil wrap.
62
+ *
63
+ * @example
64
+ * const wrapped = await wrapFileKeyWithMasterKey(fileKey, masterKey);
65
+ */
66
+ export async function wrapFileKeyWithMasterKey(fileKey, masterKey, aad) {
67
+ assertByteLength("fileKey", fileKey, 32);
68
+ assertByteLength("masterKey", masterKey, 32);
69
+ return encryptWithKey(masterKey, fileKey, aad);
70
+ }
71
+ /**
72
+ * Buka file key menggunakan master key.
73
+ *
74
+ * @param masterKey - Master key 32-byte.
75
+ * @param wrapped - Envelope hasil wrap file key.
76
+ * @param aad - Additional authenticated data (opsional).
77
+ * @returns Bytes file key.
78
+ *
79
+ * @example
80
+ * const fileKey = await unwrapFileKeyWithMasterKey(masterKey, wrapped);
81
+ */
82
+ export async function unwrapFileKeyWithMasterKey(masterKey, wrapped, aad) {
83
+ assertByteLength("masterKey", masterKey, 32);
84
+ const fileKey = await decryptWithKey(masterKey, wrapped, aad);
85
+ assertByteLength("fileKey", fileKey, 32);
86
+ return fileKey;
87
+ }
88
+ /**
89
+ * Enkripsi isi file menggunakan file key.
90
+ *
91
+ * @param fileKey - File key 32-byte.
92
+ * @param plaintext - Isi file (bytes).
93
+ * @param aad - Additional authenticated data (opsional).
94
+ * @returns Envelope hasil enkripsi file.
95
+ *
96
+ * @example
97
+ * const encrypted = await encryptFileData(fileKey, fileBytes);
98
+ */
99
+ export async function encryptFileData(fileKey, plaintext, aad) {
100
+ assertByteLength("fileKey", fileKey, 32);
101
+ return encryptWithKey(fileKey, plaintext, aad);
102
+ }
103
+ /**
104
+ * Dekripsi isi file menggunakan file key.
105
+ *
106
+ * @param fileKey - File key 32-byte.
107
+ * @param wrapped - Envelope hasil enkripsi file.
108
+ * @param aad - Additional authenticated data (opsional).
109
+ * @returns Bytes isi file.
110
+ *
111
+ * @example
112
+ * const fileBytes = await decryptFileData(fileKey, encrypted);
113
+ */
114
+ export async function decryptFileData(fileKey, wrapped, aad) {
115
+ assertByteLength("fileKey", fileKey, 32);
116
+ return decryptWithKey(fileKey, wrapped, aad);
117
+ }
118
+ //# sourceMappingURL=file-encryption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-encryption.js","sourceRoot":"","sources":["../../src/crypto/file-encryption.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { PasswordWrappedKeyV1 } from "../types.js";
2
+ import type { PasswordKdfOptions } from "./password-kdf.js";
3
+ /**
4
+ * Generate master key 32-byte secara acak.
5
+ *
6
+ * @returns Bytes master key.
7
+ *
8
+ * @example
9
+ * const masterKey = await generateMasterKey();
10
+ */
11
+ export declare function generateMasterKey(): Promise<Uint8Array>;
12
+ /**
13
+ * Generate master key baru lalu bungkus (wrap) dengan password.
14
+ *
15
+ * @param password - String password pengguna yang dipakai untuk KDF.
16
+ * @param opts - Opsi KDF.
17
+ * @param opts.opslimit - Jumlah operasi KDF (Argon2). Semakin besar -> semakin lama proses derivasi,
18
+ * lebih tahan brute-force.
19
+ * @param opts.memlimit - Batas memori KDF. Semakin besar -> lebih sulit diserang GPU/ASIC,
20
+ * tapi lebih berat di device.
21
+ * @param opts.salt - Salt KDF (32 bytes). Jika tidak diberikan, dibuat acak otomatis.
22
+ * @param aad - Data tambahan yang diikat ke ciphertext (integritas) tapi tidak dienkripsi.
23
+ * Contoh: userId, appVersion, slot.
24
+ * @returns Object berisi `masterKey` dan `wrapped`.
25
+ *
26
+ * Catatan: `nonce` untuk encrypt dibuat otomatis di wrapKeyWithPassword, tidak diisi manual.
27
+ *
28
+ * @example
29
+ * const aad = sodium.from_string("uid:user_123|app:v1|slot:password");
30
+ * const { masterKey, wrapped } = await generateMasterKeyWithPassword(
31
+ * "secret-password",
32
+ * {
33
+ * opslimit: sodium.crypto_pwhash_OPSLIMIT_MODERATE,
34
+ * memlimit: sodium.crypto_pwhash_MEMLIMIT_MODERATE,
35
+ * },
36
+ * aad
37
+ * );
38
+ */
39
+ export declare function generateMasterKeyWithPassword(password: string, opts?: PasswordKdfOptions, aad?: Uint8Array): Promise<{
40
+ masterKey: Uint8Array;
41
+ wrapped: PasswordWrappedKeyV1;
42
+ }>;
43
+ /**
44
+ * Bungkus master key menggunakan key turunan dari password.
45
+ *
46
+ * @param masterKey - Master key 32-byte.
47
+ * @param password - Password pengguna.
48
+ * @param opts - Opsi KDF.
49
+ * @param aad - Additional authenticated data (opsional).
50
+ * @returns Payload master key yang dibungkus.
51
+ *
52
+ * @example
53
+ * const wrapped = await wrapMasterKeyWithPassword(masterKey, "secret");
54
+ */
55
+ export declare function wrapMasterKeyWithPassword(masterKey: Uint8Array, password: string, opts?: PasswordKdfOptions, aad?: Uint8Array): Promise<PasswordWrappedKeyV1>;
56
+ /**
57
+ * Buka master key menggunakan key turunan dari password.
58
+ *
59
+ * @param password - Password pengguna.
60
+ * @param wrapped - Payload master key yang dibungkus.
61
+ * @param aad - Additional authenticated data (opsional).
62
+ * @returns Bytes master key.
63
+ *
64
+ * @example
65
+ * const masterKey = await unwrapMasterKeyWithPassword("secret", wrapped);
66
+ */
67
+ export declare function unwrapMasterKeyWithPassword(password: string, wrapped: PasswordWrappedKeyV1, aad?: Uint8Array): Promise<Uint8Array>;
68
+ //# sourceMappingURL=master-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"master-key.d.ts","sourceRoot":"","sources":["../../src/crypto/master-key.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAI5D;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,UAAU,CAAC,CAG7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,kBAAkB,EACzB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC;IAAE,SAAS,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,CAAC,CASnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,kBAAkB,EACzB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,oBAAoB,EAC7B,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CAErB"}
@@ -0,0 +1,79 @@
1
+ import sodium from "libsodium-wrappers-sumo";
2
+ import { assertByteLength } from "../utils/bytes.js";
3
+ import { unwrapKeyWithPassword, wrapKeyWithPassword } from "./password-kdf.js";
4
+ // Domain: master key lifecycle (generate/derive/wrap/unwrap).
5
+ /**
6
+ * Generate master key 32-byte secara acak.
7
+ *
8
+ * @returns Bytes master key.
9
+ *
10
+ * @example
11
+ * const masterKey = await generateMasterKey();
12
+ */
13
+ export async function generateMasterKey() {
14
+ await sodium.ready;
15
+ return sodium.randombytes_buf(32);
16
+ }
17
+ /**
18
+ * Generate master key baru lalu bungkus (wrap) dengan password.
19
+ *
20
+ * @param password - String password pengguna yang dipakai untuk KDF.
21
+ * @param opts - Opsi KDF.
22
+ * @param opts.opslimit - Jumlah operasi KDF (Argon2). Semakin besar -> semakin lama proses derivasi,
23
+ * lebih tahan brute-force.
24
+ * @param opts.memlimit - Batas memori KDF. Semakin besar -> lebih sulit diserang GPU/ASIC,
25
+ * tapi lebih berat di device.
26
+ * @param opts.salt - Salt KDF (32 bytes). Jika tidak diberikan, dibuat acak otomatis.
27
+ * @param aad - Data tambahan yang diikat ke ciphertext (integritas) tapi tidak dienkripsi.
28
+ * Contoh: userId, appVersion, slot.
29
+ * @returns Object berisi `masterKey` dan `wrapped`.
30
+ *
31
+ * Catatan: `nonce` untuk encrypt dibuat otomatis di wrapKeyWithPassword, tidak diisi manual.
32
+ *
33
+ * @example
34
+ * const aad = sodium.from_string("uid:user_123|app:v1|slot:password");
35
+ * const { masterKey, wrapped } = await generateMasterKeyWithPassword(
36
+ * "secret-password",
37
+ * {
38
+ * opslimit: sodium.crypto_pwhash_OPSLIMIT_MODERATE,
39
+ * memlimit: sodium.crypto_pwhash_MEMLIMIT_MODERATE,
40
+ * },
41
+ * aad
42
+ * );
43
+ */
44
+ export async function generateMasterKeyWithPassword(password, opts, aad) {
45
+ const masterKey = await generateMasterKey();
46
+ const wrapped = await wrapMasterKeyWithPassword(masterKey, password, opts, aad);
47
+ return { masterKey, wrapped };
48
+ }
49
+ /**
50
+ * Bungkus master key menggunakan key turunan dari password.
51
+ *
52
+ * @param masterKey - Master key 32-byte.
53
+ * @param password - Password pengguna.
54
+ * @param opts - Opsi KDF.
55
+ * @param aad - Additional authenticated data (opsional).
56
+ * @returns Payload master key yang dibungkus.
57
+ *
58
+ * @example
59
+ * const wrapped = await wrapMasterKeyWithPassword(masterKey, "secret");
60
+ */
61
+ export async function wrapMasterKeyWithPassword(masterKey, password, opts, aad) {
62
+ assertByteLength("masterKey", masterKey, 32);
63
+ return wrapKeyWithPassword(masterKey, password, opts, aad);
64
+ }
65
+ /**
66
+ * Buka master key menggunakan key turunan dari password.
67
+ *
68
+ * @param password - Password pengguna.
69
+ * @param wrapped - Payload master key yang dibungkus.
70
+ * @param aad - Additional authenticated data (opsional).
71
+ * @returns Bytes master key.
72
+ *
73
+ * @example
74
+ * const masterKey = await unwrapMasterKeyWithPassword("secret", wrapped);
75
+ */
76
+ export async function unwrapMasterKeyWithPassword(password, wrapped, aad) {
77
+ return unwrapKeyWithPassword(password, wrapped, aad);
78
+ }
79
+ //# sourceMappingURL=master-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"master-key.js","sourceRoot":"","sources":["../../src/crypto/master-key.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,yBAAyB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE/E,8DAA8D;AAC9D;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,MAAM,CAAC,KAAK,CAAC;IACnB,OAAO,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,QAAgB,EAChB,IAAyB,EACzB,GAAgB;IAEhB,MAAM,SAAS,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,yBAAyB,CAC7C,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,GAAG,CACJ,CAAC;IACF,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,SAAqB,EACrB,QAAgB,EAChB,IAAyB,EACzB,GAAgB;IAEhB,gBAAgB,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IAC7C,OAAO,mBAAmB,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,QAAgB,EAChB,OAA6B,EAC7B,GAAgB;IAEhB,OAAO,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,49 @@
1
+ import type { PasswordKdfParamsV1, PasswordWrappedKeyV1 } from "../types.js";
2
+ export interface PasswordKdfOptions {
3
+ opslimit?: number;
4
+ memlimit?: number;
5
+ salt?: Uint8Array;
6
+ }
7
+ /**
8
+ * Derivasi key 32-byte dari password menggunakan Argon2.
9
+ *
10
+ * @param password - Password pengguna.
11
+ * @param opts - Opsi KDF.
12
+ * @param opts.opslimit - Batas operasi KDF.
13
+ * @param opts.memlimit - Batas memori KDF.
14
+ * @param opts.salt - Salt KDF (32 bytes). Random jika tidak diisi.
15
+ * @returns Key hasil derivasi dan parameter KDF untuk disimpan.
16
+ *
17
+ * @example
18
+ * const { key, kdf } = await deriveKeyFromPassword("secret");
19
+ */
20
+ export declare function deriveKeyFromPassword(password: string, opts?: PasswordKdfOptions): Promise<{
21
+ key: Uint8Array;
22
+ kdf: PasswordKdfParamsV1;
23
+ }>;
24
+ /**
25
+ * Bungkus (wrap) key menggunakan key turunan dari password.
26
+ *
27
+ * @param key - Key 32-byte yang akan dibungkus.
28
+ * @param password - Password pengguna.
29
+ * @param opts - Opsi KDF.
30
+ * @param aad - Additional authenticated data (opsional).
31
+ * @returns Payload key yang sudah dibungkus.
32
+ *
33
+ * @example
34
+ * const wrapped = await wrapKeyWithPassword(key, "secret");
35
+ */
36
+ export declare function wrapKeyWithPassword(key: Uint8Array, password: string, opts?: PasswordKdfOptions, aad?: Uint8Array): Promise<PasswordWrappedKeyV1>;
37
+ /**
38
+ * Buka (unwrap) key menggunakan key turunan dari password.
39
+ *
40
+ * @param password - Password pengguna.
41
+ * @param wrapped - Payload key yang dibungkus.
42
+ * @param aad - Additional authenticated data (opsional).
43
+ * @returns Bytes key hasil unwrap.
44
+ *
45
+ * @example
46
+ * const key = await unwrapKeyWithPassword("secret", wrapped);
47
+ */
48
+ export declare function unwrapKeyWithPassword(password: string, wrapped: PasswordWrappedKeyV1, aad?: Uint8Array): Promise<Uint8Array>;
49
+ //# sourceMappingURL=password-kdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-kdf.d.ts","sourceRoot":"","sources":["../../src/crypto/password-kdf.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAG7E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC;IAAE,GAAG,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,mBAAmB,CAAA;CAAE,CAAC,CA4BxD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,kBAAkB,EACzB,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,oBAAoB,CAAC,CAyB/B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,oBAAoB,EAC7B,GAAG,CAAC,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CAkCrB"}
@@ -0,0 +1,93 @@
1
+ import sodium from "libsodium-wrappers-sumo";
2
+ import { assertByteLength } from "../utils/bytes.js";
3
+ import { decodeBase64, encodeBase64 } from "../utils/encoding.js";
4
+ /**
5
+ * Derivasi key 32-byte dari password menggunakan Argon2.
6
+ *
7
+ * @param password - Password pengguna.
8
+ * @param opts - Opsi KDF.
9
+ * @param opts.opslimit - Batas operasi KDF.
10
+ * @param opts.memlimit - Batas memori KDF.
11
+ * @param opts.salt - Salt KDF (32 bytes). Random jika tidak diisi.
12
+ * @returns Key hasil derivasi dan parameter KDF untuk disimpan.
13
+ *
14
+ * @example
15
+ * const { key, kdf } = await deriveKeyFromPassword("secret");
16
+ */
17
+ export async function deriveKeyFromPassword(password, opts) {
18
+ await sodium.ready;
19
+ const opslimit = opts?.opslimit ?? sodium.crypto_pwhash_OPSLIMIT_MODERATE;
20
+ const memlimit = opts?.memlimit ?? sodium.crypto_pwhash_MEMLIMIT_MODERATE;
21
+ const salt = opts?.salt ?? sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
22
+ assertByteLength("salt", salt, sodium.crypto_pwhash_SALTBYTES);
23
+ const key = sodium.crypto_pwhash(32, password, salt, opslimit, memlimit, sodium.crypto_pwhash_ALG_DEFAULT);
24
+ return {
25
+ key,
26
+ kdf: {
27
+ v: 1,
28
+ salt: encodeBase64(salt),
29
+ opslimit,
30
+ memlimit,
31
+ },
32
+ };
33
+ }
34
+ /**
35
+ * Bungkus (wrap) key menggunakan key turunan dari password.
36
+ *
37
+ * @param key - Key 32-byte yang akan dibungkus.
38
+ * @param password - Password pengguna.
39
+ * @param opts - Opsi KDF.
40
+ * @param aad - Additional authenticated data (opsional).
41
+ * @returns Payload key yang sudah dibungkus.
42
+ *
43
+ * @example
44
+ * const wrapped = await wrapKeyWithPassword(key, "secret");
45
+ */
46
+ export async function wrapKeyWithPassword(key, password, opts, aad) {
47
+ await sodium.ready;
48
+ assertByteLength("key", key, 32);
49
+ const { key: wrapKey, kdf } = await deriveKeyFromPassword(password, opts);
50
+ const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
51
+ const ct = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(key, aad ?? null, null, nonce, wrapKey);
52
+ sodium.memzero(wrapKey);
53
+ return {
54
+ v: 1,
55
+ kdf,
56
+ nonce: encodeBase64(nonce),
57
+ ct: encodeBase64(ct),
58
+ };
59
+ }
60
+ /**
61
+ * Buka (unwrap) key menggunakan key turunan dari password.
62
+ *
63
+ * @param password - Password pengguna.
64
+ * @param wrapped - Payload key yang dibungkus.
65
+ * @param aad - Additional authenticated data (opsional).
66
+ * @returns Bytes key hasil unwrap.
67
+ *
68
+ * @example
69
+ * const key = await unwrapKeyWithPassword("secret", wrapped);
70
+ */
71
+ export async function unwrapKeyWithPassword(password, wrapped, aad) {
72
+ await sodium.ready;
73
+ const salt = decodeBase64(wrapped.kdf.salt);
74
+ assertByteLength("salt", salt, sodium.crypto_pwhash_SALTBYTES);
75
+ const { key: wrapKey } = await deriveKeyFromPassword(password, {
76
+ salt,
77
+ opslimit: wrapped.kdf.opslimit,
78
+ memlimit: wrapped.kdf.memlimit,
79
+ });
80
+ const nonce = decodeBase64(wrapped.nonce);
81
+ assertByteLength("nonce", nonce, sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
82
+ const ct = decodeBase64(wrapped.ct);
83
+ try {
84
+ return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ct, aad ?? null, nonce, wrapKey);
85
+ }
86
+ catch {
87
+ throw new Error("Gagal membuka key: ciphertext/AAD/keys tidak valid");
88
+ }
89
+ finally {
90
+ sodium.memzero(wrapKey);
91
+ }
92
+ }
93
+ //# sourceMappingURL=password-kdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-kdf.js","sourceRoot":"","sources":["../../src/crypto/password-kdf.ts"],"names":[],"mappings":"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"}