分享一个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>
在线使用
AES-256-CBC 加解密工具 | 在线对称加密
网友解答:--【壹】--:
老d在胡扯啊
Screenshot202604281843021080×1607 87.5 KB
--【贰】--:
刚才那个500 deepseek额度的帖子好像被删了,那个解密有问题
--【叁】--:
啊,能解出来吗?我试了试,安装说的流程,解不了啊
--【肆】--:
我当时解出来了,余额通过API查好像是四十六块多,然后现在已经401了
--【伍】--:
一开始的确实解不出来,但是后面的那一个版本可以,有一个SHA 256那个Base64解出来的网址刚好32位作为密钥来解下面的密文。至于他被删贴的原因,可能是因为那个网址好像是一个收费的API站,有暗广嫌疑
<!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>
在线使用
AES-256-CBC 加解密工具 | 在线对称加密
网友解答:--【壹】--:
老d在胡扯啊
Screenshot202604281843021080×1607 87.5 KB
--【贰】--:
刚才那个500 deepseek额度的帖子好像被删了,那个解密有问题
--【叁】--:
啊,能解出来吗?我试了试,安装说的流程,解不了啊
--【肆】--:
我当时解出来了,余额通过API查好像是四十六块多,然后现在已经401了
--【伍】--:
一开始的确实解不出来,但是后面的那一个版本可以,有一个SHA 256那个Base64解出来的网址刚好32位作为密钥来解下面的密文。至于他被删贴的原因,可能是因为那个网址好像是一个收费的API站,有暗广嫌疑

