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

2026-04-29 08:222阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

<!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 加解密工具 | 在线对称加密

网友解答:
--【壹】--:

老d在胡扯啊
Screenshot202604281843021080×1607 87.5 KB


--【贰】--:

刚才那个500 deepseek额度的帖子好像被删了,那个解密有问题


--【叁】--:

啊,能解出来吗?我试了试,安装说的流程,解不了啊


--【肆】--:

我当时解出来了,余额通过API查好像是四十六块多,然后现在已经401了


--【伍】--:

一开始的确实解不出来,但是后面的那一个版本可以,有一个SHA 256那个Base64解出来的网址刚好32位作为密钥来解下面的密文。至于他被删贴的原因,可能是因为那个网址好像是一个收费的API站,有暗广嫌疑