broadfield-dev commited on
Commit
e47e5df
·
verified ·
1 Parent(s): 2c9dc86

Delete keylock-decoder.js

Browse files
Files changed (1) hide show
  1. keylock-decoder.js +0 -202
keylock-decoder.js DELETED
@@ -1,202 +0,0 @@
1
- /**
2
- * KeyLock-JS-Decoder
3
- * A client-side JavaScript module to decode data from images created by the KeyLock app.
4
- * Uses Web Crypto API for decryption and HTML Canvas for LSB steganography extraction.
5
- *
6
- * @version 1.1.0
7
- * @license MIT
8
- */
9
-
10
- /**
11
- * Converts a PEM-formatted key string (PKCS8) to a DER ArrayBuffer.
12
- * This is a necessary preprocessing step for the Web Crypto API.
13
- * @param {string} pem The PEM key string.
14
- * @returns {ArrayBuffer} The key in DER format.
15
- */
16
- function pemToDer(pem) {
17
- const b64 = pem
18
- .replace(/-----BEGIN PRIVATE KEY-----/g, '')
19
- .replace(/-----END PRIVATE KEY-----/g, '')
20
- .replace(/\s/g, '');
21
-
22
- const binaryDer = atob(b64);
23
- const buffer = new ArrayBuffer(binaryDer.length);
24
- const bytes = new Uint8Array(buffer);
25
- for (let i = 0; i < binaryDer.length; i++) {
26
- bytes[i] = binaryDer.charCodeAt(i);
27
- }
28
- return buffer;
29
- }
30
-
31
- /**
32
- * Extracts the hidden data payload from an image's LSBs using a Canvas.
33
- * @param {HTMLImageElement} imageElement The <img> element containing the stego image.
34
- * @returns {Promise<Uint8Array>} A promise that resolves with the extracted data payload.
35
- */
36
- function extractDataFromImage(imageElement) {
37
- return new Promise((resolve, reject) => {
38
- const canvas = document.createElement('canvas');
39
- const ctx = canvas.getContext('2d', { willReadFrequently: true });
40
-
41
- // Ensure the canvas is the same size as the image to avoid scaling artifacts
42
- canvas.width = imageElement.naturalWidth;
43
- canvas.height = imageElement.naturalHeight;
44
- ctx.drawImage(imageElement, 0, 0);
45
-
46
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
47
- const channels = 4; // RGBA
48
-
49
- // 1. Extract the header to find the data length
50
- const HEADER_BITS = 32; // 4 bytes for the length header
51
- if (imageData.length < Math.ceil(HEADER_BITS / 3) * channels) {
52
- return reject(new Error("Image is too small to contain a valid header."));
53
- }
54
-
55
- let headerBinaryString = '';
56
- for (let i = 0; headerBinaryString.length < HEADER_BITS; i++) {
57
- const pixelIndex = i * channels;
58
- headerBinaryString += imageData[pixelIndex] & 1; // R
59
- if (headerBinaryString.length < HEADER_BITS) headerBinaryString += imageData[pixelIndex + 1] & 1; // G
60
- if (headerBinaryString.length < HEADER_BITS) headerBinaryString += imageData[pixelIndex + 2] & 1; // B
61
- }
62
-
63
- const headerBytes = new Uint8Array(HEADER_BITS / 8);
64
- for (let i = 0; i < HEADER_BITS / 8; i++) {
65
- headerBytes[i] = parseInt(headerBinaryString.substring(i * 8, (i + 1) * 8), 2);
66
- }
67
-
68
- const dataView = new DataView(headerBytes.buffer);
69
- const dataLengthInBytes = dataView.getUint32(0, false); // false for big-endian
70
-
71
- if (dataLengthInBytes === 0) {
72
- return resolve(new Uint8Array(0));
73
- }
74
-
75
- // 2. Extract the data payload
76
- const dataLengthInBits = dataLengthInBytes * 8;
77
- const bitOffset = HEADER_BITS;
78
-
79
- if (imageData.length < Math.ceil((bitOffset + dataLengthInBits) / 3) * channels) {
80
- return reject(new Error("Image is too small for the data specified in the header. File may be corrupt."));
81
- }
82
-
83
- let dataBinaryString = '';
84
- for (let i = 0; dataBinaryString.length < dataLengthInBits; i++) {
85
- const bitPosition = bitOffset + i;
86
- const pixelIndex = Math.floor(bitPosition / 3) * channels;
87
- const channelOffset = bitPosition % 3;
88
- dataBinaryString += imageData[pixelIndex + channelOffset] & 1;
89
- }
90
-
91
- if (dataBinaryString.length !== dataLengthInBits) {
92
- return reject(new Error(`Data extraction error: expected ${dataLengthInBits} bits, but got ${dataBinaryString.length}. Image may be truncated or corrupt.`));
93
- }
94
-
95
- const dataBytes = new Uint8Array(dataLengthInBytes);
96
- for (let i = 0; i < dataLengthInBytes; i++) {
97
- dataBytes[i] = parseInt(dataBinaryString.substring(i * 8, (i + 1) * 8), 2);
98
- }
99
-
100
- resolve(dataBytes);
101
- });
102
- }
103
-
104
- /**
105
- * Decrypts a hybrid RSA-AES payload using the Web Crypto API.
106
- * @param {Uint8Array} cryptoPayload The full encrypted payload.
107
- * @param {string} privateKeyPem The recipient's private key in PEM format.
108
- * @returns {Promise<object>} A promise that resolves with the decrypted JavaScript object.
109
- */
110
- async function decryptHybridPayload(cryptoPayload, privateKeyPem) {
111
- // --- Constants from Python core.py ---
112
- const ENCRYPTED_AES_KEY_LEN_SIZE = 4;
113
- const AES_GCM_NONCE_SIZE = 12;
114
-
115
- // 1. Import the RSA private key
116
- const privateKey = await crypto.subtle.importKey(
117
- 'pkcs8',
118
- pemToDer(privateKeyPem),
119
- { name: 'RSA-OAEP', hash: 'SHA-256' },
120
- true,
121
- ['decrypt']
122
- );
123
-
124
- // 2. Parse the crypto payload
125
- const encryptedAesKeyLen = new DataView(cryptoPayload.buffer, 0, ENCRYPTED_AES_KEY_LEN_SIZE).getUint32(0, false);
126
-
127
- let offset = ENCRYPTED_AES_KEY_LEN_SIZE;
128
- const encryptedAesKey = cryptoPayload.slice(offset, offset + encryptedAesKeyLen);
129
-
130
- offset += encryptedAesKeyLen;
131
- const nonce = cryptoPayload.slice(offset, offset + AES_GCM_NONCE_SIZE);
132
-
133
- offset += AES_GCM_NONCE_SIZE;
134
- const ciphertextWithTag = cryptoPayload.slice(offset);
135
-
136
- // 3. Decrypt the AES key with the RSA private key
137
- const decryptedAesKeyBytes = await crypto.subtle.decrypt(
138
- { name: 'RSA-OAEP' },
139
- privateKey,
140
- encryptedAesKey
141
- );
142
-
143
- // 4. Import the decrypted AES key
144
- const aesKey = await crypto.subtle.importKey(
145
- 'raw',
146
- decryptedAesKeyBytes,
147
- { name: 'AES-GCM', length: 256 },
148
- true,
149
- ['decrypt']
150
- );
151
-
152
- // 5. Decrypt the final data with the AES key
153
- const decryptedDataBuffer = await crypto.subtle.decrypt(
154
- { name: 'AES-GCM', iv: nonce },
155
- aesKey,
156
- ciphertextWithTag
157
- );
158
-
159
- // 6. Decode the result from UTF-8 and parse as JSON
160
- const decryptedText = new TextDecoder().decode(decryptedDataBuffer);
161
- return JSON.parse(decryptedText);
162
- }
163
-
164
-
165
- /**
166
- * Main public function. Decodes an auth object from a KeyLock image.
167
- *
168
- * @param {HTMLImageElement} imageElement The <img> element displaying the KeyLock PNG.
169
- * Note: The image must be served from the same origin or have CORS headers
170
- * allowing it to be drawn on a canvas. For file uploads, this is not an issue.
171
- * @param {string} privateKeyPem The user's private RSA key in PEM format.
172
- * @returns {Promise<object>} A promise that resolves to the decoded credentials object.
173
- * @throws {Error} Throws an error if decoding or decryption fails.
174
- */
175
- export async function decodeAuthFromImage(imageElement, privateKeyPem) {
176
- if (!imageElement || !(imageElement instanceof HTMLImageElement)) {
177
- throw new Error("A valid HTMLImageElement must be provided.");
178
- }
179
- if (!privateKeyPem || typeof privateKeyPem !== 'string') {
180
- throw new Error("A valid private key in PEM format (string) must be provided.");
181
- }
182
-
183
- try {
184
- // Step 1: Extract the encrypted byte payload from the image LSBs
185
- const cryptoPayload = await extractDataFromImage(imageElement);
186
- if (cryptoPayload.length === 0) {
187
- throw new Error("No data found in image. It may not be a valid KeyLock file.");
188
- }
189
-
190
- // Step 2: Decrypt the payload
191
- const credentials = await decryptHybridPayload(cryptoPayload, privateKeyPem);
192
- return credentials;
193
-
194
- } catch (error) {
195
- // Provide a more user-friendly error message for common failures
196
- if (error.name === 'OperationError' || (error.message && error.message.toLowerCase().includes('decryption failed'))) {
197
- throw new Error("Decryption failed. This usually means the private key is incorrect or the image data is corrupted.");
198
- }
199
- // Re-throw other errors
200
- throw error;
201
- }
202
- }