分享一个AES-256-CBC加解密网页源码

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=...
分享一个AES-256-CBC加解密网页源码
分享一个AES-256-CBC加解密网页源码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  <title>AES-256-CBC 加解密工具 | 在线对称加密</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: linear-gradient(145deg, #1a1e2b 0%, #2a2f3f 100%);
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: 'Segoe UI', Roboto, system-ui, -apple-system, sans-serif;
      padding: 1.5rem;
      margin: 0;
    }

    .container {
      max-width: 750px;
      width: 100%;
      background: rgba(255, 255, 255, 0.07);
      backdrop-filter: blur(18px);
      -webkit-backdrop-filter: blur(18px);
      border-radius: 2.5rem;
      padding: 2.2rem 2rem;
      box-shadow: 0 30px 50px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.1);
      border: 1px solid rgba(255, 255, 255, 0.15);
      transition: all 0.2s ease;
    }

    h1 {
      text-align: center;
      font-weight: 500;
      font-size: 2.1rem;
      letter-spacing: 2px;
      color: #e0e5f0;
      margin-bottom: 0.3rem;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 0.5rem;
    }
    h1 span {
      background: #3b82f6;
      color: white;
      font-size: 1rem;
      font-weight: 600;
      padding: 0.2rem 0.9rem;
      border-radius: 30px;
      letter-spacing: 0.5px;
    }

    .subtitle {
      text-align: center;
      color: #9aa4bf;
      margin-bottom: 2.2rem;
      font-size: 0.95rem;
      border-bottom: 1px dashed rgba(255,255,255,0.2);
      padding-bottom: 1.2rem;
    }

    .field {
      margin-bottom: 1.5rem;
    }

    label {
      display: flex;
      align-items: center;
      gap: 0.4rem;
      font-weight: 500;
      color: #cbd5e1;
      margin-bottom: 0.5rem;
      font-size: 0.9rem;
      text-transform: uppercase;
      letter-spacing: 0.4px;
    }

    label i {
      font-style: normal;
      font-size: 1rem;
    }

    textarea, input {
      width: 100%;
      background: rgba(10, 15, 25, 0.7);
      border: 1px solid rgba(255, 255, 255, 0.2);
      border-radius: 1.2rem;
      padding: 0.9rem 1.2rem;
      font-size: 0.95rem;
      color: #f1f5f9;
      outline: none;
      transition: all 0.25s;
      font-family: 'Fira Code', 'JetBrains Mono', monospace;
      resize: vertical;
      backdrop-filter: blur(4px);
    }

    textarea:focus, input:focus {
      border-color: #3b82f6;
      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.35);
      background: rgba(20, 25, 40, 0.8);
    }

    textarea {
      min-height: 100px;
    }

    .key-wrapper {
      display: flex;
      gap: 0.6rem;
      align-items: center;
    }

    .key-wrapper input {
      flex: 1;
    }

    .icon-btn {
      background: rgba(255, 255, 255, 0.08);
      border: 1px solid rgba(255, 255, 255, 0.25);
      color: #cbd5e1;
      padding: 0.7rem 1rem;
      border-radius: 1rem;
      font-size: 1.1rem;
      cursor: pointer;
      transition: 0.2s;
      display: flex;
      align-items: center;
      justify-content: center;
      backdrop-filter: blur(8px);
    }

    .icon-btn:hover {
      background: rgba(59, 130, 246, 0.25);
      border-color: #3b82f6;
      color: white;
    }

    .actions {
      display: flex;
      gap: 1rem;
      margin: 2rem 0 1.2rem;
      flex-wrap: wrap;
    }

    .btn {
      flex: 1;
      min-width: 120px;
      background: rgba(255, 255, 255, 0.05);
      border: 1px solid rgba(255, 255, 255, 0.2);
      padding: 0.9rem 1.2rem;
      border-radius: 1.5rem;
      font-weight: 600;
      font-size: 1rem;
      color: #e2e8f0;
      cursor: pointer;
      backdrop-filter: blur(10px);
      transition: all 0.25s;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 0.4rem;
      letter-spacing: 0.5px;
    }

    .btn-encrypt {
      background: #2563eb;
      border-color: #3b82f6;
      box-shadow: 0 8px 18px -6px #1e3a8a;
      color: white;
    }

    .btn-encrypt:hover {
      background: #1d4ed8;
      border-color: #60a5fa;
      box-shadow: 0 10px 22px -6px #1e3a8a;
    }

    .btn-decrypt {
      background: #7c3aed;
      border-color: #8b5cf6;
      box-shadow: 0 8px 18px -6px #4c1d95;
      color: white;
    }

    .btn-decrypt:hover {
      background: #6d28d9;
      border-color: #a78bfa;
    }

    .btn-copy {
      background: rgba(255, 255, 255, 0.08);
      border-color: rgba(255, 255, 255, 0.25);
    }

    .btn-copy:hover {
      background: rgba(255, 255, 255, 0.18);
      border-color: #94a3b8;
    }

    .info-row {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 0.8rem;
      font-size: 0.8rem;
      color: #94a3b8;
      flex-wrap: wrap;
      gap: 0.5rem;
    }

    .badge {
      background: rgba(0,0,0,0.4);
      padding: 0.3rem 1rem;
      border-radius: 20px;
      backdrop-filter: blur(4px);
    }

    hr {
      border-color: rgba(255,255,255,0.1);
      margin: 1.2rem 0 0.8rem;
    }

    .footer-note {
      color: #7f8aa0;
      font-size: 0.8rem;
      text-align: center;
    }

    @media (max-width: 500px) {
      .container {
        padding: 1.5rem;
      }
      .actions {
        flex-direction: column;
      }
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>
      🔐 AES-256-CBC
      <span>Crypto</span>
    </h1>
    <div class="subtitle">使用 Web Crypto API · 安全客户端加解密</div>

    <!-- 密钥输入 -->
    <div class="field">
      <label><i>🔑</i> 密钥 (32字节 / 256位)</label>
      <div class="key-wrapper">
        <input type="text" id="keyInput" placeholder="输入32字符密钥或点击生成随机密钥" autocomplete="off" spellcheck="false">
        <button class="icon-btn" id="generateKeyBtn" title="生成随机256位密钥 (hex)">🎲</button>
        <button class="icon-btn" id="copyKeyBtn" title="复制密钥">📋</button>
      </div>
      <div class="info-row">
        <span id="keyLengthIndicator">⚡ 长度: 0 / 32 字节</span>
        <span class="badge" id="keyStatus">未设置</span>
      </div>
    </div>

    <!-- 明文输入 -->
    <div class="field">
      <label><i>📝</i> 明文 (Plaintext)</label>
      <textarea id="plaintextInput" placeholder="输入要加密的内容..."></textarea>
    </div>

    <!-- 密文输入 (Base64) -->
    <div class="field">
      <label><i>🔒</i> 密文 (Base64格式)</label>
      <textarea id="ciphertextInput" placeholder="输入Base64密文进行解密..."></textarea>
    </div>

    <!-- 操作按钮组 -->
    <div class="actions">
      <button class="btn btn-encrypt" id="encryptBtn">🔒 加密</button>
      <button class="btn btn-decrypt" id="decryptBtn">🔓 解密</button>
      <button class="btn btn-copy" id="copyCipherBtn">📋 复制密文</button>
    </div>

    <!-- 结果/状态信息 -->
    <div class="info-row" style="justify-content: center;">
      <span id="operationStatus" class="badge" style="background: #1e293b;">⚪ 等待操作</span>
    </div>
    <hr>
    <div class="footer-note">
      AES-256-CBC · 每次加密使用随机IV (16字节) · 密文格式: IV + 密文 (Base64)
    </div>
  </div>

  <script>
    (function() {
      // DOM 元素
      const keyInput = document.getElementById('keyInput');
      const plaintextInput = document.getElementById('plaintextInput');
      const ciphertextInput = document.getElementById('ciphertextInput');
      const encryptBtn = document.getElementById('encryptBtn');
      const decryptBtn = document.getElementById('decryptBtn');
      const generateKeyBtn = document.getElementById('generateKeyBtn');
      const copyKeyBtn = document.getElementById('copyKeyBtn');
      const copyCipherBtn = document.getElementById('copyCipherBtn');
      const keyLengthIndicator = document.getElementById('keyLengthIndicator');
      const keyStatus = document.getElementById('keyStatus');
      const operationStatus = document.getElementById('operationStatus');

      // ---------- 工具函数 ----------
      function hexStringToUint8Array(hexString) {
        // 移除空格并确保小写
        hexString = hexString.replace(/\s+/g, '').toLowerCase();
        if (hexString.length % 2 !== 0) {
          throw new Error('十六进制字符串长度必须为偶数');
        }
        const bytes = new Uint8Array(hexString.length / 2);
        for (let i = 0; i < hexString.length; i += 2) {
          const byte = parseInt(hexString.substr(i, 2), 16);
          if (isNaN(byte)) throw new Error('无效的十六进制字符');
          bytes[i / 2] = byte;
        }
        return bytes;
      }

      function uint8ArrayToHexString(uint8Array) {
        return Array.from(uint8Array)
          .map(b => b.toString(16).padStart(2, '0'))
          .join('');
      }

      // 生成随机16字节IV (用于CBC)
      function generateRandomIV() {
        return crypto.getRandomValues(new Uint8Array(16));
      }

      // 将Base64字符串转换为Uint8Array
      function base64ToUint8Array(base64) {
        try {
          const binaryString = atob(base64);
          const bytes = new Uint8Array(binaryString.length);
          for (let i = 0; i < binaryString.length; i++) {
            bytes[i] = binaryString.charCodeAt(i);
          }
          return bytes;
        } catch (e) {
          throw new Error('Base64解码失败:无效的Base64字符串');
        }
      }

      // 将Uint8Array转换为Base64
      function uint8ArrayToBase64(uint8Array) {
        let binaryString = '';
        uint8Array.forEach(byte => {
          binaryString += String.fromCharCode(byte);
        });
        return btoa(binaryString);
      }

      // 验证并获取CryptoKey (AES-256-CBC)
      async function getCryptoKeyFromHex(hexKey) {
        if (!hexKey || hexKey.trim() === '') {
          throw new Error('密钥不能为空');
        }
        const cleanHex = hexKey.replace(/\s+/g, '');
        if (cleanHex.length !== 64) {
          throw new Error(`密钥长度必须为64个十六进制字符 (32字节),当前长度: ${cleanHex.length}`);
        }
        if (!/^[0-9a-fA-F]{64}$/.test(cleanHex)) {
          throw new Error('密钥包含无效的十六进制字符');
        }
        const rawKey = hexStringToUint8Array(cleanHex);
        return await crypto.subtle.importKey(
          'raw',
          rawKey,
          { name: 'AES-CBC' },
          false,
          ['encrypt', 'decrypt']
        );
      }

      // 更新密钥状态显示
      function updateKeyIndicator() {
        const rawValue = keyInput.value.replace(/\s+/g, '');
        const byteLength = rawValue.length / 2;
        keyLengthIndicator.textContent = `⚡ 长度: ${byteLength} / 32 字节 (${rawValue.length} hex字符)`;
        
        if (rawValue.length === 0) {
          keyStatus.textContent = '未设置';
          keyStatus.style.color = '#f87171';
        } else if (rawValue.length === 64 && /^[0-9a-fA-F]{64}$/.test(rawValue)) {
          keyStatus.textContent = '✅ 有效256位密钥';
          keyStatus.style.color = '#4ade80';
        } else {
          keyStatus.textContent = '❌ 格式无效';
          keyStatus.style.color = '#fbbf24';
        }
      }

      // 生成随机256位密钥 (hex)
      function generateRandomHexKey() {
        const randomBytes = new Uint8Array(32);
        crypto.getRandomValues(randomBytes);
        return uint8ArrayToHexString(randomBytes);
      }

      // 设置操作状态
      function setStatus(message, isError = false) {
        operationStatus.textContent = message;
        operationStatus.style.color = isError ? '#fca5a5' : '#e2e8f0';
        if (isError) {
          operationStatus.style.background = '#7f1d1d';
        } else {
          operationStatus.style.background = '#1e293b';
        }
      }

      // ---------- 加密操作 ----------
      async function performEncrypt() {
        try {
          const plaintext = plaintextInput.value;
          if (plaintext === '') {
            throw new Error('明文不能为空');
          }
          const keyHex = keyInput.value.trim();
          const cryptoKey = await getCryptoKeyFromHex(keyHex);
          
          // 生成随机IV
          const iv = generateRandomIV();
          
          // 将明文编码为Uint8Array (UTF-8)
          const encoder = new TextEncoder();
          const plaintextBytes = encoder.encode(plaintext);
          
          // 执行加密
          const encryptedBuffer = await crypto.subtle.encrypt(
            { name: 'AES-CBC', iv: iv },
            cryptoKey,
            plaintextBytes
          );
          
          // 组合 IV + 密文
          const encryptedBytes = new Uint8Array(encryptedBuffer);
          const combined = new Uint8Array(iv.length + encryptedBytes.length);
          combined.set(iv, 0);
          combined.set(encryptedBytes, iv.length);
          
          // 转换为Base64
          const base64Cipher = uint8ArrayToBase64(combined);
          ciphertextInput.value = base64Cipher;
          
          setStatus('✅ 加密成功 (IV已前置)');
        } catch (error) {
          console.error('加密失败:', error);
          setStatus(`加密失败: ${error.message}`, true);
          // 不清空密文框,但提示错误
        }
      }

      // ---------- 解密操作 ----------
      async function performDecrypt() {
        try {
          const cipherBase64 = ciphertextInput.value.trim();
          if (cipherBase64 === '') {
            throw new Error('密文不能为空');
          }
          const keyHex = keyInput.value.trim();
          const cryptoKey = await getCryptoKeyFromHex(keyHex);
          
          // 解码Base64得到 IV + 密文
          const combined = base64ToUint8Array(cipherBase64);
          
          // 检查最小长度:至少需要16字节IV + 16字节块 (AES块大小)
          if (combined.length < 32) {
            throw new Error('密文数据太短,必须包含16字节IV和至少一个加密块');
          }
          
          // 提取IV (前16字节)
          const iv = combined.slice(0, 16);
          // 提取密文 (剩余部分)
          const cipherData = combined.slice(16);
          
          // 执行解密
          const decryptedBuffer = await crypto.subtle.decrypt(
            { name: 'AES-CBC', iv: iv },
            cryptoKey,
            cipherData
          );
          
          // 解码为UTF-8字符串
          const decoder = new TextDecoder();
          const plaintext = decoder.decode(decryptedBuffer);
          plaintextInput.value = plaintext;
          
          setStatus('🔓 解密成功');
        } catch (error) {
          console.error('解密失败:', error);
          setStatus(`解密失败: ${error.message}`, true);
          // 解密失败不修改明文框
        }
      }

      // 复制到剪贴板
      async function copyToClipboard(text, elementDescription = '内容') {
        if (!text || text.trim() === '') {
          setStatus(`⚠️ 没有可复制的${elementDescription}`, true);
          return;
        }
        try {
          await navigator.clipboard.writeText(text);
          setStatus(`📋 已复制${elementDescription}到剪贴板`);
        } catch (err) {
          setStatus(`❌ 复制失败: ${err.message}`, true);
        }
      }

      // ---------- 事件绑定 ----------
      keyInput.addEventListener('input', updateKeyIndicator);
      
      generateKeyBtn.addEventListener('click', () => {
        const newKey = generateRandomHexKey();
        keyInput.value = newKey;
        updateKeyIndicator();
        setStatus('🎲 已生成随机256位密钥');
      });

      copyKeyBtn.addEventListener('click', () => {
        const keyValue = keyInput.value.trim();
        copyToClipboard(keyValue, '密钥');
      });

      encryptBtn.addEventListener('click', performEncrypt);
      
      decryptBtn.addEventListener('click', performDecrypt);

      copyCipherBtn.addEventListener('click', () => {
        const cipherValue = ciphertextInput.value.trim();
        copyToClipboard(cipherValue, '密文');
      });

      // 可选:回车快捷操作(在密文框按Ctrl+Enter尝试解密,明文框Ctrl+Enter加密)
      plaintextInput.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.key === 'Enter') {
          e.preventDefault();
          performEncrypt();
        }
      });
      
      ciphertextInput.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.key === 'Enter') {
          e.preventDefault();
          performDecrypt();
        }
      });

      // 初始化状态显示
      updateKeyIndicator();
      
      // 设置一个默认示例密钥(方便测试,但提示用户可自行生成)
      // 使用一个固定的示例密钥 (32字节 hex) : "a1b2c3d4e5f6071829aabbccddeeff00112233445566778899aabbccddeeff"
      const defaultKey = "a1b2c3d4e5f6071829aabbccddeeff00112233445566778899aabbccddeeff";
      if (keyInput.value === '') {
        keyInput.value = defaultKey;
        updateKeyIndicator();
        setStatus('ℹ️ 已加载示例密钥,建议生成随机密钥');
      }
    })();
  </script>
</body>
</html>

在线使用

a5a3e081.pinme.dev

AES-256-CBC 加解密工具 | 在线对称加密

6 个帖子 - 3 位参与者

阅读完整话题

来源: linux.do查看原文