@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 CHANGED
@@ -1,60 +1,75 @@
1
1
  # @syldel/crypto-utils
2
2
 
3
- A lightweight, universal, and dependency-safe cryptographic toolkit for Node.js, Web browsers, and Mobile frameworks (Ionic/Capacitor).
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
- - **Universal Compatibility**: Works everywhere (Node, Angular, React, Ionic, NestJS).
5
+ ## 🚀 Key Features
6
+ - **Native Security**: Built on top of Node.js crypto (OpenSSL) for hardware-accelerated encryption.
7
7
 
8
- - **Zero Node-only Dependencies**: Replaced native crypto and Buffer with cross-platform alternatives.
8
+ - **AES-256-GCM**: Industry-standard authenticated encryption (AEAD) to ensure both confidentiality and data integrity.
9
9
 
10
- - **AES-256-GCM**: Secure authenticated encryption with integrity verification.
10
+ - **Lightweight JWT**: Pure implementation for signing and verifying tokens without heavy dependencies.
11
11
 
12
- - **Pure JWT**: Sign and verify JSON Web Tokens without heavy libraries.
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 (Universal)
17
- Uses node-forge for authenticated encryption. It ensures that if the data is tampered with, decryption will fail.
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 key = "your-64-char-hex-key...";
23
- const message = "Hello World";
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, key);
27
- console.log(encrypted); // { data: '...', iv: '...', tag: '...' }
26
+ const encrypted = AesGcmUtil.encrypt(message, keyHex);
27
+ // Returns: { data: '...', iv: '...', tag: '...' }
28
28
 
29
29
  // Decrypt
30
- const original = AesGcmUtil.decrypt(encrypted, key);
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
- Lightweight JWT implementation using crypto-js.
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 = "my-super-secret";
40
- const payload = { sub: "123", role: "admin" };
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
- console.error("Invalid or expired token");
53
+ // Handles "Token expired" or "Invalid signature"
50
54
  }
51
55
  ```
52
56
 
53
- ### 🔒 Security Principles
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
- - **Isomorphic Helpers**: Custom EncodingHelper uses standard Web APIs (atob/btoa) to handle Base64URL without needing the Node.js Buffer object.
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
 
@@ -4,6 +4,14 @@ export interface EncryptedData {
4
4
  tag: string;
5
5
  }
6
6
  export declare class AesGcmUtil {
7
- static encrypt(text: string, keyHex: string): EncryptedData;
8
- static decrypt(payload: EncryptedData, keyHex: string): string;
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
  }
@@ -1,41 +1,84 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
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 node_forge_1 = __importDefault(require("node-forge"));
37
+ const crypto = __importStar(require("crypto"));
8
38
  class AesGcmUtil {
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();
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
- 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)');
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';
@@ -1,4 +1,10 @@
1
1
  export declare class PureJwtUtil {
2
- static sign(payload: Record<string, any>, secret: string): string;
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 __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
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 crypto_js_1 = __importDefault(require("crypto-js"));
8
- const encoding_helper_1 = require("./encoding.helper");
37
+ const crypto = __importStar(require("crypto"));
9
38
  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
+ /**
40
+ * Verifies a JWT's signature and expiration, then returns the decoded payload.
41
+ */
19
42
  static verify(token, secret) {
20
- const [headerB64, payloadB64, signatureB64] = token.split('.');
21
- if (!signatureB64)
43
+ const parts = token.split('.');
44
+ if (parts.length !== 3)
22
45
  throw new Error('Invalid token format');
23
- const expectedSignature = encoding_helper_1.EncodingHelper.toBase64Url(crypto_js_1.default.HmacSHA256(`${headerB64}.${payloadB64}`, secret).toString(crypto_js_1.default.enc.Base64));
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(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');
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.2",
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
- };
@@ -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
- };