Spaces:
Running
Running
| <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title> Secure Text Encoder & Decoder </title> <style> * { box-sizing: border-box; } body { margin: 0; min-height: 100vh; padding: 30px 15px; font-family: Arial, sans-serif; background: #0f172a; color: white; } .container { width: 100%; max-width: 700px; margin: auto; } h1 { text-align: center; margin-bottom: 10px; } .subtitle { text-align: center; color: #94a3b8; margin-bottom: 30px; } .card { background: #1e293b; border: 1px solid #334155; border-radius: 15px; padding: 20px; margin-bottom: 20px; } h2 { margin-top: 0; } textarea { width: 100%; min-height: 150px; padding: 15px; resize: vertical; border-radius: 10px; border: 1px solid #475569; background: #0f172a; color: white; outline: none; font-size: 15px; line-height: 1.5; } input { width: 100%; padding: 13px; margin: 15px 0; border-radius: 8px; border: 1px solid #475569; background: #0f172a; color: white; outline: none; font-size: 15px; } .buttons { display: flex; flex-wrap: wrap; gap: 10px; } button { padding: 12px 18px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; color: white; background: #2563eb; } button:hover { opacity: 0.85; } .green { background: #16a34a; } .gray { background: #475569; } .result { margin-top: 15px; padding: 15px; min-height: 100px; background: #020617; border-radius: 8px; border: 1px solid #334155; white-space: pre-wrap; word-break: break-all; font-family: monospace; font-size: 13px; line-height: 1.6; } .status { display: none; padding: 12px; margin-bottom: 20px; border-radius: 8px; } .status.show { display: block; } .success { background: #14532d; } .error { background: #7f1d1d; } .info { background: #1e3a8a; } .security-note { font-size: 13px; color: #94a3b8; line-height: 1.6; margin-top: 15px; } </style> </head> <body> <div class="container"> <h1> π Secure Text Encoder & Decoder </h1> <p class="subtitle"> AES-256-GCM + PBKDF2 </p> <!-- STATUS --> <div id="status" class="status" ></div> <!-- ========================= ENCRYPTION ========================== --> <div class="card"> <h2> π Encrypt </h2> <textarea id="encodeInput" placeholder="Enter your secret text here..." ></textarea> <input id="encodePin" type="password" maxlength="4" inputmode="numeric" autocomplete="off" placeholder="Enter exactly 4 digit PIN" > <div class="buttons"> <button onclick="encryptText()" > π Encrypt </button> <button class="gray" onclick="copyEncrypted()" > π Copy </button> <button class="green" onclick="sendToDecoder()" > β‘οΈ Send to Decoder </button> </div> <h3> Encrypted Result </h3> <div id="encryptedResult" class="result" > Encrypted data will appear here... </div> <p class="security-note"> π Encryption uses AES-256-GCM. A random salt and IV are generated for every encryption. </p> </div> <!-- ========================= DECRYPTION ========================== --> <div class="card"> <h2> π Decrypt </h2> <textarea id="decodeInput" placeholder="Paste encrypted data here..." ></textarea> <input id="decodePin" type="password" maxlength="4" inputmode="numeric" autocomplete="off" placeholder="Enter the same 4 digit PIN" > <div class="buttons"> <button class="green" onclick="decryptText()" > π Decrypt </button> <button class="gray" onclick="clearAll()" > ποΈ Clear </button> </div> <h3> Original Data </h3> <div id="decodedResult" class="result" > Decrypted text will appear here... </div> </div> </div> <script> /* ================================================== CONFIGURATION ================================================== */ const VERSION = "ENC2"; const PBKDF2_ITERATIONS = 600000; /* 600,000 PBKDF2 iterations. This intentionally makes PIN-based key derivation slower. Important: 4 digit PIN still has only 10,000 possible combinations. */ let lastEncryptedData = ""; /* ================================================== STATUS ================================================== */ function showStatus( message, type ) { const status = document.getElementById( "status" ); status.textContent = message; status.className = "status show " + type; } /* ================================================== PIN VALIDATION ================================================== */ function isValidPin( pin ) { return /^[0-9]{4}$/.test( pin ); } /* ================================================== UINT8ARRAY β BASE64 ================================================== */ function bytesToBase64( bytes ) { let binary = ""; const chunkSize = 0x8000; for ( let i = 0; i < bytes.length; i += chunkSize ) { binary += String.fromCharCode( ...bytes.subarray( i, i + chunkSize ) ); } return btoa( binary ); } /* ================================================== BASE64 β UINT8ARRAY ================================================== */ function base64ToBytes( base64 ) { const binary = atob( base64 ); const bytes = new Uint8Array( binary.length ); for ( let i = 0; i < binary.length; i++ ) { bytes[i] = binary.charCodeAt( i ); } return bytes; } /* ================================================== DERIVE AES KEY FROM PIN ================================================== */ async function deriveKey( pin, salt ) { const encoder = new TextEncoder(); /* Convert PIN into cryptographic key material. */ const keyMaterial = await crypto.subtle.importKey( "raw", encoder.encode( pin ), { name: "PBKDF2" }, false, [ "deriveKey" ] ); /* Derive AES-256 key. */ return crypto.subtle.deriveKey( { name: "PBKDF2", salt: salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" }, keyMaterial, { name: "AES-GCM", length: 256 }, false, [ "encrypt", "decrypt" ] ); } /* ================================================== ENCRYPT TEXT ================================================== */ async function encryptText() { try { const text = document .getElementById( "encodeInput" ) .value; const pin = document .getElementById( "encodePin" ) .value; /* Validate text. */ if ( !text ) { showStatus( "Please enter some text.", "error" ); return; } /* Validate exactly 4 digit PIN. */ if ( !isValidPin( pin ) ) { showStatus( "PIN must be exactly 4 digits.", "error" ); return; } /* ------------------------------------------ RANDOM SALT ------------------------------------------ */ const salt = crypto.getRandomValues( new Uint8Array( 16 ) ); /* ------------------------------------------ RANDOM IV ------------------------------------------ */ const iv = crypto.getRandomValues( new Uint8Array( 12 ) ); /* ------------------------------------------ DERIVE AES KEY ------------------------------------------ */ const key = await deriveKey( pin, salt ); /* ------------------------------------------ ENCODE TEXT ------------------------------------------ */ const encoder = new TextEncoder(); const plainBytes = encoder.encode( text ); /* ------------------------------------------ AES-256-GCM ENCRYPTION ------------------------------------------ */ const encryptedBytes = await crypto.subtle.encrypt( { name: "AES-GCM", iv: iv, tagLength: 128 }, key, plainBytes ); /* ------------------------------------------ CREATE OUTPUT ------------------------------------------ Format: ENC2: iterations: salt: iv: encryptedData ------------------------------------------ */ const encryptedObject = { v: VERSION, i: PBKDF2_ITERATIONS, s: bytesToBase64( salt ), n: bytesToBase64( iv ), d: bytesToBase64( new Uint8Array( encryptedBytes ) ) }; /* ------------------------------------------ FINAL ENCRYPTED STRING ------------------------------------------ */ lastEncryptedData = VERSION + ":" + btoa( JSON.stringify( encryptedObject ) ); /* ------------------------------------------ SHOW RESULT ------------------------------------------ */ document .getElementById( "encryptedResult" ) .textContent = lastEncryptedData; showStatus( "β Data encrypted successfully!", "success" ); } catch ( error ) { console.error( error ); showStatus( "β Encryption failed.", "error" ); } } /* ================================================== DECRYPT TEXT ================================================== */ async function decryptText() { try { const encryptedString = document .getElementById( "decodeInput" ) .value .trim(); const pin = document .getElementById( "decodePin" ) .value; /* ------------------------------------------ VALIDATE PIN ------------------------------------------ */ if ( !isValidPin( pin ) ) { showStatus( "PIN must be exactly 4 digits.", "error" ); return; } /* ------------------------------------------ VALIDATE FORMAT ------------------------------------------ */ if ( !encryptedString .startsWith( VERSION + ":" ) ) { showStatus( "Invalid encrypted data.", "error" ); return; } /* ------------------------------------------ EXTRACT PAYLOAD ------------------------------------------ */ const payload = encryptedString .substring( VERSION.length + 1 ); /* ------------------------------------------ DECODE JSON ------------------------------------------ */ const encryptedObject = JSON.parse( atob( payload ) ); /* ------------------------------------------ EXTRACT DATA ------------------------------------------ */ const iterations = encryptedObject.i; const salt = base64ToBytes( encryptedObject.s ); const iv = base64ToBytes( encryptedObject.n ); const encryptedBytes = base64ToBytes( encryptedObject.d ); /* ------------------------------------------ DERIVE SAME KEY ------------------------------------------ */ const key = await deriveKey( pin, salt ); /* ------------------------------------------ DECRYPT ------------------------------------------ */ const decryptedBytes = await crypto.subtle.decrypt( { name: "AES-GCM", iv: iv, tagLength: 128 }, key, encryptedBytes ); /* ------------------------------------------ CONVERT BYTES TO TEXT ------------------------------------------ */ const decoder = new TextDecoder(); const originalText = decoder.decode( decryptedBytes ); /* ------------------------------------------ SHOW ORIGINAL ------------------------------------------ */ document .getElementById( "decodedResult" ) .textContent = originalText; showStatus( "β Correct PIN! Data decrypted successfully.", "success" ); } catch ( error ) { /* AES-GCM authentication automatically fails when: - PIN is wrong - Data modified - Salt corrupted - IV corrupted - Ciphertext corrupted */ document .getElementById( "decodedResult" ) .textContent = "β Unable to decrypt data."; showStatus( "β Wrong PIN or corrupted encrypted data.", "error" ); } } /* ================================================== SEND TO DECODER ================================================== */ function sendToDecoder() { if ( !lastEncryptedData ) { showStatus( "First encrypt some data.", "error" ); return; } /* Put encrypted result into decoder input. */ document .getElementById( "decodeInput" ) .value = lastEncryptedData; /* Copy PIN to decoder. This is only for demo convenience. */ document .getElementById( "decodePin" ) .value = document .getElementById( "encodePin" ) .value; showStatus( "Encrypted data sent to decoder.", "success" ); } /* ================================================== COPY ENCRYPTED DATA ================================================== */ async function copyEncrypted() { if ( !lastEncryptedData ) { showStatus( "Nothing to copy.", "error" ); return; } try { await navigator .clipboard .writeText( lastEncryptedData ); showStatus( "π Encrypted data copied.", "success" ); } catch ( error ) { showStatus( "Unable to copy.", "error" ); } } /* ================================================== CLEAR ================================================== */ function clearAll() { document .getElementById( "encodeInput" ) .value = ""; document .getElementById( "encodePin" ) .value = ""; document .getElementById( "decodeInput" ) .value = ""; document .getElementById( "decodePin" ) .value = ""; document .getElementById( "encryptedResult" ) .textContent = "Encrypted data will appear here..."; document .getElementById( "decodedResult" ) .textContent = "Decrypted text will appear here..."; lastEncryptedData = ""; showStatus( "Cleared.", "info" ); } </script> </body> </html> |