secure-comms-hybrid 1.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/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # ๐Ÿ” Secure Comms Hybrid
2
+
3
+ Hybrid encryption (RSA + AES-GCM) for secure client-server communications.
4
+
5
+ ## Features
6
+
7
+ - โœ… **Hybrid Encryption**: RSA-2048 for key exchange + AES-GCM-256 for data
8
+ - โœ… **Perfect Forward Secrecy**: Unique session keys
9
+ - โœ… **Zero Configuration**: Easy setup for Express.js
10
+ - โœ… **Browser & Node.js**: Universal support
11
+ - โœ… **TypeScript**: Full type definitions
12
+ - โœ… **Middleware**: Drop-in encryption for existing APIs
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install secure-comms-hybrid
@@ -0,0 +1,175 @@
1
+ const crypto = require('crypto');
2
+ const {
3
+ CRYPTO_CONFIG,
4
+ ERRORS
5
+ } = require('../shared/constants');
6
+ class HybridCrypto {
7
+ constructor(options = {}) {
8
+ this.options = {
9
+ rsaKeySize: options.rsaKeySize || CRYPTO_CONFIG.RSA.MODULUS_LENGTH,
10
+ sessionExpiry: options.sessionExpiry || CRYPTO_CONFIG.SESSION.EXPIRY_MS,
11
+ ...options
12
+ };
13
+
14
+ // Generate or use provided RSA keys
15
+ if (options.rsaPrivateKey && options.rsaPublicKey) {
16
+ this.rsaPrivateKey = options.rsaPrivateKey;
17
+ this.rsaPublicKey = options.rsaPublicKey;
18
+ } else {
19
+ this.generateRSAKeys();
20
+ }
21
+
22
+ // Session storage
23
+ this.sessionKeys = new Map();
24
+ this.sessionCleanupInterval = setInterval(() => this.cleanupExpiredSessions(), CRYPTO_CONFIG.SESSION.CLEANUP_INTERVAL);
25
+ console.log('๐Ÿ” HybridCrypto initialized');
26
+ }
27
+ generateRSAKeys() {
28
+ const {
29
+ publicKey,
30
+ privateKey
31
+ } = crypto.generateKeyPairSync('rsa', {
32
+ modulusLength: this.options.rsaKeySize,
33
+ publicKeyEncoding: CRYPTO_CONFIG.RSA.PUBLIC_KEY_ENCODING,
34
+ privateKeyEncoding: CRYPTO_CONFIG.RSA.PRIVATE_KEY_ENCODING
35
+ });
36
+ this.rsaPublicKey = publicKey;
37
+ this.rsaPrivateKey = privateKey;
38
+ }
39
+ getPublicKey() {
40
+ return {
41
+ publicKey: this.rsaPublicKey,
42
+ algorithm: 'RSA-OAEP',
43
+ hash: CRYPTO_CONFIG.RSA.HASH,
44
+ keySize: this.options.rsaKeySize,
45
+ timestamp: new Date().toISOString()
46
+ };
47
+ }
48
+ decryptAESKey(encryptedAESKey) {
49
+ try {
50
+ const decrypted = crypto.privateDecrypt({
51
+ key: this.rsaPrivateKey,
52
+ padding: crypto.constants[CRYPTO_CONFIG.RSA.PADDING],
53
+ oaepHash: 'sha256'
54
+ }, Buffer.from(encryptedAESKey, 'base64'));
55
+ return decrypted;
56
+ } catch (error) {
57
+ throw new Error(`${ERRORS.DECRYPTION_FAILED}: ${error.message}`);
58
+ }
59
+ }
60
+ storeSessionKey(clientId, aesKey, customExpiry = null) {
61
+ const expiresAt = Date.now() + (customExpiry || this.options.sessionExpiry);
62
+ this.sessionKeys.set(clientId, {
63
+ aesKey,
64
+ expiresAt,
65
+ createdAt: Date.now()
66
+ });
67
+ return expiresAt;
68
+ }
69
+ getSessionKey(clientId) {
70
+ const session = this.sessionKeys.get(clientId);
71
+ if (!session) {
72
+ throw new Error(ERRORS.SESSION_EXPIRED);
73
+ }
74
+ if (Date.now() > session.expiresAt) {
75
+ this.sessionKeys.delete(clientId);
76
+ throw new Error(ERRORS.SESSION_EXPIRED);
77
+ }
78
+ return session.aesKey;
79
+ }
80
+ cleanupExpiredSessions() {
81
+ const now = Date.now();
82
+ let cleaned = 0;
83
+ for (const [clientId, session] of this.sessionKeys.entries()) {
84
+ if (now > session.expiresAt) {
85
+ this.sessionKeys.delete(clientId);
86
+ cleaned++;
87
+ }
88
+ }
89
+ if (cleaned > 0) {
90
+ console.log(`๐Ÿงน Cleaned ${cleaned} expired sessions`);
91
+ }
92
+ }
93
+ async decryptData(ciphertextBase64, ivBase64, authTagBase64, clientId) {
94
+ try {
95
+ const aesKey = this.getSessionKey(clientId);
96
+ const ciphertext = Buffer.from(ciphertextBase64, 'base64');
97
+ const iv = Buffer.from(ivBase64, 'base64');
98
+ const authTag = Buffer.from(authTagBase64, 'base64');
99
+ const decipher = crypto.createDecipheriv(CRYPTO_CONFIG.AES.ALGORITHM.toLowerCase(), aesKey, iv);
100
+ decipher.setAuthTag(authTag);
101
+ let decrypted = decipher.update(ciphertext, null, 'utf8');
102
+ decrypted += decipher.final('utf8');
103
+ return {
104
+ success: true,
105
+ data: JSON.parse(decrypted),
106
+ raw: decrypted,
107
+ timestamp: new Date().toISOString()
108
+ };
109
+ } catch (error) {
110
+ return {
111
+ success: false,
112
+ error: error.message,
113
+ timestamp: new Date().toISOString()
114
+ };
115
+ }
116
+ }
117
+ async encryptData(data, clientId) {
118
+ try {
119
+ const aesKey = this.getSessionKey(clientId);
120
+ const iv = crypto.randomBytes(CRYPTO_CONFIG.AES.IV_LENGTH);
121
+ const cipher = crypto.createCipheriv(CRYPTO_CONFIG.AES.ALGORITHM.toLowerCase(), aesKey, iv);
122
+ let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'base64');
123
+ encrypted += cipher.final('base64');
124
+ const authTag = cipher.getAuthTag();
125
+ return {
126
+ success: true,
127
+ ciphertext: encrypted,
128
+ iv: iv.toString('base64'),
129
+ authTag: authTag.toString('base64'),
130
+ algorithm: `${CRYPTO_CONFIG.AES.ALGORITHM}-${CRYPTO_CONFIG.AES.KEY_LENGTH}`,
131
+ timestamp: new Date().toISOString()
132
+ };
133
+ } catch (error) {
134
+ return {
135
+ success: false,
136
+ error: error.message,
137
+ timestamp: new Date().toISOString()
138
+ };
139
+ }
140
+ }
141
+ async processHandshake(handshakeData, clientId, customExpiry = null) {
142
+ try {
143
+ const aesKey = this.decryptAESKey(handshakeData.encryptedAESKey);
144
+ const expiresAt = this.storeSessionKey(clientId, aesKey, customExpiry);
145
+ const clientIV = crypto.randomBytes(CRYPTO_CONFIG.AES.IV_LENGTH);
146
+ return {
147
+ success: true,
148
+ message: 'Handshake successful',
149
+ clientIV: clientIV.toString('base64'),
150
+ sessionExpiry: expiresAt - Date.now(),
151
+ expiresAt,
152
+ timestamp: new Date().toISOString()
153
+ };
154
+ } catch (error) {
155
+ return {
156
+ success: false,
157
+ error: error.message,
158
+ timestamp: new Date().toISOString()
159
+ };
160
+ }
161
+ }
162
+ destroy() {
163
+ clearInterval(this.sessionCleanupInterval);
164
+ this.sessionKeys.clear();
165
+ }
166
+ getStats() {
167
+ return {
168
+ activeSessions: this.sessionKeys.size,
169
+ rsaKeySize: this.options.rsaKeySize,
170
+ sessionExpiry: this.options.sessionExpiry,
171
+ uptime: process.uptime()
172
+ };
173
+ }
174
+ }
175
+ module.exports = HybridCrypto;
@@ -0,0 +1,23 @@
1
+ const HybridCrypto = require('./HybridCrypto');
2
+ const middleware = require('./middleware');
3
+ module.exports = {
4
+ HybridCrypto,
5
+ ...middleware,
6
+ // Quick setup function
7
+ createHybridCrypto: (options = {}) => {
8
+ const crypto = new HybridCrypto(options);
9
+ const {
10
+ createExpressMiddleware
11
+ } = middleware;
12
+ return {
13
+ crypto,
14
+ expressMiddleware: createExpressMiddleware(crypto, options.middlewareOptions),
15
+ getPublicKey: () => crypto.getPublicKey(),
16
+ getStats: () => crypto.getStats(),
17
+ destroy: () => crypto.destroy()
18
+ };
19
+ },
20
+ // Version info
21
+ version: '1.0.0',
22
+ description: 'Hybrid encryption for secure communications'
23
+ };
@@ -0,0 +1,214 @@
1
+ const {
2
+ CRYPTO_CONFIG,
3
+ ERRORS
4
+ } = require('../shared/constants');
5
+ function createEncryptionMiddleware(hybridCrypto, options = {}) {
6
+ const {
7
+ excludedPaths = ['/health', '/'],
8
+ requireEncryption = false,
9
+ logLevel = 'info'
10
+ } = options;
11
+
12
+ // Decryption middleware
13
+ const decryptRequest = (req, res, next) => {
14
+ // Check if path is excluded
15
+ if (excludedPaths.includes(req.path)) {
16
+ return next();
17
+ }
18
+ const isEncrypted = req.headers[CRYPTO_CONFIG.HEADERS.ENCRYPTED] === 'true';
19
+ const clientId = req.headers[CRYPTO_CONFIG.HEADERS.CLIENT_ID];
20
+ if (!isEncrypted) {
21
+ if (requireEncryption) {
22
+ return res.status(400).json({
23
+ success: false,
24
+ error: 'Encryption required',
25
+ timestamp: new Date().toISOString()
26
+ });
27
+ }
28
+ return next();
29
+ }
30
+ if (!clientId) {
31
+ return res.status(400).json({
32
+ success: false,
33
+ error: ERRORS.MISSING_CLIENT_ID,
34
+ timestamp: new Date().toISOString()
35
+ });
36
+ }
37
+ let rawBody = '';
38
+ req.on('data', chunk => {
39
+ rawBody += chunk.toString();
40
+ });
41
+ req.on('end', async () => {
42
+ try {
43
+ if (!rawBody) {
44
+ return res.status(400).json({
45
+ success: false,
46
+ error: 'Empty request body',
47
+ timestamp: new Date().toISOString()
48
+ });
49
+ }
50
+ let parsedBody;
51
+ try {
52
+ parsedBody = JSON.parse(rawBody);
53
+ } catch (error) {
54
+ return res.status(400).json({
55
+ success: false,
56
+ error: 'Invalid JSON',
57
+ timestamp: new Date().toISOString()
58
+ });
59
+ }
60
+ if (!parsedBody.ciphertext || !parsedBody.iv || !parsedBody.authTag) {
61
+ return res.status(400).json({
62
+ success: false,
63
+ error: ERRORS.INVALID_REQUEST,
64
+ required: ['ciphertext', 'iv', 'authTag'],
65
+ timestamp: new Date().toISOString()
66
+ });
67
+ }
68
+ const decrypted = await hybridCrypto.decryptData(parsedBody.ciphertext, parsedBody.iv, parsedBody.authTag, clientId);
69
+ if (!decrypted.success) {
70
+ return res.status(400).json({
71
+ success: false,
72
+ error: ERRORS.DECRYPTION_FAILED,
73
+ details: decrypted.error,
74
+ timestamp: new Date().toISOString()
75
+ });
76
+ }
77
+ req.originalEncryptedBody = parsedBody;
78
+ req.body = decrypted.data;
79
+ req.encryptionMetadata = {
80
+ type: 'hybrid',
81
+ clientId,
82
+ decryptedAt: decrypted.timestamp,
83
+ algorithm: 'RSA-AES-GCM'
84
+ };
85
+ next();
86
+ } catch (error) {
87
+ console.error('โŒ Decryption middleware error:', error);
88
+ return res.status(500).json({
89
+ success: false,
90
+ error: 'Internal server error',
91
+ timestamp: new Date().toISOString()
92
+ });
93
+ }
94
+ });
95
+ };
96
+
97
+ // Encryption middleware
98
+ const encryptResponse = (req, res, next) => {
99
+ const wantsEncryption = req.headers[CRYPTO_CONFIG.HEADERS.ENCRYPTED] === 'true';
100
+ const clientId = req.headers[CRYPTO_CONFIG.HEADERS.CLIENT_ID];
101
+ if (!wantsEncryption || !clientId || excludedPaths.includes(req.path)) {
102
+ return next();
103
+ }
104
+ const originalJson = res.json;
105
+ res.json = async function (data) {
106
+ try {
107
+ if (res.statusCode >= 400) {
108
+ return originalJson.call(this, data);
109
+ }
110
+ const encrypted = await hybridCrypto.encryptData(data, clientId);
111
+ if (!encrypted.success) {
112
+ throw new Error(`${ERRORS.ENCRYPTION_FAILED}: ${encrypted.error}`);
113
+ }
114
+ const encryptedResponse = {
115
+ success: true,
116
+ encrypted: true,
117
+ algorithm: encrypted.algorithm,
118
+ ciphertext: encrypted.ciphertext,
119
+ iv: encrypted.iv,
120
+ authTag: encrypted.authTag,
121
+ timestamp: encrypted.timestamp,
122
+ metadata: {
123
+ clientId,
124
+ originalDataType: typeof data
125
+ }
126
+ };
127
+ res.setHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTED, 'true');
128
+ res.setHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTION_ALGORITHM, 'RSA-AES-GCM');
129
+ res.setHeader(CRYPTO_CONFIG.HEADERS.CLIENT_ID, clientId);
130
+ return originalJson.call(this, encryptedResponse);
131
+ } catch (error) {
132
+ console.error('โŒ Response encryption failed:', error);
133
+ res.removeHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTED);
134
+ res.removeHeader(CRYPTO_CONFIG.HEADERS.ENCRYPTION_ALGORITHM);
135
+ return originalJson.call(this, {
136
+ success: false,
137
+ error: ERRORS.ENCRYPTION_FAILED,
138
+ message: error.message,
139
+ fallbackData: data,
140
+ encrypted: false,
141
+ timestamp: new Date().toISOString()
142
+ });
143
+ }
144
+ };
145
+ next();
146
+ };
147
+ return {
148
+ decryptRequest,
149
+ encryptResponse,
150
+ middleware: [decryptRequest, encryptResponse]
151
+ };
152
+ }
153
+
154
+ // Express.js specific middleware factory
155
+ function createExpressMiddleware(hybridCrypto, options = {}) {
156
+ const {
157
+ decryptRequest,
158
+ encryptResponse
159
+ } = createEncryptionMiddleware(hybridCrypto, options);
160
+ return function (req, res, next) {
161
+ // Apply decryption
162
+ decryptRequest(req, res, err => {
163
+ if (err) return next(err);
164
+ // Apply encryption
165
+ encryptResponse(req, res, next);
166
+ });
167
+ };
168
+ }
169
+
170
+ // Koa.js middleware
171
+ function createKoaMiddleware(hybridCrypto, options = {}) {
172
+ const {
173
+ decryptRequest,
174
+ encryptResponse
175
+ } = createEncryptionMiddleware(hybridCrypto, options);
176
+ return async (ctx, next) => {
177
+ // Convert Koa context to Express-like req/res
178
+ const req = ctx.request;
179
+ const res = {
180
+ json: data => {
181
+ ctx.body = data;
182
+ ctx.type = 'application/json';
183
+ },
184
+ status: code => {
185
+ ctx.status = code;
186
+ return res;
187
+ },
188
+ setHeader: (name, value) => {
189
+ ctx.set(name, value);
190
+ },
191
+ removeHeader: name => {
192
+ ctx.remove(name);
193
+ }
194
+ };
195
+ await new Promise(resolve => {
196
+ decryptRequest(req, res, err => {
197
+ if (err) throw err;
198
+ resolve();
199
+ });
200
+ });
201
+ await next();
202
+ if (ctx.headers[CRYPTO_CONFIG.HEADERS.ENCRYPTED] === 'true') {
203
+ await new Promise(resolve => {
204
+ encryptResponse(req, res, () => resolve());
205
+ });
206
+ }
207
+ };
208
+ }
209
+ module.exports = {
210
+ createEncryptionMiddleware,
211
+ createExpressMiddleware,
212
+ createKoaMiddleware,
213
+ HybridCrypto: require('./HybridCrypto')
214
+ };
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "secure-comms-hybrid",
3
+ "version": "1.0.0",
4
+ "description": "Hybrid encryption (RSA + AES) for secure client-server communications",
5
+ "main": "lib/backend/index.js",
6
+ "browser": "dist/frontend/index.js",
7
+ "module": "src/frontend/index.js",
8
+ "types": "types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "node": "./lib/backend/index.js",
12
+ "browser": "./dist/frontend/index.js",
13
+ "import": "./lib/frontend/index.js",
14
+ "require": "./lib/backend/index.js"
15
+ },
16
+ "./backend": "./lib/backend/index.js",
17
+ "./frontend": "./dist/frontend/index.js",
18
+ "./express": "./lib/backend/middleware.js"
19
+ },
20
+ "scripts": {
21
+ "build": "npm run build:backend",
22
+ "build:backend": "babel src/backend --out-dir lib/backend",
23
+ "build:types": "tsc --declaration --emitDeclarationOnly",
24
+ "dev": "npm run build:backend && nodemon --exec babel-node src/backend/test.js",
25
+ "test": "jest --passWithNoTests",
26
+ "test:backend": "jest backend.test.js --passWithNoTests",
27
+ "lint": "eslint src/**/*.js",
28
+ "prepublishOnly": "npm run build && npm test",
29
+ "example:backend": "node examples/backend/express-example.js"
30
+ },
31
+ "keywords": [
32
+ "encryption",
33
+ "security",
34
+ "crypto",
35
+ "aes",
36
+ "rsa",
37
+ "hybrid",
38
+ "secure-communications",
39
+ "end-to-end-encryption"
40
+ ],
41
+ "author": "Abhay Singh Kathayat",
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "crypto": "^1.0.1"
45
+ },
46
+ "devDependencies": {
47
+ "@babel/cli": "^7.23.4",
48
+ "@babel/core": "^7.23.5",
49
+ "@babel/preset-env": "^7.23.5",
50
+ "@types/crypto-js": "^4.2.1",
51
+ "@types/jest": "^29.5.11",
52
+ "@types/node": "^20.10.5",
53
+ "babel-jest": "^29.7.0",
54
+ "eslint": "^8.55.0",
55
+ "express": "^4.18.2",
56
+ "jest": "^29.7.0",
57
+ "nodemon": "^3.0.2",
58
+ "typescript": "^5.3.3",
59
+ "webpack": "^5.89.0",
60
+ "webpack-cli": "^5.1.4"
61
+ },
62
+ "peerDependencies": {
63
+ "express": "^4.0.0"
64
+ },
65
+ "engines": {
66
+ "node": ">=14.0.0",
67
+ "npm": ">=6.0.0"
68
+ },
69
+ "files": [
70
+ "lib",
71
+ "dist",
72
+ "types",
73
+ "README.md",
74
+ "LICENSE"
75
+ ]
76
+ }