☰ Dup Goto 📝

SelfDecryptingWebpages

PT2/crypto/web-tools 10-25 03:09:15
To Pop
209 lines, 627 words, 7435 chars Sunday 2026-10-25 03:09:15

Example file: selfdec.html -- passphrase is hello

Generated by

python encode.py cover.html payload.html PassPhrase output.html

Python source

import base64
import os
import sys
import urllib.request
from cryptography.hazmat.primitives import hashes, padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

CRYPTOJS_FILENAME = "crypto-js.min.js"
CRYPTOJS_URL = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"

def get_cryptojs_source():
    """Fetches CryptoJS once from CDN and caches it locally for offline inlining."""
    if not os.path.exists(CRYPTOJS_FILENAME):
        print("[*] Downloading CryptoJS library for offline embedding (requires internet once)...")
        try:
            urllib.request.urlretrieve(CRYPTOJS_URL, CRYPTOJS_FILENAME)
        except Exception as e:
            print(f"[-] Error downloading CryptoJS: {e}")
            sys.exit(1)

    with open(CRYPTOJS_FILENAME, "r", encoding="utf-8") as f:
        return f.read()

def derive_key(passphrase: str, salt: bytes) -> bytes:
    """Derive a 256-bit AES key from the passphrase and salt using PBKDF2-HMAC-SHA256."""
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100_000
    )
    return kdf.derive(passphrase.encode('utf-8'))

def encrypt_html(cover_path: str, payload_path: str, passphrase: str, output_path: str):
    # 1. Read files
    with open(payload_path, "r", encoding="utf-8") as f:
        payload_html = f.read()

    with open(cover_path, "r", encoding="utf-8") as f:
        cover_html = f.read()

    # Get the inlineable CryptoJS source code
    cryptojs_code = get_cryptojs_source()

    # 2. Generate cryptographic parameters
    salt = os.urandom(16)
    iv = os.urandom(16)  # AES block size is 16 bytes for CBC
    key = derive_key(passphrase, salt)

    # 3. Apply PKCS7 Padding and Encrypt using AES-CBC
    padder = padding.PKCS7(128).padder()
    padded_data = padder.update(payload_html.encode('utf-8')) + padder.finalize()

    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(padded_data) + encryptor.finalize()

    # Pack salt, iv, and ciphertext together, then base64 encode
    blob = salt + iv + ciphertext
    b64_blob = base64.b64encode(blob).decode('utf-8')

    # 4. Generate the fully offline, self-contained HTML wrapper
    template = """<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Protected Page</title>
    <!-- Inlined CryptoJS Library for 100% Offline Capability -->
    <script>
__CRYPTOJS_SOURCE__
    </script>
    <style>
        body { font-family: system-ui, sans-serif; margin: 0; padding: 0; }
        #decryption-gate {
            max-width: 400px;
            margin: 100px auto;
            padding: 30px;
            border: 1px solid #ddd;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
            text-align: center;
        }
        #decryption-gate input[type="password"] {
            width: 80%;
            padding: 10px;
            margin: 15px 0;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 16px;
        }
        #decryption-gate button {
            padding: 10px 20px;
            background-color: #0070f3;
            color: white;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
        }
        #decryption-gate button:hover { background-color: #0051a2; }
        #error-msg { color: #e00; margin-top: 10px; font-size: 14px; }
    </style>
</head>
<body>

    <!-- Cover Page / Front Door Container -->
    <div id="front-door">
        __COVER_HTML__
        <div id="decryption-gate">
            <h3>Protected Content</h3>
            <p>Please enter the passphrase to view this page.</p>
            <input type="password" id="passphrase-input" placeholder="Enter passphrase..." autofocus />
            <br>
            <button onclick="attemptDecrypt()">Decrypt</button>
            <div id="error-msg"></div>
        </div>
    </div>

    <!-- Encrypted Payload Storage -->
    <script id="encrypted-payload" type="application/json">__B64_BLOB__</script>

    <script>
        function attemptDecrypt() {
            const passphrase = document.getElementById('passphrase-input').value;
            const errorDiv = document.getElementById('error-msg');
            errorDiv.textContent = "";

            try {
                const b64Data = document.getElementById('encrypted-payload').textContent.trim();
                const rawData = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));

                // Extract components: Salt (16 bytes) + IV (16 bytes) + Ciphertext
                const salt = rawData.slice(0, 16);
                const iv = rawData.slice(16, 32);
                const ciphertext = rawData.slice(32);

                // Derive key using PBKDF2 (100k iterations, matching Python side)
                const saltWordArr = CryptoJS.lib.WordArray.create(salt);
                const key = CryptoJS.PBKDF2(passphrase, saltWordArr, {
                    keySize: 256 / 32,
                    iterations: 100000,
                    hasher: CryptoJS.algo.SHA256
                });

                // Convert IV and ciphertext to CryptoJS WordArrays
                const ivWordArr = CryptoJS.lib.WordArray.create(iv);
                const cipherParams = CryptoJS.lib.CipherParams.create({
                    ciphertext: CryptoJS.lib.WordArray.create(ciphertext)
                });

                // Decrypt using AES-CBC
                const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
                    iv: ivWordArr,
                    mode: CryptoJS.mode.CBC,
                    padding: CryptoJS.pad.Pkcs7
                });

                const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
                if (!decryptedText) {
                    throw new Error("Decryption failed");
                }

                // Replace entire document with decrypted payload
                document.open();
                document.write(decryptedText);
                document.close();

            } catch (err) {
                errorDiv.textContent = "Decryption failed. Incorrect passphrase.";
            }
        }

        // Allow pressing Enter key to submit
        document.getElementById('passphrase-input').addEventListener('keypress', function (e) {
            if (e.key === 'Enter') {
                attemptDecrypt();
            }
        });
    </script>
</body>
</html>
"""

    html_output = (template
                   .replace("__CRYPTOJS_SOURCE__", cryptojs_code)
                   .replace("__COVER_HTML__", cover_html)
                   .replace("__B64_BLOB__", b64_blob))

    with open(output_path, "w", encoding="utf-8") as f:
        f.write(html_output)
    print(f"[+] Successfully generated offline self-decrypting file: {output_path}")

if __name__ == "__main__":
    if len(sys.argv) < 5:
        print("Usage: python encrypt.py <cover.html> <payload.html> <passphrase> <output.html>")
        sys.exit(1)

    encrypt_html(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])