零宽字符隐写工具(可以定制字并隐藏任何字)

零宽字符隐写工具(可以定制字并隐藏任何字)

工具介绍

零宽字符隐写工具原理就是利用零宽字符(Zero Width Characters,如 \u200B)不可见的特性,将秘密信息二进制化后隐藏在普通文本(宿主文本)中。复制出来的文本看起来很正常,但粘贴到解密区就能看到隐藏内容。整个工具封装在一个 HTML 文件中,使用了 Tailwind CSS 做 UI,支持自动跟随系统的暗黑模式。

工具特点:

  • 纯本地运行:所有逻辑都在浏览器完成,不上传服务器,安全隐私。
  • 单文件:HTML/CSS/JS 合一,双击即用,方便部署或收藏。
  • UI 美观:适配移动端和桌面端,支持深色/浅色模式切换。
  • 一键复制:交互体验优化,支持一键复制加密结果。

使用场景:

  • 在公开论坛/群聊传递秘密信息。
  • 给文字添加“数字水印”。
  • 单纯觉得好玩,整蛊朋友(比如表面发一句“你好”,实际隐藏了“V我50”)。

目前测试情况: QQ全端支持,微信手机端不支持发送零宽,只接受和复制粘贴
源码:代码直接保存为 .html 文件,用浏览器打开即可
注意:该工具不是加密工具,只是将字变成了零宽大小,专业的零宽加密工具找风导的

图片[1]-零宽字符隐写工具(可以定制字并隐藏任何字)-QQ沐编程

源码如下

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Zero Text - 零宽文本加密</title>
    <!-- 引入 Tailwind CSS (CDN) -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 配置 Tailwind 主题 -->
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    colors: {
                        primary: '#3b82f6', // blue-500
                        primaryHover: '#2563eb', // blue-600
                        darkBg: '#0f172a', // slate-900
                        darkCard: '#1e293b', // slate-800
                    },
                    boxShadow: {
                        'glow': '0 0 15px rgba(59, 130, 246, 0.5)',
                    }
                }
            }
        }
    </script>
    <style>
        /* 自定义滚动条样式 */
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }
        ::-webkit-scrollbar-track {
            background: transparent;
        }
        ::-webkit-scrollbar-thumb {
            background: #cbd5e1;
            border-radius: 4px;
        }
        .dark ::-webkit-scrollbar-thumb {
            background: #475569;
        }
        ::-webkit-scrollbar-thumb:hover {
            background: #94a3b8;
        }

        /* 动画过渡 */
        .transition-all-300 {
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        /* 文本域聚焦样式 */
        .input-group:focus-within label {
            color: #3b82f6;
        }
    </style>
</head>
<body class="min-h-screen bg-gray-100 dark:bg-darkBg text-slate-800 dark:text-slate-200 transition-colors duration-300 flex flex-col items-center py-8 px-4 font-sans">

    <!-- 顶部标题栏 -->
    <header class="w-full max-w-5xl flex justify-between items-center mb-8">
        <div class="flex items-center gap-3">
            <div class="bg-blue-600 p-2 rounded-lg shadow-lg text-white">
                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
            </div>
            <div>
                <h1 class="text-2xl font-bold tracking-tight text-slate-900 dark:text-white">Zero Text</h1>
                <p class="text-xs text-slate-500 dark:text-slate-400">零宽字符隐写工具</p>
            </div>
        </div>

        <div class="flex items-center gap-4">
            <!-- GitHub Link -->
            <a href="https://www.52pojie.cn/" target="_blank" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-slate-700 transition-colors text-slate-600 dark:text-slate-400" title="View on GitHub">
                <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
            </a>
            <!-- Theme Toggle -->
            <button id="themeToggle" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-slate-700 transition-colors text-slate-600 dark:text-slate-400">
                <svg id="iconSun" class="hidden w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
                <svg id="iconMoon" class="block w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path></svg>
            </button>
        </div>
    </header>

    <!-- 主容器 -->
    <main class="w-full max-w-5xl grid grid-cols-1 md:grid-cols-2 gap-8">

        <!-- 左侧:加密卡片 -->
        <section class="bg-white dark:bg-darkCard rounded-2xl shadow-xl overflow-hidden border border-gray-200 dark:border-slate-700 flex flex-col h-full transition-all-300 hover:shadow-2xl">
            <div class="bg-gradient-to-r from-blue-500 to-blue-600 p-4">
                <h2 class="text-white font-semibold flex items-center gap-2">
                    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>
                    加密 / 隐藏信息
                </h2>
            </div>

            <div class="p-6 flex flex-col gap-5 flex-1">
                <!-- 宿主文本 -->
                <div class="input-group">
                    <label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5 transition-colors">
                        1. 宿主文本(表面显示的内容)
                    </label>
                    <textarea id="carrierText" class="w-full h-24 p-3 rounded-lg border border-gray-300 dark:border-slate-600 bg-gray-50 dark:bg-slate-900/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all resize-none text-sm" placeholder="例如:今天天气真不错,要不要一起去喝咖啡?"></textarea>
                </div>

                <!-- 秘密文本 -->
                <div class="input-group">
                    <label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5 transition-colors">
                        2. 秘密文本(要隐藏的内容)
                    </label>
                    <textarea id="secretText" class="w-full h-24 p-3 rounded-lg border border-gray-300 dark:border-slate-600 bg-gray-50 dark:bg-slate-900/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all resize-none text-sm" placeholder="例如:你好鸭"></textarea>
                </div>

                <!-- 按钮区 -->
                <button id="encryptBtn" class="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl shadow-lg shadow-blue-500/30 transition-all active:scale-[0.98] flex items-center justify-center gap-2 group">
                    <span class="group-hover:scale-110 transition-transform">🔒</span>
                    生成加密文本
                </button>

                <!-- 结果区 -->
                <div class="relative mt-2">
                    <label class="block text-xs font-semibold uppercase text-slate-400 dark:text-slate-500 mb-1 tracking-wider">
                        加密结果
                    </label>
                    <div class="relative group">
                        <div id="encryptResult" class="w-full h-28 p-3 pr-12 rounded-lg border-2 border-dashed border-gray-300 dark:border-slate-600 bg-gray-50 dark:bg-slate-900/50 text-sm text-slate-500 dark:text-slate-400 break-all overflow-y-auto font-mono transition-colors group-hover:border-blue-300 dark:group-hover:border-slate-500">
                            (等待生成...)
                        </div>

                        <!-- 复制按钮 (悬浮) -->
                        <button id="copyEncryptBtn" disabled class="absolute top-2 right-2 p-2 bg-white dark:bg-slate-700 border border-gray-200 dark:border-slate-600 rounded-md shadow-sm text-slate-500 hover:text-blue-600 hover:border-blue-200 dark:hover:text-blue-400 transition-all disabled:opacity-50 disabled:cursor-not-allowed group-hover:opacity-100" title="复制结果">
                            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
                        </button>
                    </div>
                </div>
            </div>
        </section>

        <!-- 右侧:解密卡片 -->
        <section class="bg-white dark:bg-darkCard rounded-2xl shadow-xl overflow-hidden border border-gray-200 dark:border-slate-700 flex flex-col h-full transition-all-300 hover:shadow-2xl">
            <div class="bg-gradient-to-r from-emerald-500 to-emerald-600 p-4">
                <h2 class="text-white font-semibold flex items-center gap-2">
                    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>
                    解密 / 提取信息
                </h2>
            </div>

            <div class="p-6 flex flex-col gap-5 flex-1">
                <!-- 解密输入 -->
                <div class="input-group">
                    <label class="block text-sm font-medium text-slate-600 dark:text-slate-400 mb-1.5 transition-colors">
                        粘贴包含零宽字符的文本
                    </label>
                    <textarea id="decodeInput" class="w-full h-[220px] p-3 rounded-lg border border-gray-300 dark:border-slate-600 bg-gray-50 dark:bg-slate-900/50 focus:ring-2 focus:ring-emerald-500 focus:border-transparent outline-none transition-all resize-none text-sm" placeholder="在此处粘贴..."></textarea>
                </div>

                <!-- 按钮区 -->
                <button id="decryptBtn" class="w-full py-3 bg-emerald-600 hover:bg-emerald-700 text-white font-semibold rounded-xl shadow-lg shadow-emerald-500/30 transition-all active:scale-[0.98] flex items-center justify-center gap-2 group">
                    <span class="group-hover:scale-110 transition-transform">🔓</span>
                    解密提取内容
                </button>

                <!-- 结果区 -->
                <div class="relative mt-2 flex-1 flex flex-col">
                    <label class="block text-xs font-semibold uppercase text-slate-400 dark:text-slate-500 mb-1 tracking-wider">
                        还原结果
                    </label>
                    <div class="relative group flex-1">
                        <div id="decryptResult" class="w-full h-full min-h-[112px] p-3 pr-12 rounded-lg border-2 border-dashed border-gray-300 dark:border-slate-600 bg-gray-50 dark:bg-slate-900/50 text-sm text-slate-500 dark:text-slate-400 break-all overflow-y-auto font-mono transition-colors group-hover:border-emerald-300 dark:group-hover:border-slate-500">
                            (等待解密...)
                        </div>

                         <!-- 复制按钮 (悬浮) -->
                         <button id="copyDecryptBtn" disabled class="absolute top-2 right-2 p-2 bg-white dark:bg-slate-700 border border-gray-200 dark:border-slate-600 rounded-md shadow-sm text-slate-500 hover:text-emerald-600 hover:border-emerald-200 dark:hover:text-emerald-400 transition-all disabled:opacity-50 disabled:cursor-not-allowed group-hover:opacity-100" title="复制结果">
                            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
                        </button>
                    </div>
                </div>
            </div>
        </section>

    </main>

    <footer class="mt-8 text-center text-xs text-slate-400 dark:text-slate-500">
        <p>🔒 所有加密/解密均在本地浏览器完成,不会上传数据</p>
    </footer>

    <!-- JavaScript 逻辑 -->
    <script>
        // ==========================================
        // 核心加密解密逻辑 (保持不变)
        // ==========================================
        const ZWC = { 0: '\u200B', 1: '\u200C' };

        function textToBinary(text) {
            const utf8 = new TextEncoder().encode(text);
            return Array.from(utf8)
                .map((b) => b.toString(2).padStart(8, '0'))
                .join('');
        }

        function binaryToText(bin) {
            const bytesMatch = bin.match(/.{8}/g);
            if (!bytesMatch) return '';
            const bytes = bytesMatch.map((b) => parseInt(b, 2));
            return new TextDecoder().decode(new Uint8Array(bytes));
        }

        function binaryToZWC(bin) {
            return bin.split('').map((b) => ZWC[Number(b)]).join('');
        }

        function zwcToBinary(zwcText) {
            return [...zwcText]
                .map((c) => (c === '\u200B' ? '0' : c === '\u200C' ? '1' : ''))
                .join('');
        }

        function insertZWCIntoCarrier(carrier, zwc) {
            if (!carrier) carrier = " "; 
            const mid = Math.floor(carrier.length / 2);
            return carrier.slice(0, mid) + zwc + carrier.slice(mid);
        }

        // ==========================================
        // DOM 操作
        // ==========================================

        document.addEventListener('DOMContentLoaded', () => {
            const carrierInput = document.getElementById('carrierText');
            const secretInput = document.getElementById('secretText');
            const encryptBtn = document.getElementById('encryptBtn');
            const encryptResultDiv = document.getElementById('encryptResult');
            const copyEncryptBtn = document.getElementById('copyEncryptBtn');

            const decodeInput = document.getElementById('decodeInput');
            const decryptBtn = document.getElementById('decryptBtn');
            const decryptResultDiv = document.getElementById('decryptResult');
            const copyDecryptBtn = document.getElementById('copyDecryptBtn');

            const themeToggle = document.getElementById('themeToggle');
            const iconSun = document.getElementById('iconSun');
            const iconMoon = document.getElementById('iconMoon');

            let encryptedValue = '';
            let decryptedValue = '';

            // 辅助函数:更新结果框状态
            function updateResultBox(element, text, isPlaceholder = false) {
                element.innerText = text;
                if (isPlaceholder) {
                    element.classList.add('text-slate-500', 'dark:text-slate-400');
                    element.classList.remove('text-slate-800', 'dark:text-slate-200');
                } else {
                    element.classList.remove('text-slate-500', 'dark:text-slate-400');
                    element.classList.add('text-slate-800', 'dark:text-slate-200');
                }
            }

            // --- 加密 ---
            encryptBtn.addEventListener('click', () => {
                const secret = secretInput.value;
                const carrier = carrierInput.value;

                if (!secret) {
                    // 简单的震动反馈或边框红色提示
                    secretInput.classList.add('ring-2', 'ring-red-500');
                    setTimeout(() => secretInput.classList.remove('ring-2', 'ring-red-500'), 500);
                    return;
                }

                try {
                    const binary = textToBinary(secret);
                    const zwcText = binaryToZWC(binary);
                    const final = insertZWCIntoCarrier(carrier || 'Processing...', zwcText);

                    encryptedValue = final;
                    updateResultBox(encryptResultDiv, final);
                    copyEncryptBtn.disabled = false;

                    // 视觉反馈:稍微闪烁一下结果框
                    encryptResultDiv.classList.add('bg-blue-50', 'dark:bg-blue-900/20');
                    setTimeout(() => encryptResultDiv.classList.remove('bg-blue-50', 'dark:bg-blue-900/20'), 300);
                } catch (e) {
                    console.error(e);
                    alert('加密失败');
                }
            });

            // --- 解密 ---
            decryptBtn.addEventListener('click', () => {
                const input = decodeInput.value;
                if (!input) return;

                try {
                    const zwcChars = [...input]
                        .filter((c) => c === '\u200B' || c === '\u200C')
                        .join('');

                    if (!zwcChars) {
                        updateResultBox(decryptResultDiv, "⚠️ 未检测到隐藏信息", true);
                        decryptedValue = "";
                        copyDecryptBtn.disabled = true;
                        return;
                    }

                    const binary = zwcToBinary(zwcChars);
                    const text = binaryToText(binary);

                    decryptedValue = text;
                    updateResultBox(decryptResultDiv, text);
                    copyDecryptBtn.disabled = false;

                    // 视觉反馈
                    decryptResultDiv.classList.add('bg-emerald-50', 'dark:bg-emerald-900/20');
                    setTimeout(() => decryptResultDiv.classList.remove('bg-emerald-50', 'dark:bg-emerald-900/20'), 300);

                } catch (e) {
                    console.error(e);
                    updateResultBox(decryptResultDiv, "❌ 解密失败:数据损坏", true);
                }
            });

            // --- 复制 ---
            async function copyToClipboard(text, btnElement) {
                if (!text) return;
                try {
                    await navigator.clipboard.writeText(text);
                    const originalIcon = btnElement.innerHTML;

                    // 切换为打钩图标
                    btnElement.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-500"><polyline points="20 6 9 17 4 12"></polyline></svg>`;

                    setTimeout(() => {
                        btnElement.innerHTML = originalIcon;
                    }, 1500);
                } catch (err) {
                    alert('复制失败');
                }
            }

            copyEncryptBtn.addEventListener('click', () => {
                copyToClipboard(encryptedValue, copyEncryptBtn);
            });

            copyDecryptBtn.addEventListener('click', () => {
                copyToClipboard(decryptedValue, copyDecryptBtn);
            });

            // --- 深色模式 ---
            function updateThemeIcon(isDark) {
                if (isDark) {
                    iconSun.classList.remove('hidden');
                    iconMoon.classList.add('hidden');
                } else {
                    iconSun.classList.add('hidden');
                    iconMoon.classList.remove('hidden');
                }
            }

            function initTheme() {
                const stored = localStorage.getItem('theme');
                const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

                if (stored === 'dark' || (!stored && prefersDark)) {
                    document.documentElement.classList.add('dark');
                    updateThemeIcon(true);
                } else {
                    document.documentElement.classList.remove('dark');
                    updateThemeIcon(false);
                }
            }

            themeToggle.addEventListener('click', () => {
                const isDark = document.documentElement.classList.toggle('dark');
                localStorage.setItem('theme', isDark ? 'dark' : 'light');
                updateThemeIcon(isDark);
            });

            initTheme();
        });
    </script>
</body>
</html>
© 版权声明
THE END
喜欢就支持一下吧
点赞8赞赏 分享