timebasedcipher 3.0.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/LICENSE +21 -0
- package/README.md +208 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +154 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Deb Kalyan Mohanty
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# timebasedcipher
|
|
2
|
+
|
|
3
|
+
A lightweight, isomorphic (**Node + Browser**) library for **time-based rotating AES encryption** with optional **JWT-based key validation**.
|
|
4
|
+
|
|
5
|
+
Built with [`crypto-js`](https://www.npmjs.com/package/crypto-js), this package provides a simple interface to:
|
|
6
|
+
|
|
7
|
+
- Generate **time-based rotating AES keys**
|
|
8
|
+
- Encrypt and decrypt JavaScript objects or strings
|
|
9
|
+
- Validate JWTs before key derivation (optional)
|
|
10
|
+
- Works seamlessly in both **frontend** and **backend** environments
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install timebasedcipher crypto-js
|
|
18
|
+
# or
|
|
19
|
+
yarn add timebasedcipher crypto-js
|
|
20
|
+
# or
|
|
21
|
+
pnpm add timebasedcipher crypto-js
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { generateKey, encrypt, decrypt } from "timebasedcipher";
|
|
30
|
+
|
|
31
|
+
const sharedSecret = "mySuperSecret";
|
|
32
|
+
const intervalSeconds = 60; // key changes every 60 seconds
|
|
33
|
+
|
|
34
|
+
// Step 1: Generate a rotating key
|
|
35
|
+
const key = generateKey(sharedSecret, intervalSeconds);
|
|
36
|
+
console.log("Generated Key:", key);
|
|
37
|
+
|
|
38
|
+
// Step 2: Encrypt some data
|
|
39
|
+
const data = { user: "Deb", role: "admin", timestamp: Date.now() };
|
|
40
|
+
const cipher = encrypt(data, key);
|
|
41
|
+
console.log("Encrypted:", cipher);
|
|
42
|
+
|
|
43
|
+
// Step 3: Decrypt it
|
|
44
|
+
const decrypted = decrypt(cipher, key);
|
|
45
|
+
console.log("Decrypted:", decrypted);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Works in both **Node.js** and **browser** environments.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Function Reference
|
|
53
|
+
|
|
54
|
+
### `generateKey(sharedSecretOrJwt: string, interval: number, isJwt?: boolean): string`
|
|
55
|
+
|
|
56
|
+
Generates a **SHA-256 key** that rotates every `interval` seconds.
|
|
57
|
+
When `isJwt` is set to `true`, the input is treated as a **JWT string** — its `exp` claim is validated before key derivation.
|
|
58
|
+
|
|
59
|
+
| Parameter | Type | Description |
|
|
60
|
+
| -------------------- | --------- | --------------------------------------------------------------------------- |
|
|
61
|
+
| `sharedSecretOrJwt` | `string` | A pre-shared secret or a JWT string when `isJwt = true`. |
|
|
62
|
+
| `interval` | `number` | Time in seconds after which the key rotates. |
|
|
63
|
+
| `isJwt` _(optional)_ | `boolean` | When `true`, validates JWT expiry and uses the raw JWT as the secret input. |
|
|
64
|
+
|
|
65
|
+
**Returns:** `string` — a 64-character hex string (256-bit key).
|
|
66
|
+
|
|
67
|
+
**Throws:**
|
|
68
|
+
|
|
69
|
+
- `Error("Invalid JWT: missing payload part")` — malformed token
|
|
70
|
+
- `Error("JWT payload does not contain a valid 'exp' claim")`
|
|
71
|
+
- `Error("JWT is expired")`
|
|
72
|
+
|
|
73
|
+
#### Example (with JWT validation)
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // your JWT
|
|
77
|
+
const interval = 120;
|
|
78
|
+
|
|
79
|
+
const key = generateKey(jwt, interval, true); // validates exp claim
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### `encrypt(data: any, encryptionKeyHex: string): string`
|
|
85
|
+
|
|
86
|
+
Encrypts any serializable JavaScript object or string using **AES-256-CBC**.
|
|
87
|
+
|
|
88
|
+
| Parameter | Type | Description |
|
|
89
|
+
| ------------------ | -------- | ------------------------------------------------- |
|
|
90
|
+
| `data` | `any` | The object or value to encrypt. |
|
|
91
|
+
| `encryptionKeyHex` | `string` | 64-character hex string key (from `generateKey`). |
|
|
92
|
+
|
|
93
|
+
**Returns:**
|
|
94
|
+
A string in format:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
cipherHex:ivHex
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### `decrypt(cipherText: string, encryptionKeyHex: string): any`
|
|
103
|
+
|
|
104
|
+
Decrypts ciphertext previously produced by `encrypt`.
|
|
105
|
+
|
|
106
|
+
| Parameter | Type | Description |
|
|
107
|
+
| ------------------ | -------- | ------------------------------------------------------- |
|
|
108
|
+
| `cipherText` | `string` | Ciphertext in `"cipherHex:ivHex"` format. |
|
|
109
|
+
| `encryptionKeyHex` | `string` | 64-character hex string key (same key used to encrypt). |
|
|
110
|
+
|
|
111
|
+
**Returns:**
|
|
112
|
+
The decrypted JavaScript value (automatically parsed from JSON).
|
|
113
|
+
|
|
114
|
+
**Throws:**
|
|
115
|
+
|
|
116
|
+
- `Error("Invalid cipher format, expected cipherHex:ivHex")`
|
|
117
|
+
- `Error("Decryption produced empty string")`
|
|
118
|
+
- `Error("Unable to decrypt: no valid key found or invalid payload")`
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## ⚙️ How It Works
|
|
123
|
+
|
|
124
|
+
- The key **rotates** every defined interval (e.g., every 60 seconds)
|
|
125
|
+
|
|
126
|
+
- Derived using:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
SHA256(`${secretInput}:${timeSlot}`)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
where
|
|
133
|
+
`timeSlot = floor(Date.now() / (interval * 1000) + 1000)`
|
|
134
|
+
|
|
135
|
+
- AES encryption uses:
|
|
136
|
+
|
|
137
|
+
- **AES-256-CBC**
|
|
138
|
+
- **PKCS#7 padding**
|
|
139
|
+
- **Random 16-byte IV**
|
|
140
|
+
|
|
141
|
+
- Encrypted output format:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
<cipherHex>:<ivHex>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Both ends** must:
|
|
148
|
+
|
|
149
|
+
- Use the **same secret** (or JWT)
|
|
150
|
+
- Use the **same rotation interval**
|
|
151
|
+
- Have **roughly synchronized clocks** (within ±1 interval)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Handling Key Rotation Drift
|
|
156
|
+
|
|
157
|
+
If decryption fails due to slight clock drift, try nearby intervals:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
const interval = 60;
|
|
162
|
+
const secret = "mySuperSecret";
|
|
163
|
+
|
|
164
|
+
const currentKey = generateKey(secret, interval);
|
|
165
|
+
const prevKey = generateKey(secret, interval); // simulate earlier slot
|
|
166
|
+
const nextKey = generateKey(secret, interval); // simulate next slot
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
return decrypt(cipher, currentKey);
|
|
170
|
+
} catch {
|
|
171
|
+
try {
|
|
172
|
+
return decrypt(cipher, prevKey);
|
|
173
|
+
} catch {
|
|
174
|
+
return decrypt(cipher, nextKey);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Browser + Node Compatibility
|
|
182
|
+
|
|
183
|
+
| Environment | Supported | Notes |
|
|
184
|
+
| --------------- | --------- | ------------------------------------------ |
|
|
185
|
+
| Node.js | ✅ | Uses `crypto-js`, no native crypto needed |
|
|
186
|
+
| React / Browser | ✅ | Fully supported |
|
|
187
|
+
| Next.js | ✅ | Works in both server and client components |
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Security Notes
|
|
192
|
+
|
|
193
|
+
- AES-256-CBC provides **confidentiality**, not integrity — consider adding an **HMAC** if you need tamper detection.
|
|
194
|
+
- Avoid embedding secrets in frontend code — end users can access them.
|
|
195
|
+
- JWT mode only validates the `exp` claim.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Requirements
|
|
200
|
+
|
|
201
|
+
- Node.js ≥ 14 or any modern browser
|
|
202
|
+
- `crypto-js` dependency (installed automatically)
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT License © 2025 [Deb Kalyan Mohanty](https://github.com/debkalyanmohanty)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a time-based rotating key (SHA256 hash of sharedSecret + current interval).
|
|
3
|
+
*
|
|
4
|
+
* The key is derived from:
|
|
5
|
+
* SHA256(`${secretInput}:${timeSlot}`)
|
|
6
|
+
* where:
|
|
7
|
+
* timeSlot = floor(Date.now() / (interval * 1000) + 1000)
|
|
8
|
+
*
|
|
9
|
+
* When `isJwt` is true:
|
|
10
|
+
* - `sharedSecretOrJwt` is treated as a JWT string
|
|
11
|
+
* - The JWT payload is decoded
|
|
12
|
+
* - The `exp` claim (if present) is checked against current time
|
|
13
|
+
* - If the JWT is expired, an error is thrown and no key is generated
|
|
14
|
+
* - The *raw JWT string* is still used as the secret input for key derivation
|
|
15
|
+
*
|
|
16
|
+
* @param sharedSecretOrJwt - A pre-shared secret string, or a JWT when `isJwt` is true.
|
|
17
|
+
* @param interval - The rotation interval in seconds. The timeSlot calculation
|
|
18
|
+
* is based on this value, so both sides must use the same interval.
|
|
19
|
+
* @param isJwt - Optional flag (default: false). When true, `sharedSecretOrJwt` is
|
|
20
|
+
* treated as a JWT and its `exp` is validated before deriving the key.
|
|
21
|
+
* @returns A 64-character hexadecimal SHA-256 hash string to be used as an AES-256 key.
|
|
22
|
+
* @throws If `isJwt` is true and the JWT is malformed or expired.
|
|
23
|
+
*/
|
|
24
|
+
export declare const generateKey: (sharedSecretOrJwt: string, interval: number, isJwt?: boolean) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Encrypt an arbitrary JavaScript value using AES-256-CBC with a random IV.
|
|
27
|
+
*
|
|
28
|
+
* The function:
|
|
29
|
+
* - JSON stringifies the input `data`
|
|
30
|
+
* - Uses the provided hex-encoded key as AES-256 key material
|
|
31
|
+
* - Generates a random 16-byte IV
|
|
32
|
+
* - Encrypts using AES-256-CBC
|
|
33
|
+
* - Returns a concatenated string in the format: "cipherHex:ivHex"
|
|
34
|
+
*
|
|
35
|
+
* @param data - Any serializable JavaScript value (e.g. object, array, string) to encrypt.
|
|
36
|
+
* @param encryptionKeyHex - A 64-character hex string representing a 32-byte key
|
|
37
|
+
* (typically the output of {@link generateKey}).
|
|
38
|
+
* @returns A string in the format `"cipherHex:ivHex"` where both parts are hex-encoded.
|
|
39
|
+
* @throws If encryption fails or JSON serialization fails.
|
|
40
|
+
*/
|
|
41
|
+
export declare const encrypt: (data: any, encryptionKeyHex: string) => string;
|
|
42
|
+
/**
|
|
43
|
+
* Decrypt an AES-256-CBC ciphertext string produced by {@link encrypt}.
|
|
44
|
+
*
|
|
45
|
+
* The function expects the input to be in the format:
|
|
46
|
+
* "cipherHex:ivHex"
|
|
47
|
+
* where:
|
|
48
|
+
* - cipherHex is the hex-encoded ciphertext
|
|
49
|
+
* - ivHex is the hex-encoded 16-byte IV
|
|
50
|
+
*
|
|
51
|
+
* It will:
|
|
52
|
+
* - Split and parse the hex components
|
|
53
|
+
* - Decrypt using AES-256-CBC with the provided key
|
|
54
|
+
* - Parse the resulting UTF-8 JSON back into the original JavaScript value
|
|
55
|
+
*
|
|
56
|
+
* @param cipherText - The ciphertext string in the format `"cipherHex:ivHex"`.
|
|
57
|
+
* @param encryptionKeyHex - A 64-character hex string representing a 32-byte key
|
|
58
|
+
* (must match the key used for encryption).
|
|
59
|
+
* @returns The decrypted JavaScript value (e.g. object, array, string).
|
|
60
|
+
* @throws If the format is invalid, decryption fails, or the decrypted output is empty/invalid JSON.
|
|
61
|
+
*/
|
|
62
|
+
export declare const decrypt: (cipherText: string, encryptionKeyHex: string) => unknown;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.decrypt = exports.encrypt = exports.generateKey = void 0;
|
|
7
|
+
const crypto_js_1 = __importDefault(require("crypto-js"));
|
|
8
|
+
/**
|
|
9
|
+
* Decode the payload of a JWT without verifying its signature.
|
|
10
|
+
* Used only to read the `exp` claim (in seconds since epoch).
|
|
11
|
+
*
|
|
12
|
+
* @param token - The JWT string (header.payload.signature)
|
|
13
|
+
* @returns Parsed payload object.
|
|
14
|
+
* @throws If the token is malformed or payload is not valid JSON.
|
|
15
|
+
*/
|
|
16
|
+
const decodeJwtPayload = (token) => {
|
|
17
|
+
const parts = token.split(".");
|
|
18
|
+
if (parts.length < 2) {
|
|
19
|
+
throw new Error("Invalid JWT: missing payload part");
|
|
20
|
+
}
|
|
21
|
+
const base64Url = parts[1];
|
|
22
|
+
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
23
|
+
// Use CryptoJS to decode base64 → UTF-8
|
|
24
|
+
const wordArray = crypto_js_1.default.enc.Base64.parse(base64);
|
|
25
|
+
const json = wordArray.toString(crypto_js_1.default.enc.Utf8);
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(json);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new Error("Invalid JWT payload JSON");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Generate a time-based rotating key (SHA256 hash of sharedSecret + current interval).
|
|
35
|
+
*
|
|
36
|
+
* The key is derived from:
|
|
37
|
+
* SHA256(`${secretInput}:${timeSlot}`)
|
|
38
|
+
* where:
|
|
39
|
+
* timeSlot = floor(Date.now() / (interval * 1000) + 1000)
|
|
40
|
+
*
|
|
41
|
+
* When `isJwt` is true:
|
|
42
|
+
* - `sharedSecretOrJwt` is treated as a JWT string
|
|
43
|
+
* - The JWT payload is decoded
|
|
44
|
+
* - The `exp` claim (if present) is checked against current time
|
|
45
|
+
* - If the JWT is expired, an error is thrown and no key is generated
|
|
46
|
+
* - The *raw JWT string* is still used as the secret input for key derivation
|
|
47
|
+
*
|
|
48
|
+
* @param sharedSecretOrJwt - A pre-shared secret string, or a JWT when `isJwt` is true.
|
|
49
|
+
* @param interval - The rotation interval in seconds. The timeSlot calculation
|
|
50
|
+
* is based on this value, so both sides must use the same interval.
|
|
51
|
+
* @param isJwt - Optional flag (default: false). When true, `sharedSecretOrJwt` is
|
|
52
|
+
* treated as a JWT and its `exp` is validated before deriving the key.
|
|
53
|
+
* @returns A 64-character hexadecimal SHA-256 hash string to be used as an AES-256 key.
|
|
54
|
+
* @throws If `isJwt` is true and the JWT is malformed or expired.
|
|
55
|
+
*/
|
|
56
|
+
const generateKey = (sharedSecretOrJwt, interval, isJwt = false) => {
|
|
57
|
+
let secretInput = sharedSecretOrJwt;
|
|
58
|
+
if (isJwt) {
|
|
59
|
+
const payload = decodeJwtPayload(sharedSecretOrJwt);
|
|
60
|
+
if (typeof payload.exp !== "number") {
|
|
61
|
+
throw new Error("JWT payload does not contain a valid 'exp' claim");
|
|
62
|
+
}
|
|
63
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
64
|
+
if (payload.exp <= nowSeconds) {
|
|
65
|
+
throw new Error("JWT is expired");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const timeSlot = Math.floor(Date.now() / (interval * 1000) + 1000);
|
|
69
|
+
const input = `${secretInput}:${timeSlot}`;
|
|
70
|
+
// SHA-256 → hex string
|
|
71
|
+
return crypto_js_1.default.SHA256(input).toString(crypto_js_1.default.enc.Hex);
|
|
72
|
+
};
|
|
73
|
+
exports.generateKey = generateKey;
|
|
74
|
+
/**
|
|
75
|
+
* Encrypt an arbitrary JavaScript value using AES-256-CBC with a random IV.
|
|
76
|
+
*
|
|
77
|
+
* The function:
|
|
78
|
+
* - JSON stringifies the input `data`
|
|
79
|
+
* - Uses the provided hex-encoded key as AES-256 key material
|
|
80
|
+
* - Generates a random 16-byte IV
|
|
81
|
+
* - Encrypts using AES-256-CBC
|
|
82
|
+
* - Returns a concatenated string in the format: "cipherHex:ivHex"
|
|
83
|
+
*
|
|
84
|
+
* @param data - Any serializable JavaScript value (e.g. object, array, string) to encrypt.
|
|
85
|
+
* @param encryptionKeyHex - A 64-character hex string representing a 32-byte key
|
|
86
|
+
* (typically the output of {@link generateKey}).
|
|
87
|
+
* @returns A string in the format `"cipherHex:ivHex"` where both parts are hex-encoded.
|
|
88
|
+
* @throws If encryption fails or JSON serialization fails.
|
|
89
|
+
*/
|
|
90
|
+
const encrypt = (data, encryptionKeyHex) => {
|
|
91
|
+
const json = JSON.stringify(data);
|
|
92
|
+
try {
|
|
93
|
+
// key and IV as WordArray
|
|
94
|
+
const key = crypto_js_1.default.enc.Hex.parse(encryptionKeyHex); // 32 bytes
|
|
95
|
+
const iv = crypto_js_1.default.lib.WordArray.random(16); // 16 bytes
|
|
96
|
+
const encrypted = crypto_js_1.default.AES.encrypt(json, key, { iv });
|
|
97
|
+
const cipherHex = encrypted.ciphertext.toString(crypto_js_1.default.enc.Hex);
|
|
98
|
+
const ivHex = iv.toString(crypto_js_1.default.enc.Hex);
|
|
99
|
+
return `${cipherHex}:${ivHex}`;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.error("Error in encrypt:", err);
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
exports.encrypt = encrypt;
|
|
108
|
+
/**
|
|
109
|
+
* Decrypt an AES-256-CBC ciphertext string produced by {@link encrypt}.
|
|
110
|
+
*
|
|
111
|
+
* The function expects the input to be in the format:
|
|
112
|
+
* "cipherHex:ivHex"
|
|
113
|
+
* where:
|
|
114
|
+
* - cipherHex is the hex-encoded ciphertext
|
|
115
|
+
* - ivHex is the hex-encoded 16-byte IV
|
|
116
|
+
*
|
|
117
|
+
* It will:
|
|
118
|
+
* - Split and parse the hex components
|
|
119
|
+
* - Decrypt using AES-256-CBC with the provided key
|
|
120
|
+
* - Parse the resulting UTF-8 JSON back into the original JavaScript value
|
|
121
|
+
*
|
|
122
|
+
* @param cipherText - The ciphertext string in the format `"cipherHex:ivHex"`.
|
|
123
|
+
* @param encryptionKeyHex - A 64-character hex string representing a 32-byte key
|
|
124
|
+
* (must match the key used for encryption).
|
|
125
|
+
* @returns The decrypted JavaScript value (e.g. object, array, string).
|
|
126
|
+
* @throws If the format is invalid, decryption fails, or the decrypted output is empty/invalid JSON.
|
|
127
|
+
*/
|
|
128
|
+
const decrypt = (cipherText, encryptionKeyHex) => {
|
|
129
|
+
const [encryptedHex, ivHex] = cipherText.split(":");
|
|
130
|
+
if (!ivHex || !encryptedHex) {
|
|
131
|
+
throw new Error("Invalid cipher format, expected cipherHex:ivHex");
|
|
132
|
+
}
|
|
133
|
+
const key = crypto_js_1.default.enc.Hex.parse(encryptionKeyHex);
|
|
134
|
+
const iv = crypto_js_1.default.enc.Hex.parse(ivHex);
|
|
135
|
+
const ciphertext = crypto_js_1.default.enc.Hex.parse(encryptedHex);
|
|
136
|
+
// Proper CipherParams object
|
|
137
|
+
const cipherParams = crypto_js_1.default.lib.CipherParams.create({
|
|
138
|
+
ciphertext,
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
const decrypted = crypto_js_1.default.AES.decrypt(cipherParams, key, { iv });
|
|
142
|
+
const utf8 = decrypted.toString(crypto_js_1.default.enc.Utf8);
|
|
143
|
+
if (!utf8) {
|
|
144
|
+
throw new Error("Decryption produced empty string");
|
|
145
|
+
}
|
|
146
|
+
return JSON.parse(utf8);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
// eslint-disable-next-line no-console
|
|
150
|
+
console.error("Error decrypting with current key:", err);
|
|
151
|
+
throw new Error("Unable to decrypt: no valid key found or invalid payload");
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
exports.decrypt = decrypt;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "timebasedcipher",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Time-based key generation and AES encryption/decryption SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"crypto",
|
|
18
|
+
"aes",
|
|
19
|
+
"encryption",
|
|
20
|
+
"sdk",
|
|
21
|
+
"time-based",
|
|
22
|
+
"cipher",
|
|
23
|
+
"security",
|
|
24
|
+
"expiry",
|
|
25
|
+
"jwt",
|
|
26
|
+
"jwt based cipher"
|
|
27
|
+
],
|
|
28
|
+
"author": "Vipul Naik & Deb Kalyan Mohanty",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/crypto-js": "^4.2.2",
|
|
32
|
+
"@types/node": "^24.10.1",
|
|
33
|
+
"typescript": "^5.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"crypto-js": "^4.2.0"
|
|
37
|
+
}
|
|
38
|
+
}
|