|
76 | 76 | <!-- Vault View --> |
77 | 77 | <div id="vaultView" class="view-content active"> |
78 | 78 | <div class="controls"> |
79 | | - <label>1. Select Image(s), Video, or Audio</label> |
80 | | - <input type="file" id="upload" accept="image/*, video/*, audio/*" multiple> |
| 79 | + <label>1. Select Image(s), Video, Audio, or Text</label> |
| 80 | + <input type="file" id="upload" accept="image/*, video/*, audio/*, text/*" multiple> |
81 | 81 | <label>2. Settings</label> |
82 | 82 | <div class="btn-group"> |
83 | 83 | <input type="password" id="password" placeholder="Secret phrase..." autocomplete="off"> |
|
140 | 140 | let processedFiles = [], destructTime = 300; |
141 | 141 | var videoFile = null; |
142 | 142 | var audioFile = null; |
| 143 | + var textFile = null; |
143 | 144 |
|
144 | 145 | function switchTab(view) { |
145 | 146 | document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); |
|
193 | 194 | }; |
194 | 195 |
|
195 | 196 | upload.onchange = async (e) => { |
| 197 | + videoFile = null; |
| 198 | + audioFile = null; |
| 199 | + textFile = null; |
196 | 200 | const files = e.target.files; |
197 | 201 | if (files.length === 1) { |
198 | 202 | const mimeType = files[0].type; |
|
213 | 217 | videoFile = files[0]; |
214 | 218 | } else if (mimeType.startsWith('audio/')) { |
215 | 219 | audioFile = files[0]; |
| 220 | + } else if (mimeType.startsWith('text/')) { |
| 221 | + textFile = files[0]; |
216 | 222 | } |
217 | 223 | } else { |
218 | 224 | cvs.style.display = 'none'; placeholder.style.display = 'block'; |
|
627 | 633 | await processVideo(videoFile, 30, seed, isUnscramble); |
628 | 634 | } else if (audioFile != null) { |
629 | 635 | await processAudio(audioFile, seed, isUnscramble); |
| 636 | + } else if (textFile != null) { |
| 637 | + await processText(textFile, seed, isUnscramble); |
630 | 638 | } else { |
631 | 639 | const files = upload.files; |
632 | 640 | if (!files.length) return alert("Select files first."); |
|
903 | 911 | setTimeout(() => { if(document.body.contains(loader)) document.body.removeChild(loader); }, 1000); |
904 | 912 | } |
905 | 913 | } |
| 914 | + |
| 915 | + // text functions |
| 916 | + async function processText(file, seed = "", isUnscramble = false) { |
| 917 | + const loader = document.createElement('div'); |
| 918 | + Object.assign(loader.style, { |
| 919 | + position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', |
| 920 | + backgroundColor: 'rgba(0,0,0,0.95)', color: 'white', display: 'flex', |
| 921 | + flexDirection: 'column', alignItems: 'center', justifyContent: 'center', zIndex: '10000' |
| 922 | + }); |
| 923 | + loader.innerHTML = `<p id="st">Processing Masquerade...</p>`; |
| 924 | + document.body.appendChild(loader); |
| 925 | + try { |
| 926 | + if (!isUnscramble) { |
| 927 | + const originalFullName = file.name; |
| 928 | + const arrayBuffer = await file.arrayBuffer(); |
| 929 | + const fileBytes = new Uint8Array(arrayBuffer); |
| 930 | + const originalSize = fileBytes.length; |
| 931 | + const totalPixels = Math.ceil(originalSize / 3); |
| 932 | + const side = Math.ceil(Math.sqrt(totalPixels)); |
| 933 | + const canvas = document.createElement('canvas'); |
| 934 | + canvas.width = side; canvas.height = side; |
| 935 | + const ctx = canvas.getContext('2d', { colorSpace: 'srgb', alpha: false }); |
| 936 | + const imageData = ctx.createImageData(side, side); |
| 937 | + const data = imageData.data; |
| 938 | + let fIdx = 0; |
| 939 | + for (let i = 0; i < data.length; i += 4) { |
| 940 | + data[i] = fIdx < originalSize ? fileBytes[fIdx++] : 0; |
| 941 | + data[i + 1] = fIdx < originalSize ? fileBytes[fIdx++] : 0; |
| 942 | + data[i + 2] = fIdx < originalSize ? fileBytes[fIdx++] : 0; |
| 943 | + data[i + 3] = 255; |
| 944 | + } |
| 945 | + // DEFAULT METHOD |
| 946 | + // because text files are already high entropy and high diffusion |
| 947 | + // a simply fisher-yates shuffle is sufficient to make it ultra quantum resistant |
| 948 | + const len = side * side; |
| 949 | + const prng = createPRNG(seed); |
| 950 | + const map = new Int32Array(len); |
| 951 | + for(let i=0; i<len; i++) map[i] = i; |
| 952 | + for(let i=len-1; i>0; i--) { |
| 953 | + const j = Math.floor((prng.next ? prng.next().value : prng()) * (i + 1)); |
| 954 | + [map[i], map[j]] = [map[j], map[i]]; |
| 955 | + } |
| 956 | + const tempData = new Uint8ClampedArray(data); |
| 957 | + for(let i=0; i<len; i++) { |
| 958 | + const s = i * 4; |
| 959 | + const d = map[i] * 4; |
| 960 | + data[d] = tempData[s]; |
| 961 | + data[d + 1] = tempData[s + 1]; |
| 962 | + data[d + 2] = tempData[s + 2]; |
| 963 | + data[d + 3] = 255; |
| 964 | + } |
| 965 | + ctx.putImageData(imageData, 0, 0); |
| 966 | + canvas.toBlob((blob) => { |
| 967 | + const a = document.createElement('a'); |
| 968 | + a.href = URL.createObjectURL(blob); |
| 969 | + // some meta data in filename cannot be changed |
| 970 | + // that means do not change text filenames! |
| 971 | + a.download = `enc_${originalFullName}_${originalSize}.txt`; |
| 972 | + a.click(); |
| 973 | + }, 'image/png'); |
| 974 | + |
| 975 | + } else { |
| 976 | + const metaMatch = file.name.match(/^enc_(.+)_(\d+)\.txt$/); |
| 977 | + if (!metaMatch) throw new Error("Format Error: File must be named enc_FILENAME_SIZE.txt"); |
| 978 | + const originalName = metaMatch[1]; |
| 979 | + const originalSize = parseInt(metaMatch[2]); |
| 980 | + const img = new Image(); |
| 981 | + img.src = URL.createObjectURL(file); |
| 982 | + await new Promise(r => img.onload = r); |
| 983 | + const canvas = document.createElement('canvas'); |
| 984 | + canvas.width = img.width; canvas.height = img.height; |
| 985 | + const ctx = canvas.getContext('2d', { colorSpace: 'srgb', alpha: false }); |
| 986 | + ctx.drawImage(img, 0, 0); |
| 987 | + const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; |
| 988 | + const len = canvas.width * canvas.height; |
| 989 | + const prng = createPRNG(seed); |
| 990 | + const map = new Int32Array(len); |
| 991 | + for(let i=0; i<len; i++) map[i] = i; |
| 992 | + for(let i=len-1; i>0; i--) { |
| 993 | + const j = Math.floor((prng.next ? prng.next().value : prng()) * (i + 1)); |
| 994 | + [map[i], map[j]] = [map[j], map[i]]; |
| 995 | + } |
| 996 | + const tempData = new Uint8ClampedArray(data); |
| 997 | + const unscrambledData = new Uint8ClampedArray(data.length); |
| 998 | + for(let i=0; i<len; i++) { |
| 999 | + const s = map[i] * 4; |
| 1000 | + const d = i * 4; |
| 1001 | + unscrambledData[d] = tempData[s]; |
| 1002 | + unscrambledData[d + 1] = tempData[s + 1]; |
| 1003 | + unscrambledData[d + 2] = tempData[s + 2]; |
| 1004 | + } |
| 1005 | + const recoveredBytes = new Uint8Array(originalSize); |
| 1006 | + let rIdx = 0; |
| 1007 | + for (let i = 0; i < unscrambledData.length && rIdx < originalSize; i += 4) { |
| 1008 | + recoveredBytes[rIdx++] = unscrambledData[i]; |
| 1009 | + if (rIdx < originalSize) recoveredBytes[rIdx++] = unscrambledData[i+1]; |
| 1010 | + if (rIdx < originalSize) recoveredBytes[rIdx++] = unscrambledData[i+2]; |
| 1011 | + } |
| 1012 | + const finalBlob = new Blob([recoveredBytes]); |
| 1013 | + const a = document.createElement('a'); |
| 1014 | + a.href = URL.createObjectURL(finalBlob); |
| 1015 | + a.download = `dec_${originalName}`; |
| 1016 | + a.click(); |
| 1017 | + } |
| 1018 | + } catch (e) { |
| 1019 | + alert("Masquerade Restoration Error: " + e.message); |
| 1020 | + } finally { |
| 1021 | + setTimeout(() => { if(document.body.contains(loader)) document.body.removeChild(loader); }, 1000); |
| 1022 | + } |
| 1023 | + } |
906 | 1024 |
|
907 | 1025 | // analytical functions |
908 | 1026 | function analyzeCanvasImage() { |
|
0 commit comments