243 lines
11 KiB
HTML
243 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="renderer" content="webkit">
|
||
<script>(function(){var t;try{t=localStorage.getItem("hp-theme")}catch(e){}if(!t){t=(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches)?"dark":"light"}document.documentElement.setAttribute("data-theme",t);})();</script>
|
||
<title>国密加解密</title>
|
||
<link rel="icon" href="../assets/img/logo.svg">
|
||
<link rel="stylesheet" href="../assets/css/common.css">
|
||
</head>
|
||
<body>
|
||
<div class="func-bar"><div class="wrap func-bar-inner">
|
||
<a class="btn btn-sm js-fb-back" href="index.php" title="返回功能区首页">← 返回功能区首页</a>
|
||
<span class="func-bar-title">国密加解密</span>
|
||
<a class="btn btn-sm" href="../index.php">首页</a>
|
||
</div></div>
|
||
|
||
<main class="page-main">
|
||
<div class="wrap page-body">
|
||
<div class="panel-card">
|
||
<p class="tip" style="margin-top:0">🔒 SM2 / SM3 / SM4 运算全部在本机浏览器完成,明文 / 密钥 / 密文不会上传服务器。算法组件优先加载本地 <code>assets/js/sm-crypto/</code> 下的 <code>sm2.js / sm3.js / sm4.js</code>,未找到时自动回退公共 CDN(仅加载代码,不含你的内容)。</p>
|
||
<p class="tip" style="margin-top:0">支持 SM2 / SM3 / SM4 三种国密算法;SM1 为不公开的硬件算法(需国密芯片),无法纯软件实现。</p>
|
||
|
||
<div class="field-row" style="align-items:center;margin-top:12px">
|
||
<label class="fl" style="margin:0" for="gmAlgo">算法</label>
|
||
<select id="gmAlgo">
|
||
<option value="sm4">SM4(对称加解密)</option>
|
||
<option value="sm2">SM2(非对称加解密)</option>
|
||
<option value="sm3">SM3(摘要,不可逆)</option>
|
||
</select>
|
||
<div class="seg" id="gmDir">
|
||
<button type="button" data-dir="enc" class="active">加密</button>
|
||
<button type="button" data-dir="dec">解密</button>
|
||
</div>
|
||
<button type="button" class="btn btn-primary" id="gmRun">执行</button>
|
||
<button type="button" class="btn" id="gmSwap" title="把输出内容放入输入区">输出 → 输入</button>
|
||
<button type="button" class="btn" id="gmClear">清空</button>
|
||
</div>
|
||
|
||
<!-- SM4 参数区 -->
|
||
<div id="gmSm4">
|
||
<div class="field-row" style="margin-top:10px;align-items:center">
|
||
<label class="fl" style="margin:0">密钥(16 字节)</label>
|
||
<input type="text" id="sm4Key" style="flex:1 1 240px;min-width:160px" placeholder="32 位十六进制,或恰好 16 个字符">
|
||
<button type="button" class="btn btn-sm" id="sm4KeyRand">随机密钥</button>
|
||
</div>
|
||
<div class="field-row" style="margin-top:8px;align-items:center">
|
||
<label class="fl" style="margin:0">模式</label>
|
||
<select id="sm4Mode">
|
||
<option value="ecb">ECB</option>
|
||
<option value="cbc">CBC</option>
|
||
</select>
|
||
<input type="text" id="sm4Iv" style="flex:1 1 240px;min-width:160px" placeholder="CBC 模式需 32 位十六进制 IV">
|
||
<button type="button" class="btn btn-sm" id="sm4IvRand">随机 IV</button>
|
||
<label class="fl" style="margin:0">密文格式</label>
|
||
<select id="sm4Out">
|
||
<option value="hex">十六进制</option>
|
||
<option value="b64">Base64</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SM2 参数区 -->
|
||
<div id="gmSm2" hidden>
|
||
<div class="field-row" style="margin-top:10px;align-items:center">
|
||
<label class="fl" style="margin:0">公钥</label>
|
||
<input type="text" id="sm2Pub" style="flex:1 1 320px;min-width:180px" placeholder="加密时填对方公钥(130 位 hex)">
|
||
<label class="fl" style="margin:0">私钥</label>
|
||
<input type="text" id="sm2Priv" style="flex:1 1 320px;min-width:180px" placeholder="解密时填自己的私钥(130 位 hex)">
|
||
<button type="button" class="btn btn-sm" id="sm2Gen">生成密钥对</button>
|
||
</div>
|
||
<p class="tip" style="margin:8px 0 0">SM2 为非对称算法:<strong>加密用“对方公钥”,解密用“自己私钥”</strong>;同一把私钥导出的公钥可直接用于加密。</p>
|
||
</div>
|
||
|
||
<!-- SM3 提示区 -->
|
||
<div id="gmSm3Tip" class="tip" hidden style="margin-top:8px">SM3 输出固定 64 位十六进制摘要(HMAC 场景暂不提供)。</div>
|
||
|
||
<div id="gmMsg"></div>
|
||
</div>
|
||
|
||
<div class="io-area">
|
||
<div class="io-box">
|
||
<div class="io-label">
|
||
<span>输入区(明文 / 待解密内容,可编辑)</span>
|
||
<button type="button" class="btn btn-sm js-copy" data-target="#gmIn">复制</button>
|
||
</div>
|
||
<textarea id="gmIn" placeholder="SM3:任意文本;SM4/SM2 加密:UTF-8 明文;解密:粘贴密文(十六进制,SM4 亦支持 Base64)"></textarea>
|
||
</div>
|
||
<div class="io-box">
|
||
<div class="io-label">
|
||
<span>输出区(可编辑)</span>
|
||
<button type="button" class="btn btn-sm js-copy" data-target="#gmOut">复制</button>
|
||
</div>
|
||
<textarea id="gmOut" placeholder="结果输出到这里,可手动修改后再次使用"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<button type="button" class="theme-fab js-theme-btn" title="切换昼夜模式" aria-label="切换昼夜模式"><span class="js-theme-icon"></span></button>
|
||
<button type="button" class="theme-fab to-top js-to-top" title="返回顶部" aria-label="返回顶部">↑</button>
|
||
<script src="../assets/js/common.js"></script>
|
||
<script src="../assets/js/gmcrypto.js"></script>
|
||
<script>
|
||
(function () {
|
||
'use strict';
|
||
function $(id) { return document.getElementById(id); }
|
||
var algo = $('gmAlgo');
|
||
var dirButtons = Array.prototype.slice.call(document.querySelectorAll('#gmDir button'));
|
||
var gmIn = $('gmIn');
|
||
var gmOut = $('gmOut');
|
||
var gmMsg = $('gmMsg');
|
||
var panSm4 = $('gmSm4');
|
||
var panSm2 = $('gmSm2');
|
||
var panSm3Tip = $('gmSm3Tip');
|
||
var sm4IvRow = $('sm4Iv');
|
||
var ivRandBtn = $('sm4IvRand');
|
||
|
||
function activeDir() {
|
||
var b = dirButtons.filter(function (x) { return x.classList.contains('active'); })[0];
|
||
return b ? b.getAttribute('data-dir') : 'enc';
|
||
}
|
||
function setMsg(text, isErr) {
|
||
gmMsg.innerHTML = '';
|
||
if (!text) return;
|
||
var d = document.createElement('div');
|
||
d.className = isErr ? 'err-msg' : 'ok-msg';
|
||
d.textContent = text;
|
||
gmMsg.appendChild(d);
|
||
}
|
||
function syncUI() {
|
||
var v = algo.value;
|
||
panSm4.hidden = v !== 'sm4';
|
||
panSm2.hidden = v !== 'sm2';
|
||
panSm3Tip.hidden = v !== 'sm3';
|
||
var isHash = v === 'sm3';
|
||
dirButtons.forEach(function (b) {
|
||
b.disabled = isHash;
|
||
if (isHash) b.classList.remove('active');
|
||
});
|
||
if (!isHash && !dirButtons.some(function (x) { return x.classList.contains('active'); })) {
|
||
dirButtons[0].classList.add('active');
|
||
}
|
||
syncIv();
|
||
}
|
||
function syncIv() {
|
||
var cbc = $('sm4Mode').value === 'cbc';
|
||
sm4IvRow.disabled = !cbc;
|
||
ivRandBtn.disabled = !cbc;
|
||
}
|
||
|
||
function busy(on) {
|
||
$('gmRun').disabled = on;
|
||
$('gmRun').textContent = on ? '处理中…' : '执行';
|
||
}
|
||
|
||
function finish(okText) {
|
||
busy(false);
|
||
setMsg(okText, false);
|
||
}
|
||
|
||
function run() {
|
||
var text = gmIn.value;
|
||
if (!text.trim()) { setMsg('请先输入内容', true); gmIn.focus(); return; }
|
||
var v = algo.value;
|
||
var dir = activeDir();
|
||
var G = window.GmCrypto;
|
||
|
||
if (v === 'sm3') {
|
||
busy(true);
|
||
setMsg('正在计算 SM3 摘要…', false);
|
||
G.sm3(text).then(function (hex) {
|
||
gmOut.value = hex;
|
||
finish('SM3 摘要计算完成(64 位十六进制)');
|
||
}).catch(function (e) { busy(false); setMsg('SM3 计算失败:' + (e && e.message ? e.message : e), true); });
|
||
return;
|
||
}
|
||
|
||
if (v === 'sm4') {
|
||
busy(true);
|
||
setMsg('正在执行 SM4 ' + (dir === 'enc' ? '加密' : '解密') + '…', false);
|
||
G.sm4(text, $('sm4Key').value, $('sm4Mode').value, $('sm4Iv').value, dir === 'enc')
|
||
.then(function (r) {
|
||
if (dir === 'enc') {
|
||
gmOut.value = ($('sm4Out').value === 'b64')
|
||
? G.bytesToB64(G.hexToBytes(r.hex))
|
||
: r.hex;
|
||
finish('SM4 加密完成');
|
||
} else {
|
||
gmOut.value = r.text || '';
|
||
finish('SM4 解密完成');
|
||
}
|
||
})
|
||
.catch(function (e) { busy(false); setMsg('SM4 ' + (dir === 'enc' ? '加密' : '解密') + '失败:' + (e && e.message ? e.message : e), true); });
|
||
return;
|
||
}
|
||
|
||
// SM2
|
||
busy(true);
|
||
setMsg('正在执行 SM2 ' + (dir === 'enc' ? '加密' : '解密') + '…', false);
|
||
var p = (dir === 'enc')
|
||
? G.sm2Encrypt(text, $('sm2Pub').value)
|
||
: G.sm2Decrypt(text, $('sm2Priv').value);
|
||
p.then(function (out) {
|
||
gmOut.value = out || '';
|
||
finish('SM2 ' + (dir === 'enc' ? '加密' : '解密') + '完成');
|
||
}).catch(function (e) { busy(false); setMsg('SM2 ' + (dir === 'enc' ? '加密' : '解密') + '失败:' + (e && e.message ? e.message : e), true); });
|
||
}
|
||
|
||
// 事件绑定
|
||
algo.addEventListener('change', syncUI);
|
||
dirButtons.forEach(function (b) {
|
||
b.addEventListener('click', function () {
|
||
if (b.disabled) return;
|
||
dirButtons.forEach(function (x) { x.classList.remove('active'); });
|
||
b.classList.add('active');
|
||
});
|
||
});
|
||
$('sm4Mode').addEventListener('change', syncIv);
|
||
$('sm4KeyRand').addEventListener('click', function () { $('sm4Key').value = window.GmCrypto.randomHex(16); });
|
||
ivRandBtn.addEventListener('click', function () { $('sm4Iv').value = window.GmCrypto.randomHex(16); });
|
||
$('sm2Gen').addEventListener('click', function () {
|
||
setMsg('正在生成 SM2 密钥对…', false);
|
||
window.GmCrypto.sm2Key().then(function (kp) {
|
||
$('sm2Pub').value = kp.publicKey;
|
||
$('sm2Priv').value = kp.privateKey;
|
||
setMsg('密钥对已生成,请妥善保存私钥后使用', false);
|
||
}).catch(function (e) { setMsg('密钥对生成失败:' + (e && e.message ? e.message : e), true); });
|
||
});
|
||
$('gmRun').addEventListener('click', run);
|
||
$('gmSwap').addEventListener('click', function () {
|
||
if (gmOut.value) { gmIn.value = gmOut.value; setMsg('已将输出内容回填到输入区', false); }
|
||
});
|
||
$('gmClear').addEventListener('click', function () {
|
||
gmIn.value = ''; gmOut.value = ''; setMsg('', false); gmIn.focus();
|
||
});
|
||
syncUI();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|