• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Step-by-Step Guide to building a custom secure file encryption vault block for Gutenberg using custom WebAssembly modules

Step-by-Step Guide to building a custom secure file encryption vault block for Gutenberg using custom WebAssembly modules

Leveraging WebAssembly for Client-Side Encryption in WordPress

Integrating robust client-side encryption directly into WordPress content presents a significant security enhancement, particularly for sensitive data. Traditional server-side encryption, while common, leaves data vulnerable in transit and at rest on the server. By utilizing WebAssembly (Wasm), we can execute complex cryptographic operations directly within the user’s browser, ensuring that data is encrypted *before* it ever leaves their machine. This approach is ideal for building custom Gutenberg blocks that manage encrypted files or text snippets.

This guide details the construction of a custom Gutenberg block that leverages a WebAssembly module for AES-GCM encryption and decryption. We’ll cover the Wasm module compilation, its integration into a WordPress plugin, and the JavaScript logic for the Gutenberg block itself.

I. Compiling a WebAssembly Cryptography Module

For this example, we’ll use C++ and Emscripten to compile a simple AES-GCM encryption/decryption function into a WebAssembly module. AES-GCM is a symmetric encryption mode that provides both confidentiality and authenticity.

A. C++ Source Code (crypto.cpp)

We’ll define functions to initialize the AES context, encrypt data, and decrypt data. For simplicity, we’ll use a fixed key and nonce, but in a production scenario, these should be securely generated and managed.

#include <emscripten.h>
#include <vector>
#include <cstring> // For memcpy

// Basic AES-GCM implementation (simplified for demonstration)
// In a real-world scenario, use a well-vetted crypto library like mbedTLS or OpenSSL compiled for Wasm.
// This example uses a placeholder for actual AES-GCM logic.

// Placeholder for AES-GCM context and operations
struct AesGcmContext {
    // Placeholder for key, IV, etc.
    unsigned char key[32]; // 256-bit key
    unsigned char iv[12];  // 96-bit IV
};

extern "C" {
    // Function to initialize the context (simplified)
    EMSCRIPTEN_KEEPALIVE
    AesGcmContext* init_aes_gcm(const unsigned char* key, size_t key_len, const unsigned char* iv, size_t iv_len) {
        if (key_len != 32 || iv_len != 12) {
            return nullptr; // Invalid key or IV size
        }
        AesGcmContext* ctx = new AesGcmContext();
        memcpy(ctx->key, key, key_len);
        memcpy(ctx->iv, iv, iv_len);
        // In a real implementation, this would initialize the AES cipher with the key.
        return ctx;
    }

    // Placeholder for encryption function
    EMSCRIPTEN_KEEPALIVE
    std::vector encrypt_aes_gcm(AesGcmContext* ctx, const unsigned char* plaintext, size_t plaintext_len) {
        if (!ctx) return {};

        std::vector<unsigned char> ciphertext(plaintext_len + 16); // Placeholder for ciphertext + tag
        // --- Actual AES-GCM encryption logic would go here ---
        // For demonstration, we'll just append a dummy tag and slightly modify plaintext
        memcpy(ciphertext.data(), plaintext, plaintext_len);
        // Append a dummy 16-byte tag
        for (int i = 0; i < 16; ++i) {
            ciphertext[plaintext_len + i] = (unsigned char)(i * 3);
        }
        // --- End of placeholder logic ---
        return ciphertext;
    }

    // Placeholder for decryption function
    EMSCRIPTEN_KEEPALIVE
    std::vector<unsigned char> decrypt_aes_gcm(AesGcmContext* ctx, const unsigned char* ciphertext_with_tag, size_t ciphertext_with_tag_len) {
        if (!ctx || ciphertext_with_tag_len < 16) return {}; // Need at least tag length

        size_t ciphertext_len = ciphertext_with_tag_len - 16;
        std::vector<unsigned char> plaintext(ciphertext_len);
        // --- Actual AES-GCM decryption and tag verification logic would go here ---
        // For demonstration, we'll just copy the data before the dummy tag
        memcpy(plaintext.data(), ciphertext_with_tag, ciphertext_len);
        // In a real implementation, verify the tag here.
        // --- End of placeholder logic ---
        return plaintext;
    }

    // Function to free the context
    EMSCRIPTEN_KEEPALIVE
    void free_aes_gcm(AesGcmContext* ctx) {
        delete ctx;
    }
}

Note: The C++ code above is a highly simplified placeholder. For production use, you must integrate a robust, audited cryptographic library like mbedTLS or OpenSSL, compile it with Emscripten, and implement the actual AES-GCM encryption and decryption logic, including secure key/IV management and tag verification.

B. Compiling with Emscripten

Ensure you have Emscripten SDK installed. You can download it from emscripten.org.

Compile the C++ code into a WebAssembly module and its JavaScript glue code:

emcc crypto.cpp -o crypto.js -s WASM=1 -s EXPORTED_FUNCTIONS='["_init_aes_gcm", "_encrypt_aes_gcm", "_decrypt_aes_gcm", "_free_aes_gcm"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap", "getValue", "setValue", "UTF8ToString", "stringToUTF8", "allocate", "FS"]' -O3

This command:

  • emcc crypto.cpp -o crypto.js: Compiles crypto.cpp and outputs crypto.js (which includes Wasm binary or JS loader).
  • -s WASM=1: Enables WebAssembly output.
  • -s EXPORTED_FUNCTIONS='[...]‘: Makes specific C++ functions callable from JavaScript.
  • -s EXPORTED_RUNTIME_METHODS='[...]‘: Exposes Emscripten runtime functions needed for memory management and data transfer.
  • -O3: Enables aggressive optimization.

This will generate two files: crypto.js (JavaScript glue code) and crypto.wasm (the WebAssembly binary).

II. Creating the WordPress Plugin and Gutenberg Block

We’ll create a simple WordPress plugin to house our custom Gutenberg block. The block will provide an interface for users to input plaintext, encrypt it, and then display the ciphertext. A separate button will allow decryption back to plaintext.

A. Plugin Structure

Create a new folder in wp-content/plugins/ named secure-encrypt-block. Inside, create the following files and directories:

secure-encrypt-block/
├── secure-encrypt-block.php
├── build/
│   ├── index.js
│   ├── index.asset.php
│   └── wasm/
│       ├── crypto.js
│       └── crypto.wasm
└── src/
    ├── index.js
    └── block.json

B. Plugin Main File (secure-encrypt-block.php)

This file registers the block type and enqueues the necessary JavaScript and CSS.

<?php
/**
 * Plugin Name: Secure Encrypt Block
 * Description: A Gutenberg block for client-side file encryption using WebAssembly.
 * Version: 1.0.0
 * Author: Your Name
 * License: GPL-2.0-or-later
 * Text Domain: secure-encrypt-block
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

/**
 * Registers the block using the metadata loaded from the `block.json` file.
 * Behind the scenes, it registers also all assets so they can be enqueued
 * through the block editor in the corresponding `wp_enqueue_scripts` action.
 *
 * @see https://developer.wordpress.org/reference/functions/register_block_type/
 */
function secure_encrypt_block_init() {
    register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'secure_encrypt_block_init' );

C. Block Metadata (src/block.json)

Defines the block’s properties, including its name, category, and script/style handles.

{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "secure-encrypt-block/encrypt-vault",
    "version": "0.1.0",
    "title": "Secure Encrypt Vault",
    "category": "widgets",
    "icon": "lock",
    "description": "Encrypt and decrypt content client-side using WebAssembly.",
    "attributes": {
        "encryptedContent": {
            "type": "string",
            "default": ""
        },
        "decryptedContent": {
            "type": "string",
            "default": ""
        }
    },
    "editorScript": "file:./index.js",
    "editorStyle": "file:./index.css",
    "style": "file:./style-index.css",
    "viewScript": "file:./index.js"
}

D. JavaScript for the Block (src/index.js)

This is the core of our Gutenberg block. It handles user input, interacts with the WebAssembly module, and manages the block’s state.

First, we need to load the WebAssembly module. We’ll use Emscripten’s generated JavaScript glue code for this.

// Import necessary WordPress components
const { registerBlockType } = wp.blocks;
const { useState, useEffect, useRef } = wp.element;
const { RichText, InspectorControls } = wp.blockEditor;
const { PanelBody, Button, TextareaControl, Placeholder, Spinner } = wp.components;
const { __ } = wp.i18n;

// --- WebAssembly Module Loading ---
// Emscripten will generate crypto.js and crypto.wasm.
// We need to ensure these are available in the WordPress build process.
// For simplicity, we'll assume they are copied to build/wasm/
// In a real project, use a build tool like Webpack or Rollup to manage this.

let wasmModule = null;
let wasmLoaded = false;
const wasmModulePath = '/wp-content/plugins/secure-encrypt-block/build/wasm/crypto.js'; // Adjust path as needed

// Function to load the Wasm module
async function loadWasmModule() {
    if (wasmLoaded) return wasmModule;

    return new Promise((resolve, reject) => {
        // Emscripten's Module object
        const Module = {
            print: (text) => console.log('WASM stdout:', text),
            printErr: (text) => console.error('WASM stderr:', text),
            onRuntimeInitialized: () => {
                console.log('WebAssembly module initialized.');
                wasmLoaded = true;
                resolve(Module);
            },
            onAbort: (reason) => {
                console.error('WebAssembly runtime aborted:', reason);
                reject(new Error('WebAssembly runtime aborted.'));
            }
        };

        // Dynamically load the Emscripten-generated JS file
        const script = document.createElement('script');
        script.src = wasmModulePath;
        script.onload = () => {
            // The Emscripten Module object is now available globally or attached to window
            // We need to ensure it's properly instantiated.
            // If Module is not globally defined, you might need to adjust this.
            if (typeof Module === 'undefined') {
                 console.error("Emscripten Module object not found after script load.");
                 return reject(new Error("Emscripten Module object not found."));
            }
            // Instantiate the module
            const instance = Module(Module); // Pass Module to itself for instantiation
            wasmModule = instance;
            resolve(instance);
        };
        script.onerror = (error) => {
            console.error('Error loading WebAssembly module:', error);
            reject(error);
        };
        document.body.appendChild(script);
    });
}

// --- Helper functions for Wasm interaction ---

// Securely generate a 256-bit key and 96-bit IV (for demonstration)
// In production, manage keys securely (e.g., user-provided, server-generated and stored securely)
function generateSecureKeyAndIv() {
    const key = new Uint8Array(32); // 256 bits
    const iv = new Uint8Array(12);  // 96 bits
    window.crypto.getRandomValues(key);
    window.crypto.getRandomValues(iv);
    return { key, iv };
}

// Convert Uint8Array to hex string for display
function bytesToHexString(bytes) {
    return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
}

// Convert hex string to Uint8Array
function hexStringToBytes(hex) {
    const bytes = new Uint8Array(hex.length / 2);
    for (let i = 0; i < hex.length; i += 2) {
        bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
    }
    return bytes;
}

// --- Block Registration ---
registerBlockType('secure-encrypt-block/encrypt-vault', {
    title: __('Secure Encrypt Vault', 'secure-encrypt-block'),
    icon: 'lock',
    category: 'widgets',
    attributes: {
        encryptedContent: {
            type: 'string',
            default: '',
        },
        decryptedContent: {
            type: 'string',
            default: '',
        },
        // Store key and IV as hex strings for simplicity in attributes
        // WARNING: Storing keys directly in block attributes is INSECURE for production.
        // This is for demonstration purposes only. Keys should be managed externally.
        encryptionKeyHex: {
            type: 'string',
            default: '',
        },
        encryptionIvHex: {
            type: 'string',
            default: '',
        },
        isEncrypting: {
            type: 'boolean',
            default: false,
        },
        isDecrypting: {
            type: 'boolean',
            default: false,
        },
        error: {
            type: 'string',
            default: '',
        }
    },

    edit: ({ attributes, setAttributes }) => {
        const { encryptedContent, decryptedContent, encryptionKeyHex, encryptionIvHex, isEncrypting, isDecrypting, error } = attributes;

        // State for user input
        const [plaintextInput, setPlaintextInput] = useState('');
        const [keyInput, setKeyInput] = useState(encryptionKeyHex || '');
        const [ivInput, setIvInput] = useState(encryptionIvHex || '');
        const [isWasmReady, setIsWasmReady] = useState(false);

        // Load Wasm module on component mount
        useEffect(() => {
            loadWasmModule()
                .then(module => {
                    setIsWasmReady(true);
                    // If key/IV are not set, generate them
                    if (!keyInput && !ivInput) {
                        const { key, iv } = generateSecureKeyAndIv();
                        const keyHex = bytesToHexString(key);
                        const ivHex = bytesToHexString(iv);
                        setKeyInput(keyHex);
                        setIvInput(ivHex);
                        setAttributes({ encryptionKeyHex: keyHex, encryptionIvHex: ivHex });
                    }
                })
                .catch(err => {
                    console.error("Failed to load Wasm module:", err);
                    setAttributes({ error: __('Failed to load encryption module.', 'secure-encrypt-block') });
                });
        }, []);

        // Update attributes when key/IV inputs change
        useEffect(() => {
            setAttributes({ encryptionKeyHex: keyInput, encryptionIvHex: ivInput });
        }, [keyInput, ivInput]);

        const handleEncrypt = async () => {
            if (!isWasmReady || !wasmModule) {
                setAttributes({ error: __('Encryption module not ready.', 'secure-encrypt-block') });
                return;
            }
            if (!keyInput || !ivInput) {
                setAttributes({ error: __('Key and IV are required for encryption.', 'secure-encrypt-block') });
                return;
            }

            setAttributes({ isEncrypting: true, error: '' });

            try {
                const keyBytes = hexStringToBytes(keyInput);
                const ivBytes = hexStringToBytes(ivInput);

                if (keyBytes.length !== 32 || ivBytes.length !== 12) {
                    throw new Error('Invalid key or IV length. Key must be 32 bytes, IV must be 12 bytes.');
                }

                // Allocate memory for plaintext in Wasm heap
                const plaintextPtr = wasmModule._malloc(plaintextInput.length + 1);
                wasmModule.stringToUTF8(plaintextInput, plaintextPtr, plaintextInput.length + 1);

                // Allocate memory for key and IV
                const keyPtr = wasmModule._malloc(keyBytes.length);
                wasmModule.writeArrayToMemory(keyBytes, keyPtr);
                const ivPtr = wasmModule._malloc(ivBytes.length);
                wasmModule.writeArrayToMemory(ivBytes, ivPtr);

                // Initialize AES context
                const ctxPtr = wasmModule.ccall(
                    'init_aes_gcm',
                    'number', // return type
                    ['number', 'number', 'number', 'number'], // argument types
                    [keyPtr, keyBytes.length, ivPtr, ivBytes.length]
                );

                if (ctxPtr === 0) { // Check for null pointer return
                    throw new Error('Failed to initialize AES context. Check key/IV length.');
                }

                // Encrypt
                const ciphertextResultPtr = wasmModule.ccall(
                    'encrypt_aes_gcm',
                    'number', // return type (pointer to vector)
                    ['number', 'number', 'number'], // argument types (context pointer, plaintext pointer, plaintext length)
                    [ctxPtr, plaintextPtr, plaintextInput.length]
                );

                // Get the size of the returned vector (ciphertext + tag)
                // Emscripten's cwrap/ccall for std::vector returns a pointer to the vector object.
                // We need to read its size and data pointer.
                // This part is tricky and depends on Emscripten's memory layout for std::vector.
                // A common pattern is that the returned pointer is to a struct containing size and data.
                // For simplicity, let's assume `encrypt_aes_gcm` returns a pointer to the data,
                // and we need a way to get its size. A better approach would be to have the C++
                // function return the size and pass a buffer for the result.

                // --- REVISED Wasm Call for better memory management ---
                // Let's modify the C++ to return a pointer to the data and its size.
                // For now, we'll use a simplified approach assuming the JS glue handles it.
                // A more robust solution would involve `allocate` and `getValue` for size.

                // Assuming `encrypt_aes_gcm` returns a pointer to the encrypted data (including tag)
                // and we need to know its size. Emscripten's default `std::vector` handling might
                // return a pointer to the vector's internal buffer.
                // A common pattern is to return a struct or use output parameters.

                // Let's simulate getting the size and data pointer.
                // In a real scenario, you'd use `getValue(pointer, '*')` for the data pointer
                // and `getValue(pointer + sizeof(pointer_type), 'i32')` for the size.
                // For this example, we'll assume the returned pointer is directly to the data
                // and we need to infer size or have C++ return it.

                // --- Simplified approach: Assume C++ returns a pointer to the data and we know the size ---
                // This is NOT robust. A proper implementation would involve C++ returning size.
                // For demonstration, let's assume the returned pointer is to the data and its size is plaintextInput.length + 16 (for tag)
                const encryptedDataSize = plaintextInput.length + 16; // Placeholder size
                const encryptedDataPtr = wasmModule.ccall(
                    'encrypt_aes_gcm',
                    'number', // return type (pointer to data)
                    ['number', 'number', 'number'], // argument types (context pointer, plaintext pointer, plaintext length)
                    [ctxPtr, plaintextPtr, plaintextInput.length]
                );

                if (encryptedDataPtr === 0) {
                    throw new Error('Encryption failed.');
                }

                // Read the encrypted data from Wasm memory
                const encryptedBytes = new Uint8Array(encryptedDataSize);
                for (let i = 0; i < encryptedDataSize; ++i) {
                    encryptedBytes[i] = wasmModule.getValue(encryptedDataPtr + i, 'i8');
                }

                // Convert to hex string for storage/display
                const encryptedContentHex = bytesToHexString(encryptedBytes);

                setAttributes({ encryptedContent: encryptedContentHex });

                // Clean up Wasm memory
                wasmModule._free(plaintextPtr);
                wasmModule._free(keyPtr);
                wasmModule._free(ivPtr);
                wasmModule._free(ctxPtr); // Free the context
                // wasmModule._free(encryptedDataPtr); // Free the returned data if C++ allocated it

            } catch (e) {
                console.error("Encryption error:", e);
                setAttributes({ error: e.message || __('An unknown error occurred during encryption.', 'secure-encrypt-block') });
            } finally {
                setAttributes({ isEncrypting: false });
            }
        };

        const handleDecrypt = async () => {
            if (!isWasmReady || !wasmModule) {
                setAttributes({ error: __('Encryption module not ready.', 'secure-encrypt-block') });
                return;
            }
            if (!encryptedContent) {
                setAttributes({ error: __('No encrypted content to decrypt.', 'secure-encrypt-block') });
                return;
            }
            if (!keyInput || !ivInput) {
                setAttributes({ error: __('Key and IV are required for decryption.', 'secure-encrypt-block') });
                return;
            }

            setAttributes({ isDecrypting: true, error: '' });

            try {
                const encryptedBytes = hexStringToBytes(encryptedContent);
                const keyBytes = hexStringToBytes(keyInput);
                const ivBytes = hexStringToBytes(ivInput);

                if (keyBytes.length !== 32 || ivBytes.length !== 12) {
                    throw new Error('Invalid key or IV length. Key must be 32 bytes, IV must be 12 bytes.');
                }
                if (encryptedBytes.length < 16) { // Minimum size for ciphertext + tag
                    throw new Error('Invalid encrypted data format.');
                }

                // Allocate memory for encrypted data (ciphertext + tag)
                const encryptedDataPtr = wasmModule._malloc(encryptedBytes.length);
                wasmModule.writeArrayToMemory(encryptedBytes, encryptedDataPtr);

                // Allocate memory for key and IV
                const keyPtr = wasmModule._malloc(keyBytes.length);
                wasmModule.writeArrayToMemory(keyBytes, keyPtr);
                const ivPtr = wasmModule._malloc(ivBytes.length);
                wasmModule.writeArrayToMemory(ivBytes, ivPtr);

                // Initialize AES context
                const ctxPtr = wasmModule.ccall(
                    'init_aes_gcm',
                    'number',
                    ['number', 'number', 'number', 'number'],
                    [keyPtr, keyBytes.length, ivPtr, ivBytes.length]
                );

                if (ctxPtr === 0) {
                    throw new Error('Failed to initialize AES context. Check key/IV length.');
                }

                // Decrypt
                const decryptedResultPtr = wasmModule.ccall(
                    'decrypt_aes_gcm',
                    'number', // return type (pointer to vector)
                    ['number', 'number', 'number'], // argument types (context pointer, ciphertext pointer, ciphertext length)
                    [ctxPtr, encryptedDataPtr, encryptedBytes.length]
                );

                if (decryptedResultPtr === 0) {
                    throw new Error('Decryption failed. Possibly invalid key, IV, or corrupted data.');
                }

                // Read the decrypted data from Wasm memory
                // Assuming the C++ function returns a pointer to the plaintext data
                // and its size is encryptedBytes.length - 16 (for tag)
                const decryptedDataSize = encryptedBytes.length - 16; // Placeholder size
                const decryptedBytes = new Uint8Array(decryptedDataSize);
                for (let i = 0; i < decryptedDataSize; ++i) {
                    decryptedBytes[i] = wasmModule.getValue(decryptedResultPtr + i, 'i8');
                }

                const decryptedContent = wasmModule.UTF8ToString(decryptedResultPtr); // If C++ returns a null-terminated string

                setAttributes({ decryptedContent: decryptedContent });

                // Clean up Wasm memory
                wasmModule._free(encryptedDataPtr);
                wasmModule._free(keyPtr);
                wasmModule._free(ivPtr);
                wasmModule._free(ctxPtr);
                // wasmModule._free(decryptedResultPtr); // Free if C++ allocated it

            } catch (e) {
                console.error("Decryption error:", e);
                setAttributes({ error: e.message || __('An unknown error occurred during decryption.', 'secure-encrypt-block') });
            } finally {
                setAttributes({ isDecrypting: false });
            }
        };

        // Render placeholder if Wasm is not ready
        if (!isWasmReady) {
            return (
                <Placeholder
                    icon={Spinner}
                    label={__('Secure Encrypt Vault', 'secure-encrypt-block')}
                    instructions={__('Loading encryption module...', 'secure-encrypt-block')}
                />
            );
        }

        return (
            <>
                {/* Inspector Controls for settings */}
                <InspectorControls>
                    <PanelBody title={__('Encryption Settings', 'secure-encrypt-block')} initialOpen={true}>
                        <TextareaControl
                            label={__('Encryption Key (Hex)', 'secure-encrypt-block')}
                            value={keyInput}
                            onChange={setKeyInput}
                            help={__('Must be 32 bytes (64 hex characters). Generate securely.', 'secure-encrypt-block')}
                            rows={3}
                        />
                        <TextareaControl
                            label={__('Initialization Vector (Hex)', 'secure-encrypt-block')}
                            value={ivInput}
                            onChange={setIvInput}
                            help={__('Must be 12 bytes (24 hex characters). Generate securely.', 'secure-encrypt-block')}
                            rows={3}
                        />
                        <Button isSecondary onClick={() => {
                            const { key, iv } = generateSecureKeyAndIv();
                            const keyHex = bytesToHexString(key);
                            const ivHex = bytesToHexString(iv);
                            setKeyInput(keyHex);
                            setIvInput(ivHex);
                            setAttributes({ encryptionKeyHex: keyHex, encryptionIvHex: ivHex });
                        }}>
                            {__('Generate New Key/IV', 'secure-encrypt-block')}
                        </Button>
                    </PanelBody>
                </InspectorControls>

                {/* Block Content */}
                <div className="secure-encrypt-block-editor">
                    <h3>{__('Plaintext Input', 'secure-encrypt-block')}</h3>
                    <RichText
                        tagName="div"
                        value={plaintextInput}
                        onChange={setPlaintextInput}
                        placeholder={__('Enter text to encrypt...', 'secure-encrypt-block')}
                        allowedFormats={[]}
                        className="secure-encrypt-block-plaintext-input"
                    />

                    <div className="secure-encrypt-block-actions">
                        <Button
                            isPrimary
                            onClick={handleEncrypt}
                            disabled={isEncrypting || !plaintextInput || !isWasmReady}
                            isBusy={isEncrypting}
                        >
                            {__('Encrypt', 'secure-encrypt-block')}
                        </Button>
                    </div>

                    {encryptedContent && (
                        <>
                            <h3>{__('Encrypted Content (Hex)', 'secure-encrypt-block')}</h3>
                            <RichText
                                tagName="div"
                                value={encryptedContent}
                                onChange={(newVal) => setAttributes({ encryptedContent: newVal })}
                                placeholder={__('Encrypted data will appear here...', 'secure-encrypt-block')}
                                readOnly
                                className="secure-encrypt-block-encrypted-output"
                            />
                            <div className="secure-encrypt-block-actions">
                                <Button
                                    isSecondary
                                    onClick={handleDecrypt}
                                    disabled={isDecrypting || !encryptedContent || !isWasmReady}
                                    isBusy={isDecrypting}
                                >
                                    {__('Decrypt', 'secure-encrypt-block')}
                                </Button>
                            </div>
                        </>
                    )}

                    {decryptedContent && (
                        <>
                            <h3>{__('Decrypted Content', 'secure-encrypt-block')}</h3>
                            <RichText
                                tagName="div"
                                value={decryptedContent}
                                onChange={(newVal) => setAttributes({ decryptedContent: newVal })}
                                placeholder={__('Decrypted data will appear here...', 'secure-encrypt-block')}
                                readOnly
                                className="secure-encrypt-block-decrypted-output"
                            />
                        </>
                    )}

                    {error && (
                        <div className="secure-encrypt-block-error">
                            {__('Error:', 'secure-encrypt-block')} {error}
                        </div>
                    )}
                </div>
            </>
        );
    },

    save: ({ attributes }) => {
        const { encryptedContent } = attributes;

        // The save function should output static HTML.
        // We cannot run JavaScript or Wasm here.
        // Therefore, we only save the encrypted content.
        // Decryption will be handled by the frontend JavaScript when the page loads.
        return (
            <div className="secure-encrypt-block-frontend" data-encrypted-content={encryptedContent}>
                <p>{__('Content is encrypted. Decryption requires JavaScript.', 'secure-encrypt-block')}</p>
                {/* Optionally, display a placeholder or a message */}
            </div>
        );
    },
});

// --- Frontend Script for Decryption ---
// This part runs on the frontend to decrypt content saved by the block.
document.addEventListener('DOMContentLoaded', () => {
    const encryptBlockElements = document.querySelectorAll('.secure-encrypt-block-frontend');

    encryptBlockElements.forEach(blockElement => {
        const encryptedContentHex = blockElement.dataset.encryptedContent;

        if (!encryptedContentHex) return;

        // We need to load the Wasm module again for the frontend.
        // This is a simplified approach. In a real app, you'd likely have a single
        // Wasm loader for both editor and frontend, or enqueue a separate script.
        loadW

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • How to securely integrate HubSpot Contacts endpoints into WordPress custom plugins using Rewrite API custom endpoints
  • Troubleshooting namespace class loading collisions in production when using modern Sage Roots modern environments wrappers
  • Troubleshooting WooCommerce hook execution loops in production when using modern Classic Core PHP wrappers
  • Implementing automated compliance reporting for custom internal server status logs ledgers using dompdf library
  • Step-by-Step Guide to building a custom CSV bulk exporter block for Gutenberg using SolidJS high-performance reactive components

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (609)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (824)
  • PHP (5)
  • PHP Development (30)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (587)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (133)
  • WordPress Theme Development (357)

Recent Posts

  • How to securely integrate HubSpot Contacts endpoints into WordPress custom plugins using Rewrite API custom endpoints
  • Troubleshooting namespace class loading collisions in production when using modern Sage Roots modern environments wrappers
  • Troubleshooting WooCommerce hook execution loops in production when using modern Classic Core PHP wrappers

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (824)
  • Debugging & Troubleshooting (609)
  • Security & Compliance (587)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala