timebasedcipher 3.0.2 → 3.0.3

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.
@@ -0,0 +1,81 @@
1
+ import { RuntimeEnv } from "./cryptoProvider";
2
+ /**
3
+ * Options controlling encryption and decryption behavior.
4
+ */
5
+ export interface CipherOptions {
6
+ /**
7
+ * Runtime environment override.
8
+ *
9
+ * @default "auto"
10
+ *
11
+ * @example
12
+ * encrypt(data, secret, 60, { env: "node" })
13
+ */
14
+ env?: RuntimeEnv;
15
+ /**
16
+ * Token expiration time in seconds.
17
+ * Defaults to intervalSeconds if not provided.
18
+ *
19
+ * @example
20
+ * encrypt(data, secret, 60, { ttlSeconds: 300 })
21
+ */
22
+ ttlSeconds?: number;
23
+ /**
24
+ * Allowed clock drift tolerance in seconds.
25
+ *
26
+ * @default 30
27
+ *
28
+ * @example
29
+ * decrypt(token, secret, 60, { clockToleranceSeconds: 60 })
30
+ */
31
+ clockToleranceSeconds?: number;
32
+ /**
33
+ * Custom signature used to validate token purpose/domain.
34
+ *
35
+ * @example
36
+ * encrypt(data, secret, 60, { signature: "auth-session" })
37
+ */
38
+ signature?: string;
39
+ }
40
+ /**
41
+ * Encrypts JSON data into a secure token.
42
+ *
43
+ * @template T
44
+ * @param data Data to encrypt
45
+ * @param secret Shared secret
46
+ * @param intervalSeconds Key rotation interval
47
+ * @param options Optional settings
48
+ *
49
+ * @returns Secure encrypted token
50
+ *
51
+ * @example
52
+ * const token = await encrypt(
53
+ * { userId: 1 },
54
+ * "super-secret",
55
+ * 60,
56
+ * { signature: "auth-session", ttlSeconds: 300 }
57
+ * )
58
+ */
59
+ export declare function encrypt<T>(data: T, secret: string, intervalSeconds: number, options?: CipherOptions): Promise<string>;
60
+ /**
61
+ * Decrypts a token and validates signature, expiration, and replay.
62
+ *
63
+ * @template T
64
+ * @param token Encrypted token
65
+ * @param secret Shared secret
66
+ * @param intervalSeconds Rotation interval
67
+ * @param options Validation options
68
+ *
69
+ * @returns Decrypted data
70
+ *
71
+ * @throws Error if token invalid, expired, replayed, or signature mismatch
72
+ *
73
+ * @example
74
+ * const data = await decrypt(
75
+ * token,
76
+ * "super-secret",
77
+ * 60,
78
+ * { signature: "auth-session" }
79
+ * )
80
+ */
81
+ export declare function decrypt<T>(token: string, secret: string, intervalSeconds: number, options?: CipherOptions): Promise<T>;
package/dist/cipher.js ADDED
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encrypt = encrypt;
4
+ exports.decrypt = decrypt;
5
+ const cryptoProvider_1 = require("./cryptoProvider");
6
+ const encoder = new TextEncoder();
7
+ const decoder = new TextDecoder();
8
+ /**
9
+ * Converts ArrayBuffer → Base64URL string.
10
+ *
11
+ * @example
12
+ * const encoded = b64url(buffer)
13
+ */
14
+ function b64url(buffer) {
15
+ return Buffer.from(buffer)
16
+ .toString("base64")
17
+ .replace(/\+/g, "-")
18
+ .replace(/\//g, "_")
19
+ .replace(/=+$/, "");
20
+ }
21
+ /**
22
+ * Converts Base64URL → ArrayBuffer.
23
+ *
24
+ * @example
25
+ * const buffer = fromB64url(encoded)
26
+ */
27
+ function fromB64url(str) {
28
+ const pad = str.length % 4 ? "=".repeat(4 - (str.length % 4)) : "";
29
+ const base64 = str.replace(/-/g, "+").replace(/_/g, "/") + pad;
30
+ return Uint8Array.from(Buffer.from(base64, "base64")).buffer;
31
+ }
32
+ /**
33
+ * Cache for derived keys to improve performance.
34
+ */
35
+ const keyCache = new Map();
36
+ /**
37
+ * Derives AES-256-GCM key using HKDF.
38
+ *
39
+ * @param cryptoObj Web Crypto instance
40
+ * @param secret Shared secret
41
+ * @param timeSlot Rotation slot
42
+ *
43
+ * @example
44
+ * const key = await deriveKey(crypto, "secret", 12345)
45
+ */
46
+ async function deriveKey(cryptoObj, secret, timeSlot) {
47
+ const cacheKey = `${secret}:${timeSlot}`;
48
+ if (keyCache.has(cacheKey))
49
+ return keyCache.get(cacheKey);
50
+ const baseKey = await cryptoObj.subtle.importKey("raw", encoder.encode(secret), { name: "HKDF" }, false, ["deriveKey"]);
51
+ const key = await cryptoObj.subtle.deriveKey({
52
+ name: "HKDF",
53
+ hash: "SHA-256",
54
+ salt: encoder.encode(String(timeSlot)),
55
+ info: encoder.encode("time-based-encryption"),
56
+ }, baseKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
57
+ keyCache.set(cacheKey, key);
58
+ if (keyCache.size > 50)
59
+ keyCache.clear();
60
+ return key;
61
+ }
62
+ /**
63
+ * In-memory nonce store for replay protection.
64
+ */
65
+ const nonceStore = new Set();
66
+ /**
67
+ * Validates nonce uniqueness.
68
+ *
69
+ * @throws Error if nonce already used
70
+ */
71
+ function checkReplay(nonce) {
72
+ if (nonceStore.has(nonce)) {
73
+ throw new Error("Replay attack detected");
74
+ }
75
+ nonceStore.add(nonce);
76
+ if (nonceStore.size > 1000)
77
+ nonceStore.clear();
78
+ }
79
+ /**
80
+ * Encrypts JSON data into a secure token.
81
+ *
82
+ * @template T
83
+ * @param data Data to encrypt
84
+ * @param secret Shared secret
85
+ * @param intervalSeconds Key rotation interval
86
+ * @param options Optional settings
87
+ *
88
+ * @returns Secure encrypted token
89
+ *
90
+ * @example
91
+ * const token = await encrypt(
92
+ * { userId: 1 },
93
+ * "super-secret",
94
+ * 60,
95
+ * { signature: "auth-session", ttlSeconds: 300 }
96
+ * )
97
+ */
98
+ async function encrypt(data, secret, intervalSeconds, options = {}) {
99
+ var _a, _b;
100
+ const cryptoObj = await (0, cryptoProvider_1.getCrypto)(options.env);
101
+ const now = Math.floor(Date.now() / 1000);
102
+ const ttl = (_a = options.ttlSeconds) !== null && _a !== void 0 ? _a : intervalSeconds;
103
+ const signature = (_b = options.signature) !== null && _b !== void 0 ? _b : "default-signature";
104
+ const payload = {
105
+ sig: signature,
106
+ iat: now,
107
+ exp: now + ttl,
108
+ nonce: cryptoObj.randomUUID(),
109
+ data,
110
+ };
111
+ const timeSlot = Math.floor(now / intervalSeconds);
112
+ const key = await deriveKey(cryptoObj, secret, timeSlot);
113
+ const iv = cryptoObj.getRandomValues(new Uint8Array(12));
114
+ const cipher = await cryptoObj.subtle.encrypt({ name: "AES-GCM", iv }, key, encoder.encode(JSON.stringify(payload)));
115
+ return `v1.${b64url(cipher)}.${b64url(iv.buffer)}`;
116
+ }
117
+ /**
118
+ * Decrypts a token and validates signature, expiration, and replay.
119
+ *
120
+ * @template T
121
+ * @param token Encrypted token
122
+ * @param secret Shared secret
123
+ * @param intervalSeconds Rotation interval
124
+ * @param options Validation options
125
+ *
126
+ * @returns Decrypted data
127
+ *
128
+ * @throws Error if token invalid, expired, replayed, or signature mismatch
129
+ *
130
+ * @example
131
+ * const data = await decrypt(
132
+ * token,
133
+ * "super-secret",
134
+ * 60,
135
+ * { signature: "auth-session" }
136
+ * )
137
+ */
138
+ async function decrypt(token, secret, intervalSeconds, options = {}) {
139
+ var _a, _b;
140
+ const cryptoObj = await (0, cryptoProvider_1.getCrypto)(options.env);
141
+ const expectedSignature = (_a = options.signature) !== null && _a !== void 0 ? _a : "default-signature";
142
+ const [version, cipherB64, ivB64] = token.split(".");
143
+ if (version !== "v1")
144
+ throw new Error("Unsupported token version");
145
+ const iv = fromB64url(ivB64);
146
+ const cipher = fromB64url(cipherB64);
147
+ const now = Math.floor(Date.now() / 1000);
148
+ const tolerance = (_b = options.clockToleranceSeconds) !== null && _b !== void 0 ? _b : 30;
149
+ const timeSlots = [
150
+ Math.floor(now / intervalSeconds),
151
+ Math.floor((now - tolerance) / intervalSeconds),
152
+ Math.floor((now + tolerance) / intervalSeconds),
153
+ ];
154
+ let payload = null;
155
+ for (const slot of timeSlots) {
156
+ try {
157
+ const key = await deriveKey(cryptoObj, secret, slot);
158
+ const plain = await cryptoObj.subtle.decrypt({ name: "AES-GCM", iv }, key, cipher);
159
+ payload = JSON.parse(decoder.decode(plain));
160
+ break;
161
+ }
162
+ catch { }
163
+ }
164
+ if (!payload)
165
+ throw new Error("Unable to decrypt token");
166
+ if (payload.sig !== expectedSignature) {
167
+ throw new Error("Invalid token signature");
168
+ }
169
+ if (payload.exp + tolerance < now) {
170
+ throw new Error("Token expired");
171
+ }
172
+ checkReplay(payload.nonce);
173
+ return payload.data;
174
+ }
@@ -0,0 +1,8 @@
1
+ export type RuntimeEnv = "auto" | "browser" | "node";
2
+ /**
3
+ * Returns a Web Crypto compatible Crypto object depending on runtime.
4
+ *
5
+ * - Browser → window.crypto
6
+ * - Node 18+ → node:crypto webcrypto
7
+ */
8
+ export declare function getCrypto(env?: RuntimeEnv): Promise<Crypto>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCrypto = getCrypto;
4
+ /**
5
+ * Returns a Web Crypto compatible Crypto object depending on runtime.
6
+ *
7
+ * - Browser → window.crypto
8
+ * - Node 18+ → node:crypto webcrypto
9
+ */
10
+ async function getCrypto(env = "auto") {
11
+ // Browser detection
12
+ if (env === "browser" || (env === "auto" && typeof window !== "undefined")) {
13
+ if (!globalThis.crypto) {
14
+ throw new Error("Web Crypto API not available in browser");
15
+ }
16
+ return globalThis.crypto;
17
+ }
18
+ // Node detection
19
+ if (env === "node" || env === "auto") {
20
+ try {
21
+ const nodeCrypto = await import("node:crypto");
22
+ return nodeCrypto.webcrypto;
23
+ }
24
+ catch {
25
+ throw new Error("Web Crypto API not available in Node.js (requires Node 18+)");
26
+ }
27
+ }
28
+ throw new Error("Unable to detect runtime crypto environment");
29
+ }
package/dist/index.d.ts CHANGED
@@ -1,30 +1,2 @@
1
- /**
2
- * Time-Based Cipher with Encrypted Signature
3
- *
4
- * Format:
5
- * sigCipherHex:sigIvHex:dataCipherHex:dataIvHex
6
- *
7
- * Requirements:
8
- * - Node.js 18+
9
- * - Modern browsers
10
- */
11
- /**
12
- * Encrypt JSON data with encrypted signature.
13
- *
14
- * @param data - JSON-serializable data.
15
- * @param secret - Shared secret.
16
- * @param intervalSeconds - Rotation interval in seconds.
17
- *
18
- * @returns Promise resolving to ciphertext string.
19
- */
20
- export declare function encrypt<T>(data: T, secret: string, intervalSeconds: number): Promise<string>;
21
- /**
22
- * Decrypt ciphertext with encrypted signature validation.
23
- *
24
- * @param payload - Ciphertext string.
25
- * @param secret - Shared secret.
26
- * @param intervalSeconds - Rotation interval.
27
- *
28
- * @returns Promise resolving to decrypted data.
29
- */
30
- export declare function decrypt<T>(payload: string, secret: string, intervalSeconds: number): Promise<T>;
1
+ export * from "./cipher";
2
+ export * from "./cryptoProvider";
package/dist/index.js CHANGED
@@ -1,122 +1,18 @@
1
1
  "use strict";
2
- /**
3
- * Time-Based Cipher with Encrypted Signature
4
- *
5
- * Format:
6
- * sigCipherHex:sigIvHex:dataCipherHex:dataIvHex
7
- *
8
- * Requirements:
9
- * - Node.js 18+
10
- * - Modern browsers
11
- */
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.encrypt = encrypt;
14
- exports.decrypt = decrypt;
15
- const cryptoObj = (() => {
16
- if (typeof globalThis !== "undefined" && globalThis.crypto) {
17
- return globalThis.crypto;
18
- }
19
- throw new Error("Web Crypto API not available. Requires Node 18+ or modern browser.");
20
- })();
21
- const encoder = new TextEncoder();
22
- const decoder = new TextDecoder();
23
- /**
24
- * Convert ArrayBuffer to hex string.
25
- */
26
- function toHex(buffer) {
27
- return Array.from(new Uint8Array(buffer))
28
- .map((b) => b.toString(16).padStart(2, "0"))
29
- .join("");
30
- }
31
- /**
32
- * Convert hex string to ArrayBuffer.
33
- */
34
- function hexToBuffer(hex) {
35
- if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0) {
36
- throw new Error("Invalid hex string");
37
- }
38
- const bytes = new Uint8Array(hex.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
39
- return bytes.buffer;
40
- }
41
- /**
42
- * Derive AES-256-GCM key using HKDF-SHA256.
43
- */
44
- async function deriveKey(secret, intervalSeconds) {
45
- if (!secret)
46
- throw new Error("Secret must not be empty");
47
- if (intervalSeconds <= 0)
48
- throw new Error("intervalSeconds must be positive");
49
- const now = Date.now() - 1000;
50
- const timeSlot = Math.floor(now / (intervalSeconds * 1000));
51
- const baseKey = await cryptoObj.subtle.importKey("raw", encoder.encode(secret).buffer, { name: "HKDF" }, false, ["deriveKey"]);
52
- return cryptoObj.subtle.deriveKey({
53
- name: "HKDF",
54
- hash: "SHA-256",
55
- salt: encoder.encode(String(timeSlot)).buffer,
56
- info: encoder.encode("time-based-encryption").buffer,
57
- }, baseKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
58
- }
59
- /**
60
- * Encrypt raw ArrayBuffer with AES-GCM.
61
- */
62
- async function aesEncrypt(key, data) {
63
- const ivBytes = cryptoObj.getRandomValues(new Uint8Array(12));
64
- const iv = ivBytes.buffer;
65
- const cipher = await cryptoObj.subtle.encrypt({ name: "AES-GCM", iv }, key, data);
66
- return { cipher, iv };
67
- }
68
- /**
69
- * Decrypt raw ArrayBuffer with AES-GCM.
70
- */
71
- async function aesDecrypt(key, cipher, iv) {
72
- return cryptoObj.subtle.decrypt({ name: "AES-GCM", iv }, key, cipher);
73
- }
74
- /**
75
- * Encrypt JSON data with encrypted signature.
76
- *
77
- * @param data - JSON-serializable data.
78
- * @param secret - Shared secret.
79
- * @param intervalSeconds - Rotation interval in seconds.
80
- *
81
- * @returns Promise resolving to ciphertext string.
82
- */
83
- async function encrypt(data, secret, intervalSeconds) {
84
- const key = await deriveKey(secret, intervalSeconds);
85
- // Signature payload (can be customized)
86
- const signaturePayload = encoder.encode("signature-v1").buffer;
87
- const sigEncrypted = await aesEncrypt(key, signaturePayload);
88
- const dataBuffer = encoder.encode(JSON.stringify(data)).buffer;
89
- const dataEncrypted = await aesEncrypt(key, dataBuffer);
90
- return [
91
- toHex(sigEncrypted.cipher),
92
- toHex(sigEncrypted.iv),
93
- toHex(dataEncrypted.cipher),
94
- toHex(dataEncrypted.iv),
95
- ].join(":");
96
- }
97
- /**
98
- * Decrypt ciphertext with encrypted signature validation.
99
- *
100
- * @param payload - Ciphertext string.
101
- * @param secret - Shared secret.
102
- * @param intervalSeconds - Rotation interval.
103
- *
104
- * @returns Promise resolving to decrypted data.
105
- */
106
- async function decrypt(payload, secret, intervalSeconds) {
107
- const parts = payload.split(":");
108
- if (parts.length !== 4) {
109
- throw new Error("Invalid cipher format");
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
110
7
  }
111
- const [sigCipherHex, sigIvHex, dataCipherHex, dataIvHex,] = parts;
112
- const key = await deriveKey(secret, intervalSeconds);
113
- // Decrypt signature
114
- const sigPlain = await aesDecrypt(key, hexToBuffer(sigCipherHex), hexToBuffer(sigIvHex));
115
- const sigText = decoder.decode(sigPlain);
116
- if (sigText !== "signature-v1") {
117
- throw new Error("Invalid signature");
118
- }
119
- // Decrypt actual data
120
- const dataPlain = await aesDecrypt(key, hexToBuffer(dataCipherHex), hexToBuffer(dataIvHex));
121
- return JSON.parse(decoder.decode(dataPlain));
122
- }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./cipher"), exports);
18
+ __exportStar(require("./cryptoProvider"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "timebasedcipher",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "Time-based key generation and AES encryption/decryption SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",