@syldel/crypto-utils 1.0.1 → 1.0.2
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 +54 -6
- package/dist/aes-gcm.util.d.ts +2 -3
- package/dist/aes-gcm.util.js +31 -57
- package/dist/encoding.helper.d.ts +9 -0
- package/dist/encoding.helper.js +25 -0
- package/dist/jwt.util.d.ts +1 -1
- package/dist/jwt.util.js +20 -59
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -1,8 +1,60 @@
|
|
|
1
1
|
# @syldel/crypto-utils
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A lightweight, universal, and dependency-safe cryptographic toolkit for Node.js, Web browsers, and Mobile frameworks (Ionic/Capacitor).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 🚀 Features
|
|
6
|
+
- **Universal Compatibility**: Works everywhere (Node, Angular, React, Ionic, NestJS).
|
|
7
|
+
|
|
8
|
+
- **Zero Node-only Dependencies**: Replaced native crypto and Buffer with cross-platform alternatives.
|
|
9
|
+
|
|
10
|
+
- **AES-256-GCM**: Secure authenticated encryption with integrity verification.
|
|
11
|
+
|
|
12
|
+
- **Pure JWT**: Sign and verify JSON Web Tokens without heavy libraries.
|
|
13
|
+
|
|
14
|
+
## 🛠 Usage
|
|
15
|
+
|
|
16
|
+
### AES-GCM Encryption (Universal)
|
|
17
|
+
Uses node-forge for authenticated encryption. It ensures that if the data is tampered with, decryption will fail.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { AesGcmUtil } from '@syldel/crypto-utils';
|
|
21
|
+
|
|
22
|
+
const key = "your-64-char-hex-key...";
|
|
23
|
+
const message = "Hello World";
|
|
24
|
+
|
|
25
|
+
// Encrypt
|
|
26
|
+
const encrypted = AesGcmUtil.encrypt(message, key);
|
|
27
|
+
console.log(encrypted); // { data: '...', iv: '...', tag: '...' }
|
|
28
|
+
|
|
29
|
+
// Decrypt
|
|
30
|
+
const original = AesGcmUtil.decrypt(encrypted, key);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### JWT Utilities
|
|
34
|
+
Lightweight JWT implementation using crypto-js.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { PureJwtUtil } from '@syldel/crypto-utils';
|
|
38
|
+
|
|
39
|
+
const secret = "my-super-secret";
|
|
40
|
+
const payload = { sub: "123", role: "admin" };
|
|
41
|
+
|
|
42
|
+
// Sign
|
|
43
|
+
const token = PureJwtUtil.sign(payload, secret);
|
|
44
|
+
|
|
45
|
+
// Verify
|
|
46
|
+
try {
|
|
47
|
+
const decoded = PureJwtUtil.verify(token, secret);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error("Invalid or expired token");
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 🔒 Security Principles
|
|
54
|
+
|
|
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
|
+
|
|
57
|
+
- **Isomorphic Helpers**: Custom EncodingHelper uses standard Web APIs (atob/btoa) to handle Base64URL without needing the Node.js Buffer object.
|
|
6
58
|
|
|
7
59
|
## 👨💻 Développement
|
|
8
60
|
|
|
@@ -24,8 +76,6 @@ Le projet utilise **ESLint** pour la logique et **Prettier** pour le formatage.
|
|
|
24
76
|
npx eslint .
|
|
25
77
|
```
|
|
26
78
|
|
|
27
|
-
---
|
|
28
|
-
|
|
29
79
|
## 🔗 Utilisation en local (Development Workflow)
|
|
30
80
|
|
|
31
81
|
Pour utiliser ce package dans tes autres projets sans le publier sur NPM :
|
|
@@ -39,8 +89,6 @@ Pour utiliser ce package dans tes autres projets sans le publier sur NPM :
|
|
|
39
89
|
npm link @syldel/crypto-utils
|
|
40
90
|
```
|
|
41
91
|
|
|
42
|
-
---
|
|
43
|
-
|
|
44
92
|
## 📦 Publication
|
|
45
93
|
|
|
46
94
|
Le projet utilise des **Granular Access Tokens** pour la publication afin de contourner la double authentification (2FA) manuelle tout en maintenant une sécurité maximale.
|
package/dist/aes-gcm.util.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export interface EncryptedData {
|
|
|
4
4
|
tag: string;
|
|
5
5
|
}
|
|
6
6
|
export declare class AesGcmUtil {
|
|
7
|
-
|
|
8
|
-
static
|
|
9
|
-
static decrypt(payload: EncryptedData, key: Buffer | string): string;
|
|
7
|
+
static encrypt(text: string, keyHex: string): EncryptedData;
|
|
8
|
+
static decrypt(payload: EncryptedData, keyHex: string): string;
|
|
10
9
|
}
|
package/dist/aes-gcm.util.js
CHANGED
|
@@ -1,67 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
})();
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
35
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
6
|
exports.AesGcmUtil = void 0;
|
|
37
|
-
const
|
|
7
|
+
const node_forge_1 = __importDefault(require("node-forge"));
|
|
38
8
|
class AesGcmUtil {
|
|
39
|
-
static
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
static encrypt(text, key) {
|
|
47
|
-
const encryptionKey = this.validateKey(key);
|
|
48
|
-
const iv = crypto.randomBytes(12);
|
|
49
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, iv);
|
|
50
|
-
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
51
|
-
encrypted += cipher.final('hex');
|
|
9
|
+
static encrypt(text, keyHex) {
|
|
10
|
+
const key = node_forge_1.default.util.hexToBytes(keyHex);
|
|
11
|
+
const iv = node_forge_1.default.random.getBytesSync(12); // IV standard GCM = 12 octets
|
|
12
|
+
const cipher = node_forge_1.default.cipher.createCipher('AES-GCM', key);
|
|
13
|
+
cipher.start({ iv: iv });
|
|
14
|
+
cipher.update(node_forge_1.default.util.createBuffer(node_forge_1.default.util.encodeUtf8(text)));
|
|
15
|
+
cipher.finish();
|
|
52
16
|
return {
|
|
53
|
-
data:
|
|
54
|
-
iv:
|
|
55
|
-
tag:
|
|
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()),
|
|
56
20
|
};
|
|
57
21
|
}
|
|
58
|
-
static decrypt(payload,
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
22
|
+
static decrypt(payload, keyHex) {
|
|
23
|
+
const key = node_forge_1.default.util.hexToBytes(keyHex);
|
|
24
|
+
const iv = node_forge_1.default.util.hexToBytes(payload.iv);
|
|
25
|
+
const tag = node_forge_1.default.util.hexToBytes(payload.tag);
|
|
26
|
+
const ciphertext = node_forge_1.default.util.hexToBytes(payload.data);
|
|
27
|
+
const decipher = node_forge_1.default.cipher.createDecipher('AES-GCM', key);
|
|
28
|
+
decipher.start({
|
|
29
|
+
iv: iv,
|
|
30
|
+
tag: node_forge_1.default.util.createBuffer(tag),
|
|
31
|
+
});
|
|
32
|
+
decipher.update(node_forge_1.default.util.createBuffer(ciphertext));
|
|
33
|
+
// finish() renvoie 'false' si le tag est invalide (données corrompues ou mauvaise clé)
|
|
34
|
+
const authenticated = decipher.finish();
|
|
35
|
+
if (!authenticated) {
|
|
36
|
+
throw new Error('Decryption failed: authentication failed (bad key or corrupted data)');
|
|
37
|
+
}
|
|
38
|
+
return node_forge_1.default.util.decodeUtf8(decipher.output.getBytes());
|
|
65
39
|
}
|
|
66
40
|
}
|
|
67
41
|
exports.AesGcmUtil = AesGcmUtil;
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
};
|
package/dist/jwt.util.d.ts
CHANGED
package/dist/jwt.util.js
CHANGED
|
@@ -1,73 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
})();
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
35
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
6
|
exports.PureJwtUtil = void 0;
|
|
37
|
-
const
|
|
7
|
+
const crypto_js_1 = __importDefault(require("crypto-js"));
|
|
8
|
+
const encoding_helper_1 = require("./encoding.helper");
|
|
38
9
|
class PureJwtUtil {
|
|
10
|
+
static sign(payload, secret) {
|
|
11
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
12
|
+
const encodedHeader = encoding_helper_1.EncodingHelper.objToBase64Url(header);
|
|
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
19
|
static verify(token, secret) {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
20
|
+
const [headerB64, payloadB64, signatureB64] = token.split('.');
|
|
21
|
+
if (!signatureB64)
|
|
42
22
|
throw new Error('Invalid token format');
|
|
43
|
-
const
|
|
44
|
-
const dataToVerify = `${headerB64}.${payloadB64}`;
|
|
45
|
-
const expectedSignature = crypto
|
|
46
|
-
.createHmac('sha256', secret)
|
|
47
|
-
.update(dataToVerify)
|
|
48
|
-
.digest('base64url');
|
|
23
|
+
const expectedSignature = encoding_helper_1.EncodingHelper.toBase64Url(crypto_js_1.default.HmacSHA256(`${headerB64}.${payloadB64}`, secret).toString(crypto_js_1.default.enc.Base64));
|
|
49
24
|
if (signatureB64 !== expectedSignature) {
|
|
50
25
|
throw new Error('Invalid signature');
|
|
51
26
|
}
|
|
52
|
-
const payload = JSON.parse(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (Date.now() >= payloadRecord.exp * 1000) {
|
|
56
|
-
throw new Error('Token expired');
|
|
57
|
-
}
|
|
27
|
+
const payload = JSON.parse(encoding_helper_1.EncodingHelper.fromBase64(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));
|
|
28
|
+
if (payload.exp && Date.now() >= payload.exp * 1000) {
|
|
29
|
+
throw new Error('Token expired');
|
|
58
30
|
}
|
|
59
31
|
return payload;
|
|
60
32
|
}
|
|
61
|
-
static sign(payload, secret) {
|
|
62
|
-
const header = { alg: 'HS256', typ: 'JWT' };
|
|
63
|
-
const encode = (obj) => Buffer.from(JSON.stringify(obj)).toString('base64url');
|
|
64
|
-
const encodedHeader = encode(header);
|
|
65
|
-
const encodedPayload = encode(payload);
|
|
66
|
-
const signature = crypto
|
|
67
|
-
.createHmac('sha256', secret)
|
|
68
|
-
.update(`${encodedHeader}.${encodedPayload}`)
|
|
69
|
-
.digest('base64url');
|
|
70
|
-
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
71
|
-
}
|
|
72
33
|
}
|
|
73
34
|
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.2",
|
|
4
4
|
"description": "Lightweight JWT and AES-256-GCM utilities",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"license": "ISC",
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@eslint/js": "^10.0.1",
|
|
34
|
+
"@types/crypto-js": "^4.2.2",
|
|
34
35
|
"@types/jest": "^30.0.0",
|
|
36
|
+
"@types/node-forge": "^1.3.14",
|
|
35
37
|
"eslint": "^10.0.1",
|
|
36
38
|
"eslint-config-prettier": "^10.1.8",
|
|
37
39
|
"eslint-plugin-jsonc": "^3.0.0",
|
|
@@ -45,5 +47,9 @@
|
|
|
45
47
|
},
|
|
46
48
|
"overrides": {
|
|
47
49
|
"minimatch": "^10.2.1"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"crypto-js": "^4.2.0",
|
|
53
|
+
"node-forge": "^1.3.3"
|
|
48
54
|
}
|
|
49
55
|
}
|