@syldel/crypto-utils 1.0.2 → 1.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.
- package/README.md +39 -24
- package/dist/aes-gcm.util.d.ts +10 -2
- package/dist/aes-gcm.util.js +75 -32
- package/dist/jwt.util.d.ts +7 -1
- package/dist/jwt.util.js +65 -20
- package/package.json +1 -7
- package/dist/encoding.helper.d.ts +0 -9
- package/dist/encoding.helper.js +0 -25
package/README.md
CHANGED
|
@@ -1,60 +1,75 @@
|
|
|
1
1
|
# @syldel/crypto-utils
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A robust, high-performance cryptographic toolkit for Node.js and NestJS applications. This package prioritizes security by leveraging Node's native crypto module (OpenSSL) for maximum protection of sensitive data and private keys.
|
|
4
4
|
|
|
5
|
-
## 🚀 Features
|
|
6
|
-
|
|
5
|
+
## 🚀 Key Features
|
|
6
|
+
- **Native Security**: Built on top of Node.js crypto (OpenSSL) for hardware-accelerated encryption.
|
|
7
7
|
|
|
8
|
-
- **
|
|
8
|
+
- **AES-256-GCM**: Industry-standard authenticated encryption (AEAD) to ensure both confidentiality and data integrity.
|
|
9
9
|
|
|
10
|
-
- **
|
|
10
|
+
- **Lightweight JWT**: Pure implementation for signing and verifying tokens without heavy dependencies.
|
|
11
11
|
|
|
12
|
-
- **
|
|
12
|
+
- **Standardized Error Handling**: Harmonized error messages ("Decryption failed") for better security and predictable testing.
|
|
13
13
|
|
|
14
14
|
## 🛠 Usage
|
|
15
15
|
|
|
16
|
-
### AES-GCM Encryption
|
|
17
|
-
|
|
16
|
+
### AES-256-GCM (Authenticated Encryption)
|
|
17
|
+
Designed for high-security environments. It automatically handles Initialization Vectors (IV) and Authentication Tags.
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
20
|
import { AesGcmUtil } from '@syldel/crypto-utils';
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
const message = "
|
|
22
|
+
const keyHex = "0123456789abcdef..."; // 64 hex characters (32 bytes)
|
|
23
|
+
const message = "Sensitive data";
|
|
24
24
|
|
|
25
25
|
// Encrypt
|
|
26
|
-
const encrypted = AesGcmUtil.encrypt(message,
|
|
27
|
-
|
|
26
|
+
const encrypted = AesGcmUtil.encrypt(message, keyHex);
|
|
27
|
+
// Returns: { data: '...', iv: '...', tag: '...' }
|
|
28
28
|
|
|
29
29
|
// Decrypt
|
|
30
|
-
|
|
30
|
+
try {
|
|
31
|
+
const original = AesGcmUtil.decrypt(encrypted, keyHex);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Throws "Decryption failed" if data is tampered or key is wrong
|
|
34
|
+
}
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
### JWT Utilities
|
|
34
|
-
|
|
37
|
+
### Pure JWT Utilities
|
|
38
|
+
A lightweight way to handle JWTs in your NestJS guards or services.
|
|
35
39
|
|
|
36
40
|
```ts
|
|
37
41
|
import { PureJwtUtil } from '@syldel/crypto-utils';
|
|
38
42
|
|
|
39
|
-
const secret = "
|
|
40
|
-
const payload = { sub: "
|
|
43
|
+
const secret = "your-secure-secret";
|
|
44
|
+
const payload = { sub: "user_123", role: "admin" };
|
|
41
45
|
|
|
42
|
-
// Sign
|
|
46
|
+
// Sign a token
|
|
43
47
|
const token = PureJwtUtil.sign(payload, secret);
|
|
44
48
|
|
|
45
|
-
// Verify
|
|
49
|
+
// Verify and decode
|
|
46
50
|
try {
|
|
47
|
-
const decoded = PureJwtUtil.verify(token, secret);
|
|
51
|
+
const decoded = PureJwtUtil.verify<MyUserType>(token, secret);
|
|
48
52
|
} catch (err) {
|
|
49
|
-
|
|
53
|
+
// Handles "Token expired" or "Invalid signature"
|
|
50
54
|
}
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
## 🔒 Security & Performance
|
|
58
|
+
|
|
59
|
+
- **Zero External Crypto Libs**: By using native Node.js APIs, this package minimizes the attack surface and dependency supply chain risks.
|
|
60
|
+
|
|
61
|
+
- **Side-Channel Protection**: Leverages OpenSSL's C++ implementation to protect against timing attacks.
|
|
62
|
+
|
|
63
|
+
- **GCM Integrity**: Unlike AES-CTR or CBC, AES-GCM ensures that any single-bit modification to the encrypted data will be detected during decryption.
|
|
54
64
|
|
|
55
|
-
- **Integrity First**: Using AES-GCM (via Node-Forge) ensures that any modification to the ciphertext or the tag results in a decryption error, preventing "padding oracle" and bit-flipping attacks.
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
## 🧪 Testing
|
|
67
|
+
|
|
68
|
+
Full test suite with 100% coverage on core crypto logic.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm run test
|
|
72
|
+
```
|
|
58
73
|
|
|
59
74
|
## 👨💻 Développement
|
|
60
75
|
|
package/dist/aes-gcm.util.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ export interface EncryptedData {
|
|
|
4
4
|
tag: string;
|
|
5
5
|
}
|
|
6
6
|
export declare class AesGcmUtil {
|
|
7
|
-
static
|
|
8
|
-
static
|
|
7
|
+
private static readonly ALGORITHM;
|
|
8
|
+
private static validateKey;
|
|
9
|
+
/**
|
|
10
|
+
* Encrypts text using native Node.js crypto (AES-256-GCM)
|
|
11
|
+
*/
|
|
12
|
+
static encrypt(text: string, key: Buffer | string): EncryptedData;
|
|
13
|
+
/**
|
|
14
|
+
* Decrypts a payload using native Node.js crypto (AES-256-GCM)
|
|
15
|
+
*/
|
|
16
|
+
static decrypt(payload: EncryptedData, key: Buffer | string): string;
|
|
9
17
|
}
|
package/dist/aes-gcm.util.js
CHANGED
|
@@ -1,41 +1,84 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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]; } };
|
|
7
|
+
}
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
5
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
36
|
exports.AesGcmUtil = void 0;
|
|
7
|
-
const
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
8
38
|
class AesGcmUtil {
|
|
9
|
-
static
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
cipher.finish();
|
|
16
|
-
return {
|
|
17
|
-
data: node_forge_1.default.util.bytesToHex(cipher.output.getBytes()),
|
|
18
|
-
iv: node_forge_1.default.util.bytesToHex(iv),
|
|
19
|
-
tag: node_forge_1.default.util.bytesToHex(cipher.mode.tag.getBytes()),
|
|
20
|
-
};
|
|
39
|
+
static validateKey(key) {
|
|
40
|
+
const encryptionKey = typeof key === 'string' ? Buffer.from(key, 'hex') : key;
|
|
41
|
+
if (encryptionKey.length !== 32) {
|
|
42
|
+
throw new Error('Invalid Key: Key must be 32 bytes (64 hex characters)');
|
|
43
|
+
}
|
|
44
|
+
return encryptionKey;
|
|
21
45
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Encrypts text using native Node.js crypto (AES-256-GCM)
|
|
48
|
+
*/
|
|
49
|
+
static encrypt(text, key) {
|
|
50
|
+
try {
|
|
51
|
+
const encryptionKey = this.validateKey(key);
|
|
52
|
+
const iv = crypto.randomBytes(12);
|
|
53
|
+
const cipher = crypto.createCipheriv(this.ALGORITHM, encryptionKey, iv);
|
|
54
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
55
|
+
encrypted += cipher.final('hex');
|
|
56
|
+
return {
|
|
57
|
+
data: encrypted,
|
|
58
|
+
iv: iv.toString('hex'),
|
|
59
|
+
tag: cipher.getAuthTag().toString('hex'),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error('Encryption failed');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Decrypts a payload using native Node.js crypto (AES-256-GCM)
|
|
68
|
+
*/
|
|
69
|
+
static decrypt(payload, key) {
|
|
70
|
+
try {
|
|
71
|
+
const encryptionKey = this.validateKey(key);
|
|
72
|
+
const decipher = crypto.createDecipheriv(this.ALGORITHM, encryptionKey, Buffer.from(payload.iv, 'hex'));
|
|
73
|
+
decipher.setAuthTag(Buffer.from(payload.tag, 'hex'));
|
|
74
|
+
let decrypted = decipher.update(payload.data, 'hex', 'utf8');
|
|
75
|
+
decrypted += decipher.final('utf8');
|
|
76
|
+
return decrypted;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error('Decryption failed');
|
|
37
80
|
}
|
|
38
|
-
return node_forge_1.default.util.decodeUtf8(decipher.output.getBytes());
|
|
39
81
|
}
|
|
40
82
|
}
|
|
41
83
|
exports.AesGcmUtil = AesGcmUtil;
|
|
84
|
+
AesGcmUtil.ALGORITHM = 'aes-256-gcm';
|
package/dist/jwt.util.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export declare class PureJwtUtil {
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Verifies a JWT's signature and expiration, then returns the decoded payload.
|
|
4
|
+
*/
|
|
3
5
|
static verify<T extends object>(token: string, secret: string): T;
|
|
6
|
+
/**
|
|
7
|
+
* Signs a payload and returns a compact JWT string.
|
|
8
|
+
*/
|
|
9
|
+
static sign(payload: Record<string, any>, secret: string): string;
|
|
4
10
|
}
|
package/dist/jwt.util.js
CHANGED
|
@@ -1,34 +1,79 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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]; } };
|
|
7
|
+
}
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
5
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
36
|
exports.PureJwtUtil = void 0;
|
|
7
|
-
const
|
|
8
|
-
const encoding_helper_1 = require("./encoding.helper");
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
9
38
|
class PureJwtUtil {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const encodedPayload = encoding_helper_1.EncodingHelper.objToBase64Url(payload);
|
|
14
|
-
const signature = crypto_js_1.default.HmacSHA256(`${encodedHeader}.${encodedPayload}`, secret);
|
|
15
|
-
// Conversion du WordArray CryptoJS en Base64URL
|
|
16
|
-
const encodedSignature = encoding_helper_1.EncodingHelper.toBase64Url(signature.toString(crypto_js_1.default.enc.Base64));
|
|
17
|
-
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
|
18
|
-
}
|
|
39
|
+
/**
|
|
40
|
+
* Verifies a JWT's signature and expiration, then returns the decoded payload.
|
|
41
|
+
*/
|
|
19
42
|
static verify(token, secret) {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
43
|
+
const parts = token.split('.');
|
|
44
|
+
if (parts.length !== 3)
|
|
22
45
|
throw new Error('Invalid token format');
|
|
23
|
-
const
|
|
46
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
47
|
+
const dataToVerify = `${headerB64}.${payloadB64}`;
|
|
48
|
+
const expectedSignature = crypto
|
|
49
|
+
.createHmac('sha256', secret)
|
|
50
|
+
.update(dataToVerify)
|
|
51
|
+
.digest('base64url');
|
|
24
52
|
if (signatureB64 !== expectedSignature) {
|
|
25
53
|
throw new Error('Invalid signature');
|
|
26
54
|
}
|
|
27
|
-
const payload = JSON.parse(
|
|
28
|
-
|
|
29
|
-
|
|
55
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'));
|
|
56
|
+
const payloadRecord = payload;
|
|
57
|
+
if (typeof payloadRecord.exp === 'number') {
|
|
58
|
+
if (Date.now() >= payloadRecord.exp * 1000) {
|
|
59
|
+
throw new Error('Token expired');
|
|
60
|
+
}
|
|
30
61
|
}
|
|
31
62
|
return payload;
|
|
32
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Signs a payload and returns a compact JWT string.
|
|
66
|
+
*/
|
|
67
|
+
static sign(payload, secret) {
|
|
68
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
69
|
+
const encode = (obj) => Buffer.from(JSON.stringify(obj)).toString('base64url');
|
|
70
|
+
const encodedHeader = encode(header);
|
|
71
|
+
const encodedPayload = encode(payload);
|
|
72
|
+
const signature = crypto
|
|
73
|
+
.createHmac('sha256', secret)
|
|
74
|
+
.update(`${encodedHeader}.${encodedPayload}`)
|
|
75
|
+
.digest('base64url');
|
|
76
|
+
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
77
|
+
}
|
|
33
78
|
}
|
|
34
79
|
exports.PureJwtUtil = PureJwtUtil;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syldel/crypto-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Lightweight JWT and AES-256-GCM utilities",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -31,9 +31,7 @@
|
|
|
31
31
|
"license": "ISC",
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@eslint/js": "^10.0.1",
|
|
34
|
-
"@types/crypto-js": "^4.2.2",
|
|
35
34
|
"@types/jest": "^30.0.0",
|
|
36
|
-
"@types/node-forge": "^1.3.14",
|
|
37
35
|
"eslint": "^10.0.1",
|
|
38
36
|
"eslint-config-prettier": "^10.1.8",
|
|
39
37
|
"eslint-plugin-jsonc": "^3.0.0",
|
|
@@ -47,9 +45,5 @@
|
|
|
47
45
|
},
|
|
48
46
|
"overrides": {
|
|
49
47
|
"minimatch": "^10.2.1"
|
|
50
|
-
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"crypto-js": "^4.2.0",
|
|
53
|
-
"node-forge": "^1.3.3"
|
|
54
48
|
}
|
|
55
49
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Encode et décode en Base64URL sans dépendance à Buffer (Universal JS)
|
|
3
|
-
*/
|
|
4
|
-
export declare const EncodingHelper: {
|
|
5
|
-
toBase64: (str: string) => string;
|
|
6
|
-
fromBase64: (base64: string) => string;
|
|
7
|
-
toBase64Url: (base64: string) => string;
|
|
8
|
-
objToBase64Url: (obj: object) => string;
|
|
9
|
-
};
|
package/dist/encoding.helper.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EncodingHelper = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Encode et décode en Base64URL sans dépendance à Buffer (Universal JS)
|
|
6
|
-
*/
|
|
7
|
-
exports.EncodingHelper = {
|
|
8
|
-
// Texte -> Base64
|
|
9
|
-
toBase64: (str) => {
|
|
10
|
-
return btoa(unescape(encodeURIComponent(str)));
|
|
11
|
-
},
|
|
12
|
-
// Base64 -> Texte
|
|
13
|
-
fromBase64: (base64) => {
|
|
14
|
-
return decodeURIComponent(escape(atob(base64)));
|
|
15
|
-
},
|
|
16
|
-
// Base64 -> Base64URL (pour JWT)
|
|
17
|
-
toBase64Url: (base64) => {
|
|
18
|
-
return base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
19
|
-
},
|
|
20
|
-
// Object -> Base64URL
|
|
21
|
-
objToBase64Url: (obj) => {
|
|
22
|
-
const str = JSON.stringify(obj);
|
|
23
|
-
return exports.EncodingHelper.toBase64Url(exports.EncodingHelper.toBase64(str));
|
|
24
|
-
},
|
|
25
|
-
};
|