@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 CHANGED
@@ -1,8 +1,60 @@
1
1
  # @syldel/crypto-utils
2
2
 
3
- Lightweight JWT and AES-256-GCM utilities
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.
@@ -4,7 +4,6 @@ export interface EncryptedData {
4
4
  tag: string;
5
5
  }
6
6
  export declare class AesGcmUtil {
7
- private static validateKey;
8
- static encrypt(text: string, key: Buffer | string): EncryptedData;
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
  }
@@ -1,67 +1,41 @@
1
1
  "use strict";
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
- })();
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 crypto = __importStar(require("crypto"));
7
+ const node_forge_1 = __importDefault(require("node-forge"));
38
8
  class AesGcmUtil {
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;
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: encrypted,
54
- iv: iv.toString('hex'),
55
- tag: cipher.getAuthTag().toString('hex'),
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, key) {
59
- const encryptionKey = this.validateKey(key);
60
- const decipher = crypto.createDecipheriv('aes-256-gcm', encryptionKey, Buffer.from(payload.iv, 'hex'));
61
- decipher.setAuthTag(Buffer.from(payload.tag, 'hex'));
62
- let decrypted = decipher.update(payload.data, 'hex', 'utf8');
63
- decrypted += decipher.final('utf8');
64
- return decrypted;
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
+ };
@@ -1,4 +1,4 @@
1
1
  export declare class PureJwtUtil {
2
- static verify<T extends object>(token: string, secret: string): T;
3
2
  static sign(payload: Record<string, any>, secret: string): string;
3
+ static verify<T extends object>(token: string, secret: string): T;
4
4
  }
package/dist/jwt.util.js CHANGED
@@ -1,73 +1,34 @@
1
1
  "use strict";
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
- })();
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 crypto = __importStar(require("crypto"));
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 parts = token.split('.');
41
- if (parts.length !== 3)
20
+ const [headerB64, payloadB64, signatureB64] = token.split('.');
21
+ if (!signatureB64)
42
22
  throw new Error('Invalid token format');
43
- const [headerB64, payloadB64, signatureB64] = parts;
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(Buffer.from(payloadB64, 'base64url').toString('utf8'));
53
- const payloadRecord = payload;
54
- if (typeof payloadRecord.exp === 'number') {
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.1",
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
  }