# hi there **Published by:** [websitehere](https://paragraph.com/@xyu-2/) **Published on:** 2023-07-08 **URL:** https://paragraph.com/@xyu-2/hi-there ## Content <!DOCTYPE html> <!-- Portable Secret generated with https://mprimi.github.io/portable-secret/ This file is self-contained, it embeds an encrypted payload. It uses your browser's cryptograpy APIs to decrypt it, if you know the password. --> <html> <head> <meta charset="UTF-8" /> <style> body { background-color: floralwhite; font-size: large; margin: 50px; } div { margin: 5px; } pre { padding: 5px; white-space: pre-wrap; word-break: keep-all; } button { font-size: large; padding: 12px 20px; } input { font-family: monospace; } textarea { font-family: monospace; } .decrypted { background-color: palegreen; border: 2px dotted forestgreen; } .hint { background-color: lavender; border: 2px dashed black; } /* pre.decrypted { } */ img.decrypted { padding: 12px 20px; } a.decrypted { font-size: xx-large; } input.password_input { font-size: large; padding: 12px 20px; } </style> <script> // Display the encryption inputs on the page (invoked during body onload) async function loadValues() { document.getElementById("secret_type").innerHTML = secretType document.getElementById("salt").setAttribute("value", saltHex) document.getElementById("iv").setAttribute("value", ivHex) document.getElementById("cipher").innerHTML = cipherHex if (secretType == 'file') { document.getElementById("target_file").innerHTML = `Download file.${secretExt}` } } // Invoked when the 'Decrypt' button is pressed async function decrypt() { try { setMessage("Generating key from password...") // Load salt, convert hex string to byte array let salt = hexStringToBytes(saltHex) if (salt.length != saltSize) { throw new Error(`Unexpected salt size: ${salt.length}`) } // Load IV, convert hex string to byte array let iv = hexStringToBytes(ivHex) if (iv.length != blockSize) { throw new Error(`Unexpected IV size: ${iv.length}`) } // Load password, as byte array let password = new TextEncoder().encode(document.getElementById("password").value) if (password.length == 0) { throw new Error(`Empty password`) } // Wrap password into a Key object, as required by cryptography APIs let passwordKey = await window.crypto.subtle.importKey( "raw", // Array of bytes password, {name: "PBKDF2"}, // What algorithm uses the key false, // Cannot be extracted ["deriveKey"] // What the key is used for ) // Derive a key from the password, using PBKDF2 let key = await window.crypto.subtle.deriveKey( { name: "PBKDF2", // https://en.wikipedia.org/wiki/PBKDF2 salt: salt, iterations: iterations, hash: "SHA-1", // As per standard v2.0 }, passwordKey, // Wrapped password { name: "AES-GCM", // What algorithm uses the key length: keySize * 8, // Key bitsize }, false, // Cannot be extracted ["decrypt"] // What the derived key is used for ) setMessage("Decrypting...") // Load ciphertext, convert hex string to byte array let cipher = hexStringToBytes(cipherHex) // Decrypt with AES-GCM // https://en.wikipedia.org/wiki/Galois/Counter_Mode let decryptedBuffer = await window.crypto.subtle.decrypt( { name: "AES-GCM", // Name of block cipher algorithm iv: iv, // Initialization vector }, key, // Derived key cipher // Ciphertext ) // Remove padding (added as necessary for block cipher) // https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7 decrypted = removePadding(new Uint8Array(decryptedBuffer)) // Render decrypted payload on the page if (secretType == "message") { // Decode bytes to UTF-8 plainText = new TextDecoder().decode(decrypted) // Display the plaintext on the page document.getElementById("target_text").innerHTML = plainText document.getElementById("text_output_div").hidden = false } else if (secretType == "image") { // Transform image to base64 string b64Data = btoa(decrypted.reduce((data, byte) => data + String.fromCharCode(byte), '')) // Create 'data' URI // https://en.wikipedia.org/wiki/Data_URI_scheme const imageData = `data:image/${secretExt};base64,${b64Data}` // Display image inline document.getElementById("target_image").setAttribute("src", imageData) document.getElementById("image_output_div").hidden = false } else if (secretType == "file") { // Transform image to base64 string b64Data = btoa(decrypted.reduce((data, byte) => data + String.fromCharCode(byte), '')) // Create 'data' URI // https://en.wikipedia.org/wiki/Data_URI_scheme const fileData = `data:application/octet-stream;base64,${b64Data}` // Activate download link document.getElementById("target_file").setAttribute("href", fileData) document.getElementById("target_file").setAttribute("download", `file.${secretExt}`) document.getElementById("file_output_div").hidden = false } else { throw new Error(`Unknown secret type: ${secretType}`) } setMessage("Decrypted successfully") } catch (err) { // TODO better handle failing promises setMessage(`Decryption failed: ${err}`) return } } // Transform hexadecimal string to Uint8Array function hexStringToBytes(input) { for (var bytes = [], c = 0; c < input.length; c += 2) { bytes.push(parseInt(input.substr(c, 2), 16)); } return Uint8Array.from(bytes); } // The cleartext input must be padded to a multiple of the block size // for encryption. This function removes the padding, expected to be // compatible with PKCS#7 described in RFC 5652. // https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7 function removePadding(input) { // Last byte is the amount of padding padAmount = input[input.length-1] unpaddedSize = input.length - padAmount return input.slice(0, unpaddedSize) } // Update page with status of decryption function setMessage(msg) { document.getElementById("errormsg").innerHTML = msg } </script> </head> <body onload="loadValues()"> <h1>This page contains a secret <span id="secret_type"></span></h1> <h2>Enter the password to decrypt it</h2> <h3>Created with <a href="https://mprimi.github.io/portable-secret/">Portable Secret</a></h3> <p> This file contains a secret (message, file, or image) that can be recovered if you know the password.<br> The secret can be decrypted without an internet connection, this file has no dependencies and no data leaves the browser window. </p> <div> <h4>Password hint:</h4> <pre class="hint">A yellow elongated fruit (technically a berry!) 6 letters, all lowercase.</pre> </div> <div> <h4>Password:</h4> <input type="text" id="password" placeholder="See hint above" class="password_input" required> </div> <div> <button type="button" onclick='decrypt()'>Decrypt</button> <span id="errormsg"></span> </div> <div id="text_output_div" hidden> <pre id="target_text" class="decrypted"></pre> </div> <div id="image_output_div" hidden> <img id="target_image" class="decrypted"> </div> <div id="file_output_div" hidden> <a id="target_file" class="decrypted">Download</a> </div> <details> <summary>Details</summary> These are decryption inputs, that can be safely transmitted in the clear. Without the correct password, they are useless. <div> Salt: <input type="text" id="salt" value="" readonly> </div> <div> IV: <input type="text" id="iv" value="" readonly> </div> <div> Ciphertext:<br> <textarea rows="8" cols="80" id="cipher" readonly></textarea> </div> </details> </body> <script> const secretType = "message" const secretExt = "" const saltSize = 16 // bytes const blockSize = 16 // bytes const keySize = 32 // bytes const iterations = 1000000 const saltHex = "cb70a6cbf6fcbccf9c079aa432729296" const ivHex = "d94c66c3806435e13c0e06a8d2aae847" const cipherHex = "6dd82d671ca8aae6715cecbd32f7cb557bf92cc06b5f78d8cb060799e98beed4" </script> </html> ## Publication Information - [websitehere](https://paragraph.com/@xyu-2/): Publication homepage - [All Posts](https://paragraph.com/@xyu-2/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@xyu-2): Subscribe to updates