@alikhalilll/nuxt-crypto 1.2.1 → 1.3.1
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 +11 -2
- package/dist/core/index.d.ts +20 -15
- package/dist/core/index.mjs +3 -25
- package/dist/create-service-CgBxeMb0.mjs +179 -0
- package/dist/create-service-CgBxeMb0.mjs.map +1 -0
- package/dist/index-DkMNygRG.d.ts +30 -0
- package/dist/module.d.ts +7 -5
- package/dist/module.mjs +61 -64
- package/dist/module.mjs.map +1 -1
- package/dist/payload-CvzYMicC.d.ts +111 -0
- package/dist/runtime/plugin.d.ts +5 -0
- package/dist/runtime/plugin.mjs +13 -25
- package/dist/runtime/plugin.mjs.map +1 -1
- package/dist/server/index.d.ts +25 -23
- package/dist/server/index.mjs +67 -36
- package/dist/server/index.mjs.map +1 -1
- package/dist/subtle-Dh8m0gIA.mjs +51 -0
- package/dist/subtle-Dh8m0gIA.mjs.map +1 -0
- package/dist/types/index.d.ts +3 -30
- package/dist/types/index.mjs +0 -2
- package/logo-dark.png +0 -0
- package/logo-light.png +0 -0
- package/logo.svg +23 -0
- package/package.json +12 -5
- package/dist/chunk-EFZPSZWO.mjs +0 -1
- package/dist/chunk-EFZPSZWO.mjs.map +0 -1
- package/dist/chunk-FKHKOTFR.mjs +0 -160
- package/dist/chunk-FKHKOTFR.mjs.map +0 -1
- package/dist/chunk-FKXZ7VYW.mjs +0 -50
- package/dist/chunk-FKXZ7VYW.mjs.map +0 -1
- package/dist/core/index.mjs.map +0 -1
- package/dist/payload-BOueSWcI.d.ts +0 -108
- package/dist/types/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alikhalilll/ali-nuxt-toolkit/master/packages/crypto/logo-dark.png">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/alikhalilll/ali-nuxt-toolkit/master/packages/crypto/logo-light.png" alt="nuxt-crypto" width="96" height="96">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
<h1 align="center">@alikhalilll/nuxt-crypto</h1>
|
|
9
|
+
|
|
10
|
+
<p align="center">Symmetric encryption for Nuxt 3 / 4 built on the native <strong>Web Crypto API</strong>. Defaults to <strong>AES-256-GCM</strong> with <strong>PBKDF2-SHA256</strong> key derivation.</p>
|
|
11
|
+
|
|
12
|
+
---
|
|
4
13
|
|
|
5
14
|
- **Framework-agnostic core** — `createCryptoService()` works anywhere `SubtleCrypto` exists (browser, Deno, Bun, Node 20+).
|
|
6
15
|
- **Key caching** — derived keys are cached per salt, so bulk decrypt stays fast even at 100k+ PBKDF2 iterations.
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as CryptoServiceConfig,
|
|
2
|
-
export { a as CryptoOperationOptions, P as ParsedPayload, e as encodePayload, p as parsePayload } from '../payload-BOueSWcI.js';
|
|
1
|
+
import { a as CryptoAlgorithm, c as CryptoServiceConfig, i as Bytes, n as encodePayload, o as CryptoOperationOptions, r as parsePayload, s as CryptoService, t as ParsedPayload } from "../payload-CvzYMicC.js";
|
|
3
2
|
|
|
3
|
+
//#region src/core/create-service.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Create a framework-agnostic `CryptoService` from a passphrase.
|
|
6
6
|
*
|
|
@@ -9,13 +9,15 @@ export { a as CryptoOperationOptions, P as ParsedPayload, e as encodePayload, p
|
|
|
9
9
|
* versioned payload envelope.
|
|
10
10
|
*/
|
|
11
11
|
declare function createCryptoService(config: CryptoServiceConfig): Promise<CryptoService>;
|
|
12
|
-
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/core/algorithms/aes-gcm.d.ts
|
|
13
14
|
/**
|
|
14
15
|
* AES-256-GCM with PBKDF2-SHA256 key derivation. This is the default and
|
|
15
16
|
* currently the only built-in algorithm; pluggable via `CryptoServiceConfig.algorithm`.
|
|
16
17
|
*/
|
|
17
18
|
declare const aesGcmPbkdf2: CryptoAlgorithm;
|
|
18
|
-
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/core/subtle.d.ts
|
|
19
21
|
/**
|
|
20
22
|
* Resolve a SubtleCrypto implementation across environments:
|
|
21
23
|
*
|
|
@@ -25,12 +27,14 @@ declare const aesGcmPbkdf2: CryptoAlgorithm;
|
|
|
25
27
|
declare function getSubtle(): Promise<SubtleCrypto>;
|
|
26
28
|
/** Fill the given buffer with cryptographically strong random bytes. */
|
|
27
29
|
declare function getRandomBytes(length: number): Uint8Array<ArrayBuffer>;
|
|
28
|
-
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/core/base64.d.ts
|
|
29
32
|
/** Encode bytes to standard base64. Works in browser and Node. */
|
|
30
33
|
declare function toBase64(data: Bytes): string;
|
|
31
34
|
/** Decode a base64 string back to bytes. Works in browser and Node. */
|
|
32
35
|
declare function fromBase64(input: string): Bytes;
|
|
33
|
-
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/core/key-cache.d.ts
|
|
34
38
|
/**
|
|
35
39
|
* A tiny LRU-ish cache for derived `CryptoKey` values, keyed on the salt.
|
|
36
40
|
* When capacity is exceeded the oldest entry is evicted. Derived key
|
|
@@ -38,13 +42,14 @@ declare function fromBase64(input: string): Bytes;
|
|
|
38
42
|
* a big impact when decrypting many payloads in a row.
|
|
39
43
|
*/
|
|
40
44
|
declare class KeyCache {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
private readonly capacity;
|
|
46
|
+
private readonly map;
|
|
47
|
+
constructor(capacity: number);
|
|
48
|
+
static key(salt: Bytes, iterations: number, fingerprint?: string): string;
|
|
49
|
+
get(key: string): Promise<CryptoKey> | undefined;
|
|
50
|
+
set(key: string, value: Promise<CryptoKey>): void;
|
|
51
|
+
clear(): void;
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
export { Bytes, CryptoAlgorithm, CryptoService, CryptoServiceConfig, KeyCache, aesGcmPbkdf2, createCryptoService, fromBase64, getRandomBytes, getSubtle, toBase64 };
|
|
53
|
+
//#endregion
|
|
54
|
+
export { type Bytes, type CryptoAlgorithm, type CryptoOperationOptions, type CryptoService, type CryptoServiceConfig, KeyCache, type ParsedPayload, aesGcmPbkdf2, createCryptoService, encodePayload, fromBase64, getRandomBytes, getSubtle, parsePayload, toBase64 };
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,25 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
createCryptoService,
|
|
5
|
-
encodePayload,
|
|
6
|
-
parsePayload
|
|
7
|
-
} from "../chunk-FKHKOTFR.mjs";
|
|
8
|
-
import {
|
|
9
|
-
fromBase64,
|
|
10
|
-
getRandomBytes,
|
|
11
|
-
getSubtle,
|
|
12
|
-
toBase64
|
|
13
|
-
} from "../chunk-FKXZ7VYW.mjs";
|
|
14
|
-
export {
|
|
15
|
-
KeyCache,
|
|
16
|
-
aesGcmPbkdf2,
|
|
17
|
-
createCryptoService,
|
|
18
|
-
encodePayload,
|
|
19
|
-
fromBase64,
|
|
20
|
-
getRandomBytes,
|
|
21
|
-
getSubtle,
|
|
22
|
-
parsePayload,
|
|
23
|
-
toBase64
|
|
24
|
-
};
|
|
25
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import { a as aesGcmPbkdf2, i as KeyCache, n as encodePayload, r as parsePayload, t as createCryptoService } from "../create-service-CgBxeMb0.mjs";
|
|
2
|
+
import { i as toBase64, n as getSubtle, r as fromBase64, t as getRandomBytes } from "../subtle-Dh8m0gIA.mjs";
|
|
3
|
+
export { KeyCache, aesGcmPbkdf2, createCryptoService, encodePayload, fromBase64, getRandomBytes, getSubtle, parsePayload, toBase64 };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { i as toBase64, n as getSubtle, r as fromBase64, t as getRandomBytes } from "./subtle-Dh8m0gIA.mjs";
|
|
2
|
+
//#region src/core/algorithms/aes-gcm.ts
|
|
3
|
+
/**
|
|
4
|
+
* AES-256-GCM with PBKDF2-SHA256 key derivation. This is the default and
|
|
5
|
+
* currently the only built-in algorithm; pluggable via `CryptoServiceConfig.algorithm`.
|
|
6
|
+
*/
|
|
7
|
+
const aesGcmPbkdf2 = {
|
|
8
|
+
version: "v1",
|
|
9
|
+
async deriveKey({ subtle, passphrase, fingerprint, salt, iterations }) {
|
|
10
|
+
const material = fingerprint ? `${passphrase}\x00${fingerprint}` : passphrase;
|
|
11
|
+
const keyMaterial = await subtle.importKey("raw", new TextEncoder().encode(material), { name: "PBKDF2" }, false, ["deriveKey"]);
|
|
12
|
+
return subtle.deriveKey({
|
|
13
|
+
name: "PBKDF2",
|
|
14
|
+
salt,
|
|
15
|
+
iterations,
|
|
16
|
+
hash: "SHA-256"
|
|
17
|
+
}, keyMaterial, {
|
|
18
|
+
name: "AES-GCM",
|
|
19
|
+
length: 256
|
|
20
|
+
}, false, ["encrypt", "decrypt"]);
|
|
21
|
+
},
|
|
22
|
+
async encrypt({ subtle, key, plainText }) {
|
|
23
|
+
const iv = new Uint8Array(/* @__PURE__ */ new ArrayBuffer(12));
|
|
24
|
+
globalThis.crypto.getRandomValues(iv);
|
|
25
|
+
const buf = await subtle.encrypt({
|
|
26
|
+
name: "AES-GCM",
|
|
27
|
+
iv
|
|
28
|
+
}, key, plainText);
|
|
29
|
+
const cipher = new Uint8Array(new ArrayBuffer(buf.byteLength));
|
|
30
|
+
cipher.set(new Uint8Array(buf));
|
|
31
|
+
return {
|
|
32
|
+
cipher,
|
|
33
|
+
iv
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
async decrypt({ subtle, key, cipher, iv }) {
|
|
37
|
+
const buf = await subtle.decrypt({
|
|
38
|
+
name: "AES-GCM",
|
|
39
|
+
iv
|
|
40
|
+
}, key, cipher);
|
|
41
|
+
const out = new Uint8Array(new ArrayBuffer(buf.byteLength));
|
|
42
|
+
out.set(new Uint8Array(buf));
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/core/key-cache.ts
|
|
48
|
+
/**
|
|
49
|
+
* A tiny LRU-ish cache for derived `CryptoKey` values, keyed on the salt.
|
|
50
|
+
* When capacity is exceeded the oldest entry is evicted. Derived key
|
|
51
|
+
* material is expensive (PBKDF2 is designed to be slow), so caching has
|
|
52
|
+
* a big impact when decrypting many payloads in a row.
|
|
53
|
+
*/
|
|
54
|
+
var KeyCache = class {
|
|
55
|
+
capacity;
|
|
56
|
+
map = /* @__PURE__ */ new Map();
|
|
57
|
+
constructor(capacity) {
|
|
58
|
+
this.capacity = capacity;
|
|
59
|
+
}
|
|
60
|
+
static key(salt, iterations, fingerprint) {
|
|
61
|
+
return `${iterations}.${fingerprint ?? ""}.${toBase64(salt)}`;
|
|
62
|
+
}
|
|
63
|
+
get(key) {
|
|
64
|
+
const value = this.map.get(key);
|
|
65
|
+
if (!value) return void 0;
|
|
66
|
+
this.map.delete(key);
|
|
67
|
+
this.map.set(key, value);
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
set(key, value) {
|
|
71
|
+
if (this.capacity <= 0) return;
|
|
72
|
+
this.map.set(key, value);
|
|
73
|
+
while (this.map.size > this.capacity) {
|
|
74
|
+
const oldest = this.map.keys().next().value;
|
|
75
|
+
if (oldest === void 0) break;
|
|
76
|
+
this.map.delete(oldest);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
clear() {
|
|
80
|
+
this.map.clear();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/core/payload.ts
|
|
85
|
+
function encodePayload(parts) {
|
|
86
|
+
return [
|
|
87
|
+
parts.version,
|
|
88
|
+
toBase64(parts.salt),
|
|
89
|
+
toBase64(parts.iv),
|
|
90
|
+
toBase64(parts.cipher)
|
|
91
|
+
].join(".");
|
|
92
|
+
}
|
|
93
|
+
function parsePayload(payload) {
|
|
94
|
+
const segments = payload.split(".");
|
|
95
|
+
if (segments.length !== 4) throw new Error("[nuxt-crypto] Invalid payload format — expected 4 dot-separated segments.");
|
|
96
|
+
const [version, saltB64, ivB64, cipherB64] = segments;
|
|
97
|
+
if (!version || !saltB64 || !ivB64 || !cipherB64) throw new Error("[nuxt-crypto] Invalid payload format — one or more segments were empty.");
|
|
98
|
+
return {
|
|
99
|
+
version,
|
|
100
|
+
salt: fromBase64(saltB64),
|
|
101
|
+
iv: fromBase64(ivB64),
|
|
102
|
+
cipher: fromBase64(cipherB64)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/core/create-service.ts
|
|
107
|
+
const DEFAULT_ITERATIONS = 1e5;
|
|
108
|
+
const DEFAULT_CACHE = 64;
|
|
109
|
+
const SALT_BYTES = 16;
|
|
110
|
+
/**
|
|
111
|
+
* Create a framework-agnostic `CryptoService` from a passphrase.
|
|
112
|
+
*
|
|
113
|
+
* Uses AES-256-GCM with PBKDF2-SHA256 (100k iterations by default). Pass a
|
|
114
|
+
* different `algorithm` to plug in another cipher while preserving the same
|
|
115
|
+
* versioned payload envelope.
|
|
116
|
+
*/
|
|
117
|
+
async function createCryptoService(config) {
|
|
118
|
+
if (!config.passphrase) throw new Error("[nuxt-crypto] passphrase is required.");
|
|
119
|
+
const iterations = config.iterations ?? DEFAULT_ITERATIONS;
|
|
120
|
+
const algorithm = config.algorithm ?? aesGcmPbkdf2;
|
|
121
|
+
const subtle = config.subtle ?? await getSubtle();
|
|
122
|
+
const cache = new KeyCache(config.keyCacheSize ?? DEFAULT_CACHE);
|
|
123
|
+
const passphrase = String(config.passphrase).trim();
|
|
124
|
+
const encoder = new TextEncoder();
|
|
125
|
+
const decoder = new TextDecoder();
|
|
126
|
+
async function getDerivedKey(salt, fingerprint) {
|
|
127
|
+
const key = KeyCache.key(salt, iterations, fingerprint);
|
|
128
|
+
const cached = cache.get(key);
|
|
129
|
+
if (cached) return cached;
|
|
130
|
+
const pending = algorithm.deriveKey({
|
|
131
|
+
subtle,
|
|
132
|
+
passphrase,
|
|
133
|
+
fingerprint,
|
|
134
|
+
salt,
|
|
135
|
+
iterations
|
|
136
|
+
});
|
|
137
|
+
cache.set(key, pending);
|
|
138
|
+
return pending;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
async encrypt(plainText, options) {
|
|
142
|
+
const salt = getRandomBytes(SALT_BYTES);
|
|
143
|
+
const key = await getDerivedKey(salt, options?.fingerprint);
|
|
144
|
+
const encoded = encoder.encode(plainText);
|
|
145
|
+
const plainBytes = new Uint8Array(new ArrayBuffer(encoded.byteLength));
|
|
146
|
+
plainBytes.set(encoded);
|
|
147
|
+
const { cipher, iv } = await algorithm.encrypt({
|
|
148
|
+
subtle,
|
|
149
|
+
key,
|
|
150
|
+
plainText: plainBytes
|
|
151
|
+
});
|
|
152
|
+
return encodePayload({
|
|
153
|
+
version: algorithm.version,
|
|
154
|
+
salt,
|
|
155
|
+
iv,
|
|
156
|
+
cipher
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
async decrypt(payload, options) {
|
|
160
|
+
const parsed = parsePayload(payload);
|
|
161
|
+
if (parsed.version !== algorithm.version) throw new Error(`[nuxt-crypto] Unsupported payload version: ${parsed.version} (algorithm expects ${algorithm.version}).`);
|
|
162
|
+
const key = await getDerivedKey(parsed.salt, options?.fingerprint);
|
|
163
|
+
const plainBytes = await algorithm.decrypt({
|
|
164
|
+
subtle,
|
|
165
|
+
key,
|
|
166
|
+
cipher: parsed.cipher,
|
|
167
|
+
iv: parsed.iv
|
|
168
|
+
});
|
|
169
|
+
return decoder.decode(plainBytes);
|
|
170
|
+
},
|
|
171
|
+
clearKeyCache() {
|
|
172
|
+
cache.clear();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
export { aesGcmPbkdf2 as a, KeyCache as i, encodePayload as n, parsePayload as r, createCryptoService as t };
|
|
178
|
+
|
|
179
|
+
//# sourceMappingURL=create-service-CgBxeMb0.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-service-CgBxeMb0.mjs","names":[],"sources":["../src/core/algorithms/aes-gcm.ts","../src/core/key-cache.ts","../src/core/payload.ts","../src/core/create-service.ts"],"sourcesContent":["import type { CryptoAlgorithm } from '../types';\n\n/**\n * AES-256-GCM with PBKDF2-SHA256 key derivation. This is the default and\n * currently the only built-in algorithm; pluggable via `CryptoServiceConfig.algorithm`.\n */\nexport const aesGcmPbkdf2: CryptoAlgorithm = {\n version: 'v1',\n\n async deriveKey({ subtle, passphrase, fingerprint, salt, iterations }) {\n // NUL separator prevents `\"ab\" + \"cd\"` and `\"abc\" + \"d\"` from colliding.\n const material = fingerprint ? `${passphrase}\\x00${fingerprint}` : passphrase;\n const keyMaterial = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(material),\n { name: 'PBKDF2' },\n false,\n ['deriveKey']\n );\n\n return subtle.deriveKey(\n { name: 'PBKDF2', salt, iterations, hash: 'SHA-256' },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt']\n );\n },\n\n async encrypt({ subtle, key, plainText }) {\n const iv = new Uint8Array(new ArrayBuffer(12));\n globalThis.crypto.getRandomValues(iv);\n const buf = await subtle.encrypt({ name: 'AES-GCM', iv }, key, plainText);\n const cipher = new Uint8Array(new ArrayBuffer(buf.byteLength));\n cipher.set(new Uint8Array(buf));\n return { cipher, iv };\n },\n\n async decrypt({ subtle, key, cipher, iv }) {\n const buf = await subtle.decrypt({ name: 'AES-GCM', iv }, key, cipher);\n const out = new Uint8Array(new ArrayBuffer(buf.byteLength));\n out.set(new Uint8Array(buf));\n return out;\n },\n};\n","import type { Bytes } from './types';\nimport { toBase64 } from './base64';\n\n/**\n * A tiny LRU-ish cache for derived `CryptoKey` values, keyed on the salt.\n * When capacity is exceeded the oldest entry is evicted. Derived key\n * material is expensive (PBKDF2 is designed to be slow), so caching has\n * a big impact when decrypting many payloads in a row.\n */\nexport class KeyCache {\n private readonly map = new Map<string, Promise<CryptoKey>>();\n constructor(private readonly capacity: number) {}\n\n static key(salt: Bytes, iterations: number, fingerprint?: string): string {\n // `.` separates the fields; iterations and base64 can't contain one,\n // so the joined key is unambiguous even when fingerprint is empty.\n return `${iterations}.${fingerprint ?? ''}.${toBase64(salt)}`;\n }\n\n get(key: string): Promise<CryptoKey> | undefined {\n const value = this.map.get(key);\n if (!value) return undefined;\n // Refresh recency by re-inserting.\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n set(key: string, value: Promise<CryptoKey>): void {\n if (this.capacity <= 0) return;\n this.map.set(key, value);\n while (this.map.size > this.capacity) {\n const oldest = this.map.keys().next().value;\n if (oldest === undefined) break;\n this.map.delete(oldest);\n }\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import { fromBase64, toBase64 } from './base64';\nimport type { Bytes } from './types';\n\n/**\n * Payload serialisation format (v1): `${version}.${saltB64}.${ivB64}.${cipherB64}`.\n *\n * Each part is URL-safe base64, all joined with `.`. The version tag is\n * always the first segment so consumers can route to the correct algorithm\n * during decrypt.\n */\nexport interface ParsedPayload {\n version: string;\n salt: Bytes;\n iv: Bytes;\n cipher: Bytes;\n}\n\nexport function encodePayload(parts: ParsedPayload): string {\n return [parts.version, toBase64(parts.salt), toBase64(parts.iv), toBase64(parts.cipher)].join(\n '.'\n );\n}\n\nexport function parsePayload(payload: string): ParsedPayload {\n const segments = payload.split('.');\n if (segments.length !== 4) {\n throw new Error('[nuxt-crypto] Invalid payload format — expected 4 dot-separated segments.');\n }\n const [version, saltB64, ivB64, cipherB64] = segments;\n if (!version || !saltB64 || !ivB64 || !cipherB64) {\n throw new Error('[nuxt-crypto] Invalid payload format — one or more segments were empty.');\n }\n return {\n version,\n salt: fromBase64(saltB64),\n iv: fromBase64(ivB64),\n cipher: fromBase64(cipherB64),\n };\n}\n","import { aesGcmPbkdf2 } from './algorithms/aes-gcm';\nimport { KeyCache } from './key-cache';\nimport { encodePayload, parsePayload } from './payload';\nimport { getRandomBytes, getSubtle } from './subtle';\nimport type { CryptoAlgorithm, CryptoService, CryptoServiceConfig } from './types';\n\nconst DEFAULT_ITERATIONS = 100_000;\nconst DEFAULT_CACHE = 64;\nconst SALT_BYTES = 16;\n\n/**\n * Create a framework-agnostic `CryptoService` from a passphrase.\n *\n * Uses AES-256-GCM with PBKDF2-SHA256 (100k iterations by default). Pass a\n * different `algorithm` to plug in another cipher while preserving the same\n * versioned payload envelope.\n */\nexport async function createCryptoService(config: CryptoServiceConfig): Promise<CryptoService> {\n if (!config.passphrase) {\n throw new Error('[nuxt-crypto] passphrase is required.');\n }\n\n const iterations = config.iterations ?? DEFAULT_ITERATIONS;\n const algorithm: CryptoAlgorithm = config.algorithm ?? aesGcmPbkdf2;\n const subtle = config.subtle ?? (await getSubtle());\n const cache = new KeyCache(config.keyCacheSize ?? DEFAULT_CACHE);\n const passphrase = String(config.passphrase).trim();\n const encoder = new TextEncoder();\n const decoder = new TextDecoder();\n\n async function getDerivedKey(\n salt: Uint8Array<ArrayBuffer>,\n fingerprint?: string\n ): Promise<CryptoKey> {\n const key = KeyCache.key(salt, iterations, fingerprint);\n const cached = cache.get(key);\n if (cached) return cached;\n const pending = algorithm.deriveKey({ subtle, passphrase, fingerprint, salt, iterations });\n cache.set(key, pending);\n return pending;\n }\n\n return {\n async encrypt(plainText, options) {\n const salt = getRandomBytes(SALT_BYTES);\n const key = await getDerivedKey(salt, options?.fingerprint);\n const encoded = encoder.encode(plainText);\n const plainBytes = new Uint8Array(new ArrayBuffer(encoded.byteLength));\n plainBytes.set(encoded);\n const { cipher, iv } = await algorithm.encrypt({ subtle, key, plainText: plainBytes });\n return encodePayload({ version: algorithm.version, salt, iv, cipher });\n },\n\n async decrypt(payload, options) {\n const parsed = parsePayload(payload);\n if (parsed.version !== algorithm.version) {\n throw new Error(\n `[nuxt-crypto] Unsupported payload version: ${parsed.version} (algorithm expects ${algorithm.version}).`\n );\n }\n const key = await getDerivedKey(parsed.salt, options?.fingerprint);\n const plainBytes = await algorithm.decrypt({\n subtle,\n key,\n cipher: parsed.cipher,\n iv: parsed.iv,\n });\n return decoder.decode(plainBytes);\n },\n\n clearKeyCache(): void {\n cache.clear();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/types/module-options.d.ts
|
|
2
|
+
interface CryptoModuleOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Passphrase used to derive the AES key. Prefer wiring this from a
|
|
5
|
+
* runtime config env var (e.g. `NUXT_ENCRYPTION_PASSPHRASE`) rather than
|
|
6
|
+
* committing it.
|
|
7
|
+
*/
|
|
8
|
+
passphrase: string;
|
|
9
|
+
/**
|
|
10
|
+
* Name under which the service is provided. Accepts `$crypto` or `crypto`
|
|
11
|
+
* — the leading `$` is stripped. Default: `$crypto`.
|
|
12
|
+
*/
|
|
13
|
+
provideName?: string;
|
|
14
|
+
/** PBKDF2 iterations. Default 100_000. */
|
|
15
|
+
iterations?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Max number of derived keys held in memory. Default 64. Set to 0 to
|
|
18
|
+
* disable caching.
|
|
19
|
+
*/
|
|
20
|
+
keyCacheSize?: number;
|
|
21
|
+
/**
|
|
22
|
+
* When `true`, the runtime plugin is only registered on the server. Useful
|
|
23
|
+
* when you do not want the plaintext passphrase shipped to the browser.
|
|
24
|
+
* Default: `false`.
|
|
25
|
+
*/
|
|
26
|
+
serverOnly?: boolean;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { CryptoModuleOptions as t };
|
|
30
|
+
//# sourceMappingURL=index-DkMNygRG.d.ts.map
|
package/dist/module.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CryptoModuleOptions } from
|
|
3
|
-
|
|
1
|
+
import { a as CryptoAlgorithm, c as CryptoServiceConfig, i as Bytes, o as CryptoOperationOptions, s as CryptoService, t as ParsedPayload } from "./payload-CvzYMicC.js";
|
|
2
|
+
import { t as CryptoModuleOptions } from "./index-DkMNygRG.js";
|
|
3
|
+
import { NuxtModule } from "@nuxt/schema";
|
|
4
4
|
|
|
5
|
+
//#region src/module.d.ts
|
|
5
6
|
declare const cryptoModule: NuxtModule<CryptoModuleOptions>;
|
|
6
|
-
|
|
7
|
-
export { CryptoModuleOptions, cryptoModule as default };
|
|
7
|
+
//#endregion
|
|
8
|
+
export { type Bytes, type CryptoAlgorithm, type CryptoModuleOptions, type CryptoOperationOptions, type CryptoService, type CryptoServiceConfig, type ParsedPayload, cryptoModule as default };
|
|
9
|
+
//# sourceMappingURL=module.d.ts.map
|
package/dist/module.mjs
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
|
|
3
|
-
// src/module.ts
|
|
1
|
+
import "./types/index.mjs";
|
|
4
2
|
import { addPlugin, addTemplate, createResolver, defineNuxtModule } from "@nuxt/kit";
|
|
5
|
-
|
|
6
|
-
// src/templates/config.template.ts
|
|
3
|
+
//#region src/templates/config.template.ts
|
|
7
4
|
function createConfigTemplateContents(params) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`;
|
|
5
|
+
const { options, normalizedProvideName } = params;
|
|
6
|
+
const cfg = {
|
|
7
|
+
passphrase: options.passphrase,
|
|
8
|
+
iterations: options.iterations ?? 1e5,
|
|
9
|
+
keyCacheSize: options.keyCacheSize ?? 64,
|
|
10
|
+
provideName: normalizedProvideName
|
|
11
|
+
};
|
|
12
|
+
return `export default ${JSON.stringify(cfg)};\n`;
|
|
17
13
|
}
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/templates/types.template.ts
|
|
20
16
|
function createTypesTemplateContents(params) {
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
const { typesEntryPath, normalizedProvideName } = params;
|
|
18
|
+
return `// Auto-generated by @alikhalilll/nuxt-crypto
|
|
23
19
|
import type { CryptoService } from '${typesEntryPath}';
|
|
24
20
|
|
|
25
21
|
declare module '#app' {
|
|
@@ -37,53 +33,54 @@ declare module 'vue' {
|
|
|
37
33
|
export {};
|
|
38
34
|
`;
|
|
39
35
|
}
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/utils/normalize-provide-name.ts
|
|
42
38
|
function normalizeProvideName(provideName, fallback) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return stripped || fallback;
|
|
39
|
+
const name = (provideName ?? fallback).trim();
|
|
40
|
+
return (name.startsWith("$") ? name.slice(1) : name) || fallback;
|
|
46
41
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/module.ts
|
|
44
|
+
const cryptoModule = defineNuxtModule({
|
|
45
|
+
meta: {
|
|
46
|
+
name: "@alikhalilll/nuxt-crypto",
|
|
47
|
+
configKey: "crypto",
|
|
48
|
+
compatibility: { nuxt: ">=3.0.0" }
|
|
49
|
+
},
|
|
50
|
+
defaults: {
|
|
51
|
+
passphrase: "",
|
|
52
|
+
provideName: "$crypto",
|
|
53
|
+
iterations: 1e5,
|
|
54
|
+
keyCacheSize: 64,
|
|
55
|
+
serverOnly: false
|
|
56
|
+
},
|
|
57
|
+
setup(options, nuxt) {
|
|
58
|
+
const resolver = createResolver(import.meta.url);
|
|
59
|
+
const normalizedProvideName = normalizeProvideName(options.provideName, "crypto");
|
|
60
|
+
addTemplate({
|
|
61
|
+
filename: "crypto-config.mjs",
|
|
62
|
+
getContents: () => createConfigTemplateContents({
|
|
63
|
+
options,
|
|
64
|
+
normalizedProvideName
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
const typesTemplate = addTemplate({
|
|
68
|
+
filename: "types/crypto.d.ts",
|
|
69
|
+
getContents: () => createTypesTemplateContents({
|
|
70
|
+
typesEntryPath: resolver.resolve("./types/index"),
|
|
71
|
+
normalizedProvideName
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
addPlugin({
|
|
75
|
+
mode: options.serverOnly ? "server" : "all",
|
|
76
|
+
src: resolver.resolve("./runtime/plugin")
|
|
77
|
+
});
|
|
78
|
+
nuxt.hook("prepare:types", ({ references }) => {
|
|
79
|
+
references.push({ path: typesTemplate.dst });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
84
82
|
});
|
|
85
|
-
|
|
86
|
-
export {
|
|
87
|
-
|
|
88
|
-
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { cryptoModule as default };
|
|
85
|
+
|
|
89
86
|
//# sourceMappingURL=module.mjs.map
|
package/dist/module.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"module.mjs","names":[],"sources":["../src/templates/config.template.ts","../src/templates/types.template.ts","../src/utils/normalize-provide-name.ts","../src/module.ts"],"sourcesContent":["import type { CryptoModuleOptions } from '../types/module-options';\n\nexport interface ResolvedCryptoConfig {\n passphrase: string;\n iterations: number;\n keyCacheSize: number;\n provideName: string;\n}\n\nexport function createConfigTemplateContents(params: {\n options: CryptoModuleOptions;\n normalizedProvideName: string;\n}): string {\n const { options, normalizedProvideName } = params;\n const cfg: ResolvedCryptoConfig = {\n passphrase: options.passphrase,\n iterations: options.iterations ?? 100_000,\n keyCacheSize: options.keyCacheSize ?? 64,\n provideName: normalizedProvideName,\n };\n return `export default ${JSON.stringify(cfg)};\\n`;\n}\n","export function createTypesTemplateContents(params: {\n typesEntryPath: string;\n normalizedProvideName: string;\n}): string {\n const { typesEntryPath, normalizedProvideName } = params;\n\n return `// Auto-generated by @alikhalilll/nuxt-crypto\nimport type { CryptoService } from '${typesEntryPath}';\n\ndeclare module '#app' {\n interface NuxtApp {\n $${normalizedProvideName}: CryptoService;\n }\n}\n\ndeclare module 'vue' {\n interface ComponentCustomProperties {\n $${normalizedProvideName}: CryptoService;\n }\n}\n\nexport {};\n`;\n}\n","export function normalizeProvideName(provideName: string | undefined, fallback: string): string {\n const name = (provideName ?? fallback).trim();\n const stripped = name.startsWith('$') ? name.slice(1) : name;\n return stripped || fallback;\n}\n","import { addPlugin, addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit';\nimport type { NuxtModule } from '@nuxt/schema';\n\nimport { createConfigTemplateContents } from './templates/config.template';\nimport { createTypesTemplateContents } from './templates/types.template';\nimport type { CryptoModuleOptions } from './types/module-options';\nimport { normalizeProvideName } from './utils/normalize-provide-name';\n\nexport * from './types';\n\nconst cryptoModule: NuxtModule<CryptoModuleOptions> = defineNuxtModule<CryptoModuleOptions>({\n meta: {\n name: '@alikhalilll/nuxt-crypto',\n configKey: 'crypto',\n compatibility: { nuxt: '>=3.0.0' },\n },\n defaults: {\n passphrase: '',\n provideName: '$crypto',\n iterations: 100_000,\n keyCacheSize: 64,\n serverOnly: false,\n },\n setup(options, nuxt) {\n const resolver = createResolver(import.meta.url);\n const normalizedProvideName = normalizeProvideName(options.provideName, 'crypto');\n\n addTemplate({\n filename: 'crypto-config.mjs',\n getContents: () => createConfigTemplateContents({ options, normalizedProvideName }),\n });\n\n const typesTemplate = addTemplate({\n filename: 'types/crypto.d.ts',\n getContents: () =>\n createTypesTemplateContents({\n typesEntryPath: resolver.resolve('./types/index'),\n normalizedProvideName,\n }),\n });\n\n addPlugin({\n mode: options.serverOnly ? 'server' : 'all',\n src: resolver.resolve('./runtime/plugin'),\n });\n\n nuxt.hook('prepare:types', ({ references }) => {\n references.push({ path: typesTemplate.dst });\n });\n },\n});\n\nexport default cryptoModule;\n"],"mappings":";;;AASA,SAAgB,6BAA6B,QAGlC;CACT,MAAM,EAAE,SAAS,0BAA0B;CAC3C,MAAM,MAA4B;EAChC,YAAY,QAAQ;EACpB,YAAY,QAAQ,cAAc;EAClC,cAAc,QAAQ,gBAAgB;EACtC,aAAa;CACf;CACA,OAAO,kBAAkB,KAAK,UAAU,GAAG,EAAE;AAC/C;;;ACrBA,SAAgB,4BAA4B,QAGjC;CACT,MAAM,EAAE,gBAAgB,0BAA0B;CAElD,OAAO;sCAC6B,eAAe;;;;OAI9C,sBAAsB;;;;;;OAMtB,sBAAsB;;;;;;AAM7B;;;ACvBA,SAAgB,qBAAqB,aAAiC,UAA0B;CAC9F,MAAM,QAAQ,eAAe,UAAU,KAAK;CAE5C,QADiB,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI,SACrC;AACrB;;;ACMA,MAAM,eAAgD,iBAAsC;CAC1F,MAAM;EACJ,MAAM;EACN,WAAW;EACX,eAAe,EAAE,MAAM,UAAU;CACnC;CACA,UAAU;EACR,YAAY;EACZ,aAAa;EACb,YAAY;EACZ,cAAc;EACd,YAAY;CACd;CACA,MAAM,SAAS,MAAM;EACnB,MAAM,WAAW,eAAe,OAAO,KAAK,GAAG;EAC/C,MAAM,wBAAwB,qBAAqB,QAAQ,aAAa,QAAQ;EAEhF,YAAY;GACV,UAAU;GACV,mBAAmB,6BAA6B;IAAE;IAAS;GAAsB,CAAC;EACpF,CAAC;EAED,MAAM,gBAAgB,YAAY;GAChC,UAAU;GACV,mBACE,4BAA4B;IAC1B,gBAAgB,SAAS,QAAQ,eAAe;IAChD;GACF,CAAC;EACL,CAAC;EAED,UAAU;GACR,MAAM,QAAQ,aAAa,WAAW;GACtC,KAAK,SAAS,QAAQ,kBAAkB;EAC1C,CAAC;EAED,KAAK,KAAK,kBAAkB,EAAE,iBAAiB;GAC7C,WAAW,KAAK,EAAE,MAAM,cAAc,IAAI,CAAC;EAC7C,CAAC;CACH;AACF,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//#region src/core/types.d.ts
|
|
2
|
+
/** Shorthand for the `Uint8Array` we use everywhere in the crypto package. */
|
|
3
|
+
type Bytes = Uint8Array<ArrayBuffer>;
|
|
4
|
+
/**
|
|
5
|
+
* Options accepted by `encrypt` / `decrypt`. Both calls must receive the
|
|
6
|
+
* same `fingerprint` value to round-trip; passing different fingerprints
|
|
7
|
+
* produces undecryptable ciphertext.
|
|
8
|
+
*/
|
|
9
|
+
interface CryptoOperationOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Optional extra key material mixed into the PBKDF2 input. Typically a
|
|
12
|
+
* device fingerprint produced server-side from an HttpOnly device-ID
|
|
13
|
+
* cookie via `getClientFingerprint()` from `@alikhalilll/nuxt-crypto/server`.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ Binding ciphertext to a fingerprint means the payload becomes
|
|
16
|
+
* undecryptable the moment the device cookie is cleared. Use only for
|
|
17
|
+
* ephemeral tokens (short-lived session cookies, one-time links), never
|
|
18
|
+
* for long-lived user data.
|
|
19
|
+
*/
|
|
20
|
+
fingerprint?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A CryptoService encrypts plaintext to a serialisable string payload and
|
|
24
|
+
* decrypts it back. Every operation is async because it hits the Web Crypto
|
|
25
|
+
* subtle API.
|
|
26
|
+
*/
|
|
27
|
+
interface CryptoService {
|
|
28
|
+
/** Encrypt a UTF-8 string to a versioned, base64-encoded payload. */
|
|
29
|
+
encrypt(plainText: string, options?: CryptoOperationOptions): Promise<string>;
|
|
30
|
+
/** Decrypt a payload produced by `encrypt`. Pass the same `fingerprint` used at encrypt time, if any. */
|
|
31
|
+
decrypt(payload: string, options?: CryptoOperationOptions): Promise<string>;
|
|
32
|
+
/** Clear any cached derived keys. Safe to call at any time. */
|
|
33
|
+
clearKeyCache(): void;
|
|
34
|
+
}
|
|
35
|
+
/** Config for `createCryptoService`. */
|
|
36
|
+
interface CryptoServiceConfig {
|
|
37
|
+
/** Passphrase used to derive the symmetric key. */
|
|
38
|
+
passphrase: string;
|
|
39
|
+
/** PBKDF2 iteration count. Default 100_000. */
|
|
40
|
+
iterations?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Algorithm implementation. Defaults to AES-256-GCM with PBKDF2-SHA256 key
|
|
43
|
+
* derivation. Replace to plug in a different cipher.
|
|
44
|
+
*/
|
|
45
|
+
algorithm?: CryptoAlgorithm;
|
|
46
|
+
/**
|
|
47
|
+
* Max number of derived keys held in memory. Derived keys are expensive —
|
|
48
|
+
* caching them by salt dramatically speeds up bulk decrypt. Default 64.
|
|
49
|
+
* Set to 0 to disable caching.
|
|
50
|
+
*/
|
|
51
|
+
keyCacheSize?: number;
|
|
52
|
+
/** Override the SubtleCrypto implementation. Useful for testing. */
|
|
53
|
+
subtle?: SubtleCrypto;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A pluggable symmetric-encryption algorithm. Each implementation owns the
|
|
57
|
+
* payload format (via its version tag), key derivation, and the actual
|
|
58
|
+
* encrypt/decrypt work.
|
|
59
|
+
*/
|
|
60
|
+
interface CryptoAlgorithm {
|
|
61
|
+
/** Short identifier stored as the first segment of every payload (e.g. `'v1'`). */
|
|
62
|
+
readonly version: string;
|
|
63
|
+
/**
|
|
64
|
+
* Derive a CryptoKey from a passphrase + salt pair. Expected to be idempotent.
|
|
65
|
+
* When `fingerprint` is provided it is mixed into the key-derivation input
|
|
66
|
+
* (alongside the passphrase) so ciphertext becomes bound to that fingerprint.
|
|
67
|
+
*/
|
|
68
|
+
deriveKey(args: {
|
|
69
|
+
subtle: SubtleCrypto;
|
|
70
|
+
passphrase: string;
|
|
71
|
+
fingerprint?: string;
|
|
72
|
+
salt: Bytes;
|
|
73
|
+
iterations: number;
|
|
74
|
+
}): Promise<CryptoKey>;
|
|
75
|
+
/** Encrypt plaintext bytes using a pre-derived key. Returns cipher bytes + any per-call params (e.g. IV). */
|
|
76
|
+
encrypt(args: {
|
|
77
|
+
subtle: SubtleCrypto;
|
|
78
|
+
key: CryptoKey;
|
|
79
|
+
plainText: Bytes;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
cipher: Bytes;
|
|
82
|
+
iv: Bytes;
|
|
83
|
+
}>;
|
|
84
|
+
/** Decrypt cipher bytes using a pre-derived key and the IV produced during encrypt. */
|
|
85
|
+
decrypt(args: {
|
|
86
|
+
subtle: SubtleCrypto;
|
|
87
|
+
key: CryptoKey;
|
|
88
|
+
cipher: Bytes;
|
|
89
|
+
iv: Bytes;
|
|
90
|
+
}): Promise<Bytes>;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/core/payload.d.ts
|
|
94
|
+
/**
|
|
95
|
+
* Payload serialisation format (v1): `${version}.${saltB64}.${ivB64}.${cipherB64}`.
|
|
96
|
+
*
|
|
97
|
+
* Each part is URL-safe base64, all joined with `.`. The version tag is
|
|
98
|
+
* always the first segment so consumers can route to the correct algorithm
|
|
99
|
+
* during decrypt.
|
|
100
|
+
*/
|
|
101
|
+
interface ParsedPayload {
|
|
102
|
+
version: string;
|
|
103
|
+
salt: Bytes;
|
|
104
|
+
iv: Bytes;
|
|
105
|
+
cipher: Bytes;
|
|
106
|
+
}
|
|
107
|
+
declare function encodePayload(parts: ParsedPayload): string;
|
|
108
|
+
declare function parsePayload(payload: string): ParsedPayload;
|
|
109
|
+
//#endregion
|
|
110
|
+
export { CryptoAlgorithm as a, CryptoServiceConfig as c, Bytes as i, encodePayload as n, CryptoOperationOptions as o, parsePayload as r, CryptoService as s, ParsedPayload as t };
|
|
111
|
+
//# sourceMappingURL=payload-CvzYMicC.d.ts.map
|