|
1 | 1 | <html> |
2 | 2 | <head> |
3 | 3 | <style> |
4 | | - /* Add basic styles for tabs */ |
5 | 4 | .tab { |
6 | 5 | background: #f7f7f7; |
7 | 6 | border: 1px solid #bbb; |
|
17 | 16 | top: 1px; |
18 | 17 | transition: background 0.2s; |
19 | 18 | } |
20 | | - |
21 | 19 | #tabs { |
22 | 20 | display: flex; |
23 | 21 | flex-direction: row; |
24 | | - margin-bottom: -1px; /* aligns with tab borders */ |
| 22 | + margin-bottom: -1px; |
25 | 23 | } |
26 | | - |
27 | 24 | .tab.active { |
28 | 25 | background: #fff; |
29 | 26 | font-weight: bold; |
30 | 27 | border-color: #888 #888 #fff #888; |
31 | 28 | z-index: 1; |
32 | 29 | } |
33 | | - |
34 | 30 | .tab:not(.active):hover { |
35 | 31 | background: #ececec; |
36 | 32 | } |
37 | | - |
38 | | - .tab-content { |
39 | | - display: none; |
40 | | - border: 1px solid #ccc; |
41 | | - padding: 15px; |
42 | | - margin-top: 10px; |
43 | | - } |
44 | | - |
45 | | - .tab-content.active { |
46 | | - display: block !important; |
47 | | - } |
| 33 | +.tab-content { |
| 34 | + display: none; |
| 35 | + border: 1px solid #ccc; |
| 36 | + padding: 15px; |
| 37 | + margin-top: 10px; |
| 38 | +} |
| 39 | +.tab-content.active { |
| 40 | + display: block !important; |
| 41 | +} |
48 | 42 | </style> |
49 | 43 | </head> |
50 | 44 | <body> |
51 | 45 | <input type="text" placeholder="encryption key" id="key"> |
52 | 46 | <input type="number" placeholder="message number" id="num"> |
53 | | - |
54 | | - <!-- Tabs --> |
55 | 47 | <div id="tabs"> |
56 | 48 | <div class="tab" onclick="showTabContent('tab1')">Encrypt</div> |
57 | 49 | <div class="tab" onclick="showTabContent('tab2')">Decrypt</div> |
|
60 | 52 | Plaintext: |
61 | 53 | <textarea id="plaintext"></textarea> |
62 | 54 | <button type="button" onclick="encrypt()">Encrypt</button> |
| 55 | + <input type="file" id="fileInput" style="display:none" /> |
| 56 | + <button type="button" onclick="document.getElementById('fileInput').click()">Attach File</button> |
| 57 | + <button type="button" id="downloadAttachedBtn" style="display:none" onclick="downloadAttachedFile()">Download Attached File</button> |
63 | 58 | </div> |
64 | 59 | <div id="tab2" class="tab-content"> |
65 | 60 | Ciphertext: |
|
69 | 64 | </body> |
70 | 65 | <script> |
71 | 66 | showTabContent('tab1') |
72 | | - // Helper: Convert a string to ArrayBuffer |
73 | 67 | function strToBuf(str) { |
74 | 68 | return new TextEncoder().encode(str); |
75 | 69 | } |
|
82 | 76 | function b64ToBuf(b64) { |
83 | 77 | return Uint8Array.from(atob(b64), c => c.charCodeAt(0)); |
84 | 78 | } |
85 | | - |
86 | | - // Helper: SHA-256 as hex string |
87 | 79 | async function sha256(message) { |
88 | 80 | const data = strToBuf(message); |
89 | 81 | const hashBuffer = await crypto.subtle.digest('SHA-256', data); |
90 | 82 | return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join(''); |
91 | 83 | } |
92 | | - |
93 | | - // Derive AES key from SHA-256 hex (finalHash) |
94 | 84 | async function hashToKey(hashHex) { |
95 | 85 | const raw = Uint8Array.from(hashHex.match(/.{2}/g).map(b => parseInt(b, 16))); |
96 | 86 | return crypto.subtle.importKey( |
97 | 87 | "raw", raw, "AES-GCM", false, ["encrypt", "decrypt"] |
98 | 88 | ); |
99 | 89 | } |
100 | | - |
101 | | - // Compute finalHash using your logic |
102 | 90 | async function getFinalHash(key, num) { |
103 | 91 | const keyHash = await sha256(key); |
104 | 92 | const numHash = await sha256(num); |
|
108 | 96 | return sha256(multiplied.toString()); |
109 | 97 | } |
110 | 98 |
|
| 99 | + let attachedFile = null; |
| 100 | + let attachedFilename = ""; |
| 101 | + let lastDecryptedFile = null; |
| 102 | + let lastDecryptedFilename = ""; |
| 103 | + |
| 104 | + document.getElementById('fileInput').addEventListener('change', function(evt) { |
| 105 | + if (evt.target.files.length > 0) { |
| 106 | + attachedFile = evt.target.files[0]; |
| 107 | + attachedFilename = attachedFile.name; |
| 108 | + document.getElementById('downloadAttachedBtn').style.display = "none"; |
| 109 | + } else { |
| 110 | + attachedFile = null; |
| 111 | + attachedFilename = ""; |
| 112 | + } |
| 113 | + }); |
| 114 | + |
| 115 | + function fileToBase64(file) { |
| 116 | + return new Promise((resolve, reject) => { |
| 117 | + const reader = new FileReader(); |
| 118 | + reader.onload = e => resolve(btoa(e.target.result)); |
| 119 | + reader.onerror = reject; |
| 120 | + reader.readAsBinaryString(file); |
| 121 | + }); |
| 122 | + } |
| 123 | + function base64ToUint8Array(b64) { |
| 124 | + var binary = atob(b64); |
| 125 | + var len = binary.length; |
| 126 | + var bytes = new Uint8Array(len); |
| 127 | + for (var i = 0; i < len; i++) { |
| 128 | + bytes[i] = binary.charCodeAt(i); |
| 129 | + } |
| 130 | + return bytes; |
| 131 | + } |
| 132 | + |
111 | 133 | async function encrypt() { |
112 | 134 | const keyStr = document.getElementById('key').value; |
113 | 135 | let num = document.getElementById('num').value; |
114 | 136 | const plaintext = document.getElementById('plaintext').value; |
115 | 137 | if (!keyStr || !plaintext) return alert("Provide key and plaintext."); |
116 | | - |
117 | 138 | if (!num || num === "0") { |
118 | 139 | num = Math.floor(Math.random() * 1000000); |
119 | 140 | document.getElementById('num').value = num; |
120 | 141 | } |
121 | | - |
| 142 | + let envelope = null; |
| 143 | + if (attachedFile) { |
| 144 | + const fileDataB64 = await fileToBase64(attachedFile); |
| 145 | + envelope = JSON.stringify({ |
| 146 | + t: plaintext, |
| 147 | + f: attachedFilename, |
| 148 | + d: fileDataB64 |
| 149 | + }); |
| 150 | + } else { |
| 151 | + envelope = JSON.stringify({ t: plaintext }); |
| 152 | + } |
122 | 153 | const finalHash = await getFinalHash(keyStr, num); |
123 | 154 | const aesKey = await hashToKey(finalHash); |
124 | 155 | const iv = crypto.getRandomValues(new Uint8Array(12)); |
125 | 156 | const ctBuf = await crypto.subtle.encrypt( |
126 | | - { name: "AES-GCM", iv }, aesKey, strToBuf(plaintext) |
| 157 | + { name: "AES-GCM", iv }, aesKey, strToBuf(envelope) |
127 | 158 | ); |
128 | | - // Output: base64(iv + ciphertext) |
129 | 159 | const full = new Uint8Array([...iv, ...new Uint8Array(ctBuf)]); |
130 | 160 | document.getElementById('ciphertext').value = bufToB64(full); |
| 161 | + attachedFile = null; |
| 162 | + attachedFilename = ""; |
| 163 | + document.getElementById('fileInput').value = ""; |
| 164 | + document.getElementById('downloadAttachedBtn').style.display = "none"; |
131 | 165 | showTabContent('tab2'); |
132 | 166 | } |
133 | 167 |
|
|
136 | 170 | const num = document.getElementById('num').value; |
137 | 171 | const b64 = document.getElementById('ciphertext').value; |
138 | 172 | if (!keyStr || !num || !b64) return alert("Provide key, message number, and ciphertext."); |
139 | | - |
140 | | - const full = b64ToBuf(b64); |
| 173 | + const full = b64ToBuf(b64.trim()); |
141 | 174 | const iv = full.slice(0, 12); |
142 | 175 | const ct = full.slice(12); |
143 | 176 | const finalHash = await getFinalHash(keyStr, num); |
144 | 177 | const aesKey = await hashToKey(finalHash); |
145 | | - |
146 | 178 | try { |
147 | 179 | const ptBuf = await crypto.subtle.decrypt( |
148 | 180 | { name: "AES-GCM", iv }, aesKey, ct |
149 | 181 | ); |
150 | | - document.getElementById('plaintext').value = bufToStr(ptBuf); |
| 182 | + const envelopeStr = bufToStr(ptBuf); |
| 183 | + let envelope; |
| 184 | + try { |
| 185 | + envelope = JSON.parse(envelopeStr); |
| 186 | + } catch { |
| 187 | + throw new Error('Corrupted decrypted data'); |
| 188 | + } |
| 189 | + document.getElementById('plaintext').value = envelope.t || ""; |
| 190 | + if (envelope.f && envelope.d) { |
| 191 | + lastDecryptedFile = base64ToUint8Array(envelope.d); |
| 192 | + lastDecryptedFilename = envelope.f; |
| 193 | + document.getElementById('downloadAttachedBtn').style.display = "inline"; |
| 194 | + } else { |
| 195 | + lastDecryptedFile = null; |
| 196 | + lastDecryptedFilename = ""; |
| 197 | + document.getElementById('downloadAttachedBtn').style.display = "none"; |
| 198 | + } |
151 | 199 | showTabContent('tab1'); |
152 | 200 | } catch { |
153 | 201 | alert("Decryption failed! (wrong key/message number or data corrupted)"); |
154 | 202 | } |
155 | 203 | } |
156 | 204 |
|
157 | | - // Tab logic |
| 205 | + function downloadAttachedFile() { |
| 206 | + if (!lastDecryptedFile || !lastDecryptedFilename) return; |
| 207 | + const blob = new Blob([lastDecryptedFile]); |
| 208 | + const url = URL.createObjectURL(blob); |
| 209 | + const a = document.createElement('a'); |
| 210 | + a.href = url; |
| 211 | + a.download = lastDecryptedFilename; |
| 212 | + document.body.appendChild(a); |
| 213 | + a.click(); |
| 214 | + setTimeout(() => { |
| 215 | + document.body.removeChild(a); |
| 216 | + URL.revokeObjectURL(url); |
| 217 | + }, 100); |
| 218 | + } |
| 219 | + |
158 | 220 | function showTabContent(tabId) { |
159 | | - document.querySelectorAll('.tab-content').forEach( |
160 | | - c => c.classList.remove('active')); |
161 | | - document.getElementById(tabId).classList.add('active'); |
162 | | - // Add this to update tab styling |
163 | | - document.querySelectorAll('#tabs .tab').forEach(tab => tab.classList.remove('active')); |
164 | | - if (tabId === 'tab1') { |
165 | | - document.querySelectorAll('#tabs .tab')[0].classList.add('active'); |
166 | | - } else { |
167 | | - document.querySelectorAll('#tabs .tab')[1].classList.add('active'); |
| 221 | + document.querySelectorAll('.tab-content').forEach( |
| 222 | + c => c.classList.remove('active')); |
| 223 | + document.getElementById(tabId).classList.add('active'); |
| 224 | + document.querySelectorAll('#tabs .tab').forEach(tab => tab.classList.remove('active')); |
| 225 | + if (tabId === 'tab1') { |
| 226 | + document.querySelectorAll('#tabs .tab')[0].classList.add('active'); |
| 227 | + } else { |
| 228 | + document.querySelectorAll('#tabs .tab')[1].classList.add('active'); |
| 229 | + } |
168 | 230 | } |
169 | | -} |
170 | 231 | </script> |
171 | 232 | </html> |
172 | | - |
|
0 commit comments