@pulkitsinha007/hybrid-crypto-js 1.4.2 → 1.5.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 +1 -1
- package/package.json +1 -1
- package/src/crypto.js +1 -1
- package/src/index.d.ts +2 -2
- package/examples/full-stack/backend/server.js +0 -133
- package/examples/full-stack/frontend/client.js +0 -92
- package/examples/full-stack/frontend/index.html +0 -39
- package/test/asymmetric.test.js +0 -43
- package/test/new_flow.test.js +0 -60
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ const data = await decryptAsymmetric(encryptedData, privateKey);
|
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
### `getCustomSymmetricKey(inputKey)`
|
|
120
|
-
Provides a Crypto Key by taking custom
|
|
120
|
+
Provides a Crypto Key by taking a custom base string as the input.
|
|
121
121
|
- **Parameter**:
|
|
122
122
|
- `inputKey`: `string` - Base key string supposed to be used for encoding.
|
|
123
123
|
- `options`: `Object` - Optional Configuration.
|
package/package.json
CHANGED
package/src/crypto.js
CHANGED
|
@@ -246,7 +246,7 @@ export async function decryptAsymmetric(encryptedData, privateKey) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
/**
|
|
249
|
-
* Provides a Crypto Key by taking custom
|
|
249
|
+
* Provides a Crypto Key by taking a custom base string as the input
|
|
250
250
|
* @param {string} inputKey - Base key string supposed to be used for encoding
|
|
251
251
|
* @param {Object} [options] - Optional configuration.
|
|
252
252
|
* @param {boolean} [options.extractable=false] - Whether the key can be exported.
|
package/src/index.d.ts
CHANGED
|
@@ -110,10 +110,10 @@ export function encryptAsymmetric(data: string | object, publicKey: string): Pro
|
|
|
110
110
|
export function decryptAsymmetric<T = any>(encryptedData: string, privateKey: string): Promise<T>;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Provides a Crypto Key by taking custom
|
|
113
|
+
* Provides a Crypto Key by taking a custom base string as the input
|
|
114
114
|
* @param {string} inputKey - Base key string supposed to be used for encoding
|
|
115
115
|
* @param {Object} [options] - Optional configuration.
|
|
116
116
|
* @param {boolean} [options.extractable=false] - Whether the key can be exported.
|
|
117
117
|
* @returns {Promise<CryptoKey>} The extractable AES encrypted Crypto Key
|
|
118
118
|
*/
|
|
119
|
-
export
|
|
119
|
+
export function getCustomSymmetricKey(inputKey: string, options?: {extractable?: boolean}): Promise<CryptoKey>;
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import http from 'http';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import {
|
|
6
|
-
generateKeyPair,
|
|
7
|
-
unwrapKey,
|
|
8
|
-
decryptWithSymmetricKey,
|
|
9
|
-
encryptWithSymmetricKey
|
|
10
|
-
} from '../../../src/index.js';
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = path.dirname(__filename);
|
|
14
|
-
|
|
15
|
-
const PORT = 3000;
|
|
16
|
-
|
|
17
|
-
// Store keys in memory
|
|
18
|
-
let publicKey;
|
|
19
|
-
let privateKey;
|
|
20
|
-
|
|
21
|
-
// Initialize keys
|
|
22
|
-
(async () => {
|
|
23
|
-
try {
|
|
24
|
-
console.log('Generating server keys...');
|
|
25
|
-
const keys = await generateKeyPair();
|
|
26
|
-
publicKey = keys.publicKey;
|
|
27
|
-
privateKey = keys.privateKey;
|
|
28
|
-
console.log('Server keys generated.');
|
|
29
|
-
} catch (err) {
|
|
30
|
-
console.error('Failed to generate keys:', err);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
})();
|
|
34
|
-
|
|
35
|
-
const server = http.createServer(async (req, res) => {
|
|
36
|
-
// CORS headers for development convenience
|
|
37
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
38
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
39
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
40
|
-
|
|
41
|
-
if (req.method === 'OPTIONS') {
|
|
42
|
-
res.writeHead(200);
|
|
43
|
-
res.end();
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
48
|
-
|
|
49
|
-
// API Endpoints
|
|
50
|
-
if (url.pathname === '/api/public-key' && req.method === 'GET') {
|
|
51
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
52
|
-
res.end(JSON.stringify({ publicKey }));
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (url.pathname === '/api/submit' && req.method === 'POST') {
|
|
57
|
-
let body = '';
|
|
58
|
-
req.on('data', chunk => body += chunk);
|
|
59
|
-
req.on('end', async () => {
|
|
60
|
-
try {
|
|
61
|
-
const { wrappedKey, encryptedPackage } = JSON.parse(body);
|
|
62
|
-
|
|
63
|
-
// 1. Unwrap the symmetric key
|
|
64
|
-
const sessionKey = await unwrapKey(wrappedKey, privateKey);
|
|
65
|
-
|
|
66
|
-
// 2. Decrypt the payload using the session key
|
|
67
|
-
const decrypted = await decryptWithSymmetricKey(encryptedPackage, sessionKey);
|
|
68
|
-
console.log('Decrypted message from client:', decrypted);
|
|
69
|
-
|
|
70
|
-
// 3. Prepare a response
|
|
71
|
-
const responsePayload = {
|
|
72
|
-
status: 'success',
|
|
73
|
-
receivedMessage: decrypted,
|
|
74
|
-
serverTimestamp: Date.now(),
|
|
75
|
-
secret: "This is a secret response encrypted with your session key!"
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// 4. Encrypt the response with the SAME session key
|
|
79
|
-
const responsePackage = await encryptWithSymmetricKey(responsePayload, sessionKey);
|
|
80
|
-
|
|
81
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
82
|
-
res.end(JSON.stringify({ encryptedResponse: responsePackage }));
|
|
83
|
-
|
|
84
|
-
} catch (err) {
|
|
85
|
-
console.error('Processing failed:', err);
|
|
86
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
87
|
-
res.end(JSON.stringify({ error: 'Processing failed' }));
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Static File Serving
|
|
94
|
-
let filePath;
|
|
95
|
-
if (url.pathname === '/') {
|
|
96
|
-
filePath = path.join(__dirname, '../frontend/index.html');
|
|
97
|
-
} else if (url.pathname === '/client.js') {
|
|
98
|
-
filePath = path.join(__dirname, '../frontend/client.js');
|
|
99
|
-
} else if (url.pathname.startsWith('/src/')) {
|
|
100
|
-
// Map /src/xxx to ../../../src/xxx
|
|
101
|
-
const relativePath = url.pathname.replace('/src/', '');
|
|
102
|
-
filePath = path.join(__dirname, '../../../src', relativePath);
|
|
103
|
-
} else {
|
|
104
|
-
res.writeHead(404);
|
|
105
|
-
res.end('Not Found');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const ext = path.extname(filePath);
|
|
110
|
-
let contentType = 'text/html';
|
|
111
|
-
if (ext === '.js') contentType = 'application/javascript';
|
|
112
|
-
|
|
113
|
-
fs.readFile(filePath, (err, content) => {
|
|
114
|
-
if (err) {
|
|
115
|
-
if (err.code === 'ENOENT') {
|
|
116
|
-
console.log('404 Not Found:', filePath);
|
|
117
|
-
res.writeHead(404);
|
|
118
|
-
res.end('Not Found');
|
|
119
|
-
} else {
|
|
120
|
-
console.error('500 Server Error:', err);
|
|
121
|
-
res.writeHead(500);
|
|
122
|
-
res.end('Server Error');
|
|
123
|
-
}
|
|
124
|
-
} else {
|
|
125
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
126
|
-
res.end(content);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
server.listen(PORT, () => {
|
|
132
|
-
console.log(`Server running at http://localhost:${PORT}/`);
|
|
133
|
-
});
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createSymmetricKey,
|
|
3
|
-
wrapKey,
|
|
4
|
-
encryptWithSymmetricKey,
|
|
5
|
-
decryptWithSymmetricKey
|
|
6
|
-
} from '/src/index.js';
|
|
7
|
-
|
|
8
|
-
let serverPublicKey;
|
|
9
|
-
|
|
10
|
-
const statusMain = document.getElementById('status-main');
|
|
11
|
-
|
|
12
|
-
async function init() {
|
|
13
|
-
try {
|
|
14
|
-
// 1. Fetch Server Public Key (Client doesn't need its own RSA key pair anymore)
|
|
15
|
-
const res = await fetch('/api/public-key');
|
|
16
|
-
const data = await res.json();
|
|
17
|
-
serverPublicKey = data.publicKey;
|
|
18
|
-
console.log('Server public key fetched.');
|
|
19
|
-
|
|
20
|
-
statusMain.textContent = 'Ready. Server Public Key initialized.';
|
|
21
|
-
statusMain.style.color = 'green';
|
|
22
|
-
} catch (err) {
|
|
23
|
-
console.error(err);
|
|
24
|
-
statusMain.textContent = 'Error initializing keys. Check console.';
|
|
25
|
-
statusMain.style.color = 'red';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
document.getElementById('btn-send').addEventListener('click', async () => {
|
|
30
|
-
const input = document.getElementById('input-message').value;
|
|
31
|
-
const resultDiv = document.getElementById('result-send');
|
|
32
|
-
|
|
33
|
-
if (!input) {
|
|
34
|
-
alert('Please enter a message');
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
resultDiv.innerHTML = 'Generating Session Key & Encrypting...';
|
|
40
|
-
|
|
41
|
-
// 1. Generate a fresh AES Session Key
|
|
42
|
-
const sessionKey = await createSymmetricKey();
|
|
43
|
-
|
|
44
|
-
// 2. Wrap the session key with Server's Public Key
|
|
45
|
-
const wrappedKey = await wrapKey(sessionKey, serverPublicKey);
|
|
46
|
-
|
|
47
|
-
// 3. Encrypt the payload with the Session Key
|
|
48
|
-
const encryptedPackage = await encryptWithSymmetricKey(input, sessionKey);
|
|
49
|
-
|
|
50
|
-
resultDiv.innerHTML += '<br>Sending wrapped key & encrypted package...';
|
|
51
|
-
|
|
52
|
-
const res = await fetch('/api/submit', {
|
|
53
|
-
method: 'POST',
|
|
54
|
-
body: JSON.stringify({
|
|
55
|
-
wrappedKey,
|
|
56
|
-
encryptedPackage
|
|
57
|
-
})
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const data = await res.json();
|
|
61
|
-
|
|
62
|
-
if (data.error) {
|
|
63
|
-
throw new Error(data.error);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const { encryptedResponse } = data;
|
|
67
|
-
|
|
68
|
-
resultDiv.innerHTML += '<br>Received encrypted response. Decrypting with Session Key...';
|
|
69
|
-
|
|
70
|
-
// 4. Decrypt the response with the SAME Session Key
|
|
71
|
-
const decryptedResponse = await decryptWithSymmetricKey(encryptedResponse, sessionKey);
|
|
72
|
-
|
|
73
|
-
resultDiv.innerHTML = `
|
|
74
|
-
<strong>Status:</strong> Success<br>
|
|
75
|
-
<strong>Server Response Decrypted:</strong> <pre>${JSON.stringify(decryptedResponse, null, 2)}</pre>
|
|
76
|
-
`;
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.error(err);
|
|
79
|
-
resultDiv.textContent = 'Error during session: ' + err.message;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Disable the old "Get Secret" button as it's not part of the new flow
|
|
84
|
-
const btnGet = document.getElementById('btn-get');
|
|
85
|
-
if (btnGet) {
|
|
86
|
-
btnGet.disabled = true;
|
|
87
|
-
btnGet.textContent = "Deprecated (See Session Flow above)";
|
|
88
|
-
const resultGet = document.getElementById('result-get');
|
|
89
|
-
if (resultGet) resultGet.textContent = "This feature is replaced by the bidirectional session flow above.";
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
init();
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Hybrid Crypto Full Stack Example</title>
|
|
7
|
-
<style>
|
|
8
|
-
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
|
9
|
-
section { border: 1px solid #ccc; padding: 20px; margin-bottom: 20px; border-radius: 8px; }
|
|
10
|
-
textarea { width: 100%; height: 60px; }
|
|
11
|
-
button { padding: 10px 20px; cursor: pointer; }
|
|
12
|
-
pre { background: #f4f4f4; padding: 10px; overflow-x: auto; }
|
|
13
|
-
.status { margin-top: 10px; font-weight: bold; }
|
|
14
|
-
</style>
|
|
15
|
-
</head>
|
|
16
|
-
<body>
|
|
17
|
-
<h1>Hybrid Crypto Full Stack Example</h1>
|
|
18
|
-
|
|
19
|
-
<div id="status-main" class="status">Initializing Keys...</div>
|
|
20
|
-
|
|
21
|
-
<section>
|
|
22
|
-
<h2>1. Client -> Server (Secure Submit)</h2>
|
|
23
|
-
<p>Encrypt a message with the Server's Public Key. Only the server can decrypt it.</p>
|
|
24
|
-
<textarea id="input-message" placeholder="Type a secret message..."></textarea>
|
|
25
|
-
<br><br>
|
|
26
|
-
<button id="btn-send">Encrypt & Send</button>
|
|
27
|
-
<div id="result-send"></div>
|
|
28
|
-
</section>
|
|
29
|
-
|
|
30
|
-
<section>
|
|
31
|
-
<h2>2. Server -> Client (Secure Retrieve)</h2>
|
|
32
|
-
<p>Request a secret message from the server. The server will encrypt it with your Client Public Key.</p>
|
|
33
|
-
<button id="btn-get">Get Secret Message</button>
|
|
34
|
-
<div id="result-get"></div>
|
|
35
|
-
</section>
|
|
36
|
-
|
|
37
|
-
<script type="module" src="./client.js"></script>
|
|
38
|
-
</body>
|
|
39
|
-
</html>
|
package/test/asymmetric.test.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { test, describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import {
|
|
4
|
-
generateKeyPair,
|
|
5
|
-
encryptAsymmetric,
|
|
6
|
-
decryptAsymmetric
|
|
7
|
-
} from '../src/index.js';
|
|
8
|
-
|
|
9
|
-
describe('Asymmetric Encryption (RSA-OAEP)', () => {
|
|
10
|
-
it('should encrypt and decrypt a plain string', async () => {
|
|
11
|
-
const { publicKey, privateKey } = await generateKeyPair();
|
|
12
|
-
const originalData = "Hello, Asymmetric World!";
|
|
13
|
-
|
|
14
|
-
const encryptedData = await encryptAsymmetric(originalData, publicKey);
|
|
15
|
-
assert.notStrictEqual(encryptedData, originalData);
|
|
16
|
-
|
|
17
|
-
const decryptedData = await decryptAsymmetric(encryptedData, privateKey);
|
|
18
|
-
assert.strictEqual(decryptedData, originalData);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should encrypt and decrypt a JSON object', async () => {
|
|
22
|
-
const { publicKey, privateKey } = await generateKeyPair();
|
|
23
|
-
const originalData = { foo: "bar", baz: 123 };
|
|
24
|
-
|
|
25
|
-
const encryptedData = await encryptAsymmetric(originalData, publicKey);
|
|
26
|
-
const decryptedData = await decryptAsymmetric(encryptedData, privateKey);
|
|
27
|
-
|
|
28
|
-
assert.deepStrictEqual(decryptedData, originalData);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should fail to decrypt with the wrong private key', async () => {
|
|
32
|
-
const keys1 = await generateKeyPair();
|
|
33
|
-
const keys2 = await generateKeyPair();
|
|
34
|
-
|
|
35
|
-
const originalData = "Secret message";
|
|
36
|
-
const encryptedData = await encryptAsymmetric(originalData, keys1.publicKey);
|
|
37
|
-
|
|
38
|
-
await assert.rejects(
|
|
39
|
-
() => decryptAsymmetric(encryptedData, keys2.privateKey),
|
|
40
|
-
/OperationError|DataError/
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
});
|
package/test/new_flow.test.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { test, describe, it, before } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import {
|
|
4
|
-
generateKeyPair,
|
|
5
|
-
createSymmetricKey,
|
|
6
|
-
wrapKey,
|
|
7
|
-
unwrapKey,
|
|
8
|
-
encryptWithSymmetricKey,
|
|
9
|
-
decryptWithSymmetricKey
|
|
10
|
-
} from '../src/index.js';
|
|
11
|
-
|
|
12
|
-
describe('New Hybrid Flow (Session Based)', () => {
|
|
13
|
-
let backendPublicKey;
|
|
14
|
-
let backendPrivateKey;
|
|
15
|
-
|
|
16
|
-
before(async () => {
|
|
17
|
-
// 1. Backend setup: Generate RSA Key Pair
|
|
18
|
-
const keys = await generateKeyPair();
|
|
19
|
-
backendPublicKey = keys.publicKey;
|
|
20
|
-
backendPrivateKey = keys.privateKey;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should successfully complete the full session flow', async () => {
|
|
24
|
-
const clientPayload = "Request: Give me the secret";
|
|
25
|
-
const serverResponsePayload = "Response: Here is the secret [12345]";
|
|
26
|
-
|
|
27
|
-
// --- FRONTEND SIDE (Request) ---
|
|
28
|
-
|
|
29
|
-
// 2. Client generates AES key
|
|
30
|
-
const clientAesKey = await createSymmetricKey();
|
|
31
|
-
|
|
32
|
-
// 3. Client wraps AES key with Backend Public Key
|
|
33
|
-
const wrappedKey = await wrapKey(clientAesKey, backendPublicKey);
|
|
34
|
-
|
|
35
|
-
// 4. Client encrypts payload with AES key
|
|
36
|
-
const clientEncryptedPackage = await encryptWithSymmetricKey(clientPayload, clientAesKey);
|
|
37
|
-
|
|
38
|
-
// Client sends { wrappedKey, encryptedPayload: ... } to backend
|
|
39
|
-
|
|
40
|
-
// --- BACKEND SIDE ---
|
|
41
|
-
|
|
42
|
-
// 5. Backend receives package. Unwraps AES key.
|
|
43
|
-
const serverAesKey = await unwrapKey(wrappedKey, backendPrivateKey);
|
|
44
|
-
|
|
45
|
-
// 6. Backend decrypts payload
|
|
46
|
-
const decryptedRequest = await decryptWithSymmetricKey(clientEncryptedPackage, serverAesKey);
|
|
47
|
-
assert.strictEqual(decryptedRequest, clientPayload, "Backend failed to decrypt client request");
|
|
48
|
-
|
|
49
|
-
// 7. Backend encrypts response with SAME AES key
|
|
50
|
-
const serverEncryptedPackage = await encryptWithSymmetricKey(serverResponsePayload, serverAesKey);
|
|
51
|
-
|
|
52
|
-
// Backend sends { encryptedResponse: ... } to client
|
|
53
|
-
|
|
54
|
-
// --- FRONTEND SIDE (Response) ---
|
|
55
|
-
|
|
56
|
-
// 8. Client receives encrypted response. Decrypts with original AES key.
|
|
57
|
-
const decryptedResponse = await decryptWithSymmetricKey(serverEncryptedPackage, clientAesKey);
|
|
58
|
-
assert.strictEqual(decryptedResponse, serverResponsePayload, "Client failed to decrypt server response");
|
|
59
|
-
});
|
|
60
|
-
});
|