Python:用AES-GCM算法加密链接内容,并生成可分享的加密链接和解密工具

用AES-GCM算法加密链接内容,并生成可分享的加密链接和解密工具。

```html

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>链接内容加密 · AES-GCM 演示</title>

<style>

* { box-sizing: border-box; margin: 0; }

body {

font-family: 'Inter', system-ui, -apple-system, sans-serif;

background: linear-gradient(145deg, #f5f7fc 0%, #e8ecf5 100%);

min-height: 100vh;

display: flex;

align-items: center;

justify-content: center;

padding: 1.5rem;

margin: 0;

}

.card {

max-width: 820px;

width: 100%;

background: rgba(255, 255, 255, 0.75);

backdrop-filter: blur(10px);

-webkit-backdrop-filter: blur(10px);

border: 1px solid rgba(255, 255, 255, 0.6);

border-radius: 2rem;

padding: 2rem 2.2rem 2.4rem;

box-shadow: 0 30px 50px -20px rgba(0, 20, 40, 0.25), inset 0 1px 0 rgba(255,255,255,0.8);

}

h1 {

font-size: 1.9rem;

font-weight: 600;

color: #0f1a2b;

letter-spacing: -0.02em;

display: flex;

align-items: center;

gap: 10px;

margin-bottom: 0.3rem;

}

.sub {

color: #4d627a;

font-size: 0.95rem;

margin-bottom: 1.8rem;

border-left: 4px solid #3b82f6;

padding-left: 1rem;

font-weight: 400;

}

label {

display: block;

font-size: 0.8rem;

text-transform: uppercase;

letter-spacing: 0.03em;

font-weight: 600;

color: #3f5570;

margin-bottom: 0.4rem;

}

.field {

margin-bottom: 1.5rem;

}

input, textarea {

width: 100%;

font-family: 'JetBrains Mono', 'Fira Code', monospace;

font-size: 0.9rem;

padding: 0.9rem 1.2rem;

border-radius: 1rem;

border: 1.5px solid #d7dfeb;

background: white;

outline: none;

transition: 0.2s;

resize: vertical;

color: #101c2c;

}

input:focus, textarea:focus {

border-color: #3b82f6;

box-shadow: 0 0 0 4px rgba(59,130,246,0.15);

}

textarea {

min-height: 80px;

}

.row {

display: flex;

gap: 1rem;

align-items: center;

flex-wrap: wrap;

margin-bottom: 1.5rem;

}

.btn {

border: none;

background: #1d2b3f;

color: white;

font-weight: 600;

font-size: 0.9rem;

padding: 0.8rem 1.8rem;

border-radius: 2rem;

cursor: pointer;

transition: 0.15s;

display: inline-flex;

align-items: center;

gap: 8px;

box-shadow: 0 6px 14px rgba(0,20,40,0.15);

flex: 0 0 auto;

}

.btn:hover {

background: #0f1a2b;

transform: translateY(-1px);

box-shadow: 0 10px 20px rgba(0,20,40,0.2);

}

.btn.secondary {

background: white;

color: #1d2b3f;

border: 1.5px solid #cbd5e1;

box-shadow: none;

}

.btn.secondary:hover {

background: #f1f5f9;

border-color: #94a3b8;

}

.btn:disabled {

opacity: 0.4;

pointer-events: none;

transform: none;

}

.output {

background: #0f1a2b;

color: #b4d0ff;

padding: 1rem 1.3rem;

border-radius: 1.2rem;

font-family: 'JetBrains Mono', monospace;

font-size: 0.85rem;

word-break: break-all;

white-space: pre-wrap;

border: 1px solid #2b3b52;

margin-bottom: 1.2rem;

min-height: 3.2rem;

}

.badge {

display: inline-block;

background: #dbeafe;

color: #1e40af;

font-size: 0.7rem;

font-weight: 600;

padding: 0.2rem 0.7rem;

border-radius: 40px;

letter-spacing: 0.02em;

margin-left: 8px;

}

.status {

font-size: 0.8rem;

color: #2563eb;

background: #eff6ff;

padding: 0.6rem 1rem;

border-radius: 1rem;

margin-top: 0.5rem;

display: flex;

align-items: center;

gap: 8px;

border: 1px solid #bfdbfe;

font-weight: 500;

}

hr {

border: none;

border-top: 1.5px dashed #cbd5e1;

margin: 2rem 0 1.5rem;

}

.footer-note {

font-size: 0.8rem;

color: #5e728b;

display: flex;

gap: 1rem;

flex-wrap: wrap;

justify-content: space-between;

align-items: center;

}

.key-hint {

font-family: monospace;

background: #eef2f7;

padding: 0.2rem 0.6rem;

border-radius: 20px;

font-size: 0.75rem;

color: #1e293b;

}

.icon {

font-size: 1.3rem;

}

</style>

</head>

<body>

<div class="card">

<h1>

<span class="icon">🔗</span>

AES‑GCM 链接加密

<span class="badge">URL‑safe</span>

</h1>

<div class="sub">加密原始链接 → 得到安全 Token,可拼接到任意分享链接中</div>

<!-- 原始链接输入 -->

<div class="field">

<label for="plainUrl">📎 原始链接 (明文)</label>

<input id="plainUrl" type="text" value="https://example.com/pay?order=123\&amount=999\&user=alice" spellcheck="false" />

</div>

<!-- 加密 / 解密 按钮 -->

<div class="row">

<button class="btn" id="encryptBtn"><span>🔒</span> 加密链接</button>

<button class="btn secondary" id="decryptBtn"><span>🔓</span> 解密</button>

<button class="btn secondary" id="refreshKeyBtn" title="生成新的 AES-256 密钥">🔑 新密钥</button>

</div>

<!-- 加密结果(Token) -->

<div class="field">

<label>🔐 加密 Token (可直接拼到 URL 参数)</label>

<div class="output" id="tokenOutput">等待加密...</div>

</div>

<!-- 完整分享链接 -->

<div class="field">

<label>🌐 完整分享链接</label>

<div class="output" id="shareLinkOutput" style="color:#c9e0ff;">https://your.site/r?d=...</div>

</div>

<!-- 解密输入区 -->

<hr />

<div class="field">

<label>📥 粘贴加密 Token 解密</label>

<textarea id="tokenInput" placeholder="把加密后的 Token 粘贴到这里..." spellcheck="false"></textarea>

</div>

<button class="btn secondary" id="decryptFromInputBtn"><span>⚡</span> 解密 Token</button>

<!-- 解密结果展示 -->

<div class="field" style="margin-top:1.2rem;">

<label>🔎 解密结果</label>

<div class="output" id="decryptOutput" style="color:#bbf7d0; min-height:2.8rem;">---</div>

</div>

<!-- 状态提示 -->

<div class="status" id="statusMsg">

<span>✨</span> 就绪 · 使用 Web Crypto API (AES-GCM 256)

</div>

<!-- 底部密钥信息 -->

<div class="footer-note" style="margin-top:1.8rem;">

<span class="key-hint">密钥指纹: <span id="keyFingerprint">---</span></span>

<span style="display:flex; gap:10px;">

<span>⏳ 有效期 1 小时</span>

<span>🛡️ 认证加密</span>

</span>

</div>

</div>

<script>

(function() {

// ---------- 工具: ArrayBuffer / Base64url ----------

function bufToBase64Url(buf) {

const bytes = new Uint8Array(buf);

let binary = '';

for (let i = 0; i < bytes.length; i++) {

binary += String.fromCharCode(bytesi);

}

return btoa(binary)

.replace(/\+/g, '-')

.replace(/\//g, '_')

.replace(/=+$/, '');

}

function base64UrlToBuf(base64url) {

let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');

while (base64.length % 4) base64 += '=';

const binary = atob(base64);

const bytes = new Uint8Array(binary.length);

for (let i = 0; i < binary.length; i++) {

bytesi = binary.charCodeAt(i);

}

return bytes.buffer;

}

// ---------- 密钥管理 ----------

let cryptoKey = null; // CryptoKey 对象

let keyFingerprint = '';

async function generateKey() {

// 生成 AES-GCM 256 位密钥(可导出以便保存到环境变量,这里仅用于演示)

const rawKey = crypto.getRandomValues(new Uint8Array(32)); // 256 bit

cryptoKey = await crypto.subtle.importKey(

'raw',

rawKey,

{ name: 'AES-GCM', length: 256 },

false, // 不可导出(安全)

'encrypt', 'decrypt'

);

// 计算密钥指纹(使用 SHA-256 的前 8 字节)

const hash = await crypto.subtle.digest('SHA-256', rawKey);

const hashBytes = new Uint8Array(hash);

keyFingerprint = Array.from(hashBytes.slice(0, 4))

.map(b => b.toString(16).padStart(2, '0'))

.join(':');

document.getElementById('keyFingerprint').textContent = keyFingerprint;

}

// ---------- 加密 ----------

async function encryptLink(url, ttlSeconds = 3600) {

if (!cryptoKey) throw new Error('密钥未初始化');

if (!url || url.trim() === '') throw new Error('链接不能为空');

const payload = {

u: url,

exp: Math.floor(Date.now() / 1000) + ttlSeconds,

};

const encoder = new TextEncoder();

const plaintext = encoder.encode(JSON.stringify(payload));

// 12 字节随机 nonce (IV)

const iv = crypto.getRandomValues(new Uint8Array(12));

// AES-GCM 加密 (自动附加 16 字节认证标签)

const ciphertextWithTag = await crypto.subtle.encrypt(

{ name: 'AES-GCM', iv, tagLength: 128 },

cryptoKey,

plaintext

);

// 组合: nonce (12) + ciphertext+tag

const combined = new Uint8Array(iv.length + ciphertextWithTag.byteLength);

combined.set(iv, 0);

combined.set(new Uint8Array(ciphertextWithTag), iv.length);

return bufToBase64Url(combined.buffer);

}

// ---------- 解密 ----------

async function decryptLink(tokenBase64Url) {

if (!cryptoKey) throw new Error('密钥未初始化');

if (!tokenBase64Url || tokenBase64Url.trim() === '') throw new Error('Token 不能为空');

let combined;

try {

combined = new Uint8Array(base64UrlToBuf(tokenBase64Url.trim()));

} catch (e) {

throw new Error('Token 不是合法的 Base64URL 格式');

}

if (combined.length < 12 + 16) throw new Error('Token 太短,无法解密');

const iv = combined.slice(0, 12);

const ciphertextWithTag = combined.slice(12);

let decrypted;

try {

decrypted = await crypto.subtle.decrypt(

{ name: 'AES-GCM', iv, tagLength: 128 },

cryptoKey,

ciphertextWithTag

);

} catch (err) {

// 认证失败 / 密钥不匹配 / 密文被篡改

throw new Error('解密失败:密钥不匹配或数据被篡改');

}

const decoder = new TextDecoder();

let payload;

try {

payload = JSON.parse(decoder.decode(decrypted));

} catch (e) {

throw new Error('解密内容不是有效的 JSON');

}

// 检查过期

if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {

throw new Error('链接已过期(超过有效期)');

}

return payload.u;

}

// ---------- DOM 元素 ----------

const plainUrlInput = document.getElementById('plainUrl');

const tokenOutput = document.getElementById('tokenOutput');

const shareLinkOutput = document.getElementById('shareLinkOutput');

const tokenInput = document.getElementById('tokenInput');

const decryptOutput = document.getElementById('decryptOutput');

const statusMsg = document.getElementById('statusMsg');

// 更新状态栏

function setStatus(text, isError = false) {

statusMsg.innerHTML = `<span>{isError ? '⚠️' : '✨'}\ {text}`;

statusMsg.style.background = isError ? '#fef2f2' : '#eff6ff';

statusMsg.style.color = isError ? '#b91c1c' : '#2563eb';

statusMsg.style.borderColor = isError ? '#fecaca' : '#bfdbfe';

}

// 刷新展示加密 token 和分享链接

async function doEncrypt() {

try {

const url = plainUrlInput.value.trim();

if (!url) throw new Error('请输入原始链接');

const token = await encryptLink(url, 3600); // 1 小时有效期

tokenOutput.textContent = token;

shareLinkOutput.textContent = `https://your.site/r?d=${token}\`;

// 自动填充到解密输入框,方便测试

tokenInput.value = token;

setStatus('加密成功 · Token 已生成(有效期 1 小时)');

} catch (err) {

setStatus(err.message, true);

}

}

// 从 tokenInput 解密

async function doDecryptFromInput() {

try {

const token = tokenInput.value.trim();

if (!token) throw new Error('请粘贴加密 Token');

const plain = await decryptLink(token);

decryptOutput.textContent = plain;

setStatus('解密成功 · 链接有效');

} catch (err) {

decryptOutput.textContent = '---';

setStatus(err.message, true);

}

}

// ---------- 事件绑定 ----------

document.getElementById('encryptBtn').addEventListener('click', doEncrypt);

document.getElementById('decryptBtn').addEventListener('click', () => {

// 使用当前 tokenOutput 中的 token 解密

const token = tokenOutput.textContent;

if (!token || token === '等待加密...') {

setStatus('请先点击「加密链接」生成 Token', true);

return;

}

tokenInput.value = token;

doDecryptFromInput();

});

document.getElementById('decryptFromInputBtn').addEventListener('click', doDecryptFromInput);

document.getElementById('refreshKeyBtn').addEventListener('click', async () => {

await generateKey();

setStatus('已生成新的 AES-256 密钥,之前的 Token 将无法解密');

// 清空输出

tokenOutput.textContent = '等待加密...';

shareLinkOutput.textContent = 'https://your.site/r?d=...';

tokenInput.value = '';

decryptOutput.textContent = '---';

});

// ---------- 初始化 ----------

(async function init() {

await generateKey();

setStatus('就绪 · 密钥已生成,可以加密链接了');

})();

// 方便控制台调试

window.__linkCrypto = { encryptLink, decryptLink, generateKey };

})();

</script>

</body>

</html>

```

加密与解密操作流程

· 加密链接:在"原始链接"输入框里填写要分享的网址,点击"加密链接"按钮,下方会立刻显示加密后的Token和完整的分享链接,方便复制使用。

· 解密链接:把加密Token粘贴到"粘贴加密Token解密"文本框,点击"解密Token"按钮,就能看到原始链接;也可以直接点击"解密"按钮,用当前生成的Token进行快速解密。

· 密钥管理:点击"新密钥"可以刷新AES-256密钥,之前的Token会立刻失效,保证只有掌握当前密钥的人才能解密。页面底部会显示密钥指纹,方便核对。

此仅为教学示例参考用。

相关推荐
Evan_Lai2 小时前
(一)独热编码、标签编码、目标编码、序数编码详解
python·机器学习
用户7624117567052 小时前
第二章 Python语言基础
python
129Lab2 小时前
BMS 核心算法:SOC 估算从安时积分到 LSTM-UKF 串级融合的 30 年演进(附 Python 实现)
python·算法·lstm·python3.11·bms·soc估算
129Lab3 小时前
电池热管理仿真的AI加速:用Python+PINN物理信息神经网络替代传统CFD的可行性探索
pytorch·python·cfd·pinn·物理信息神经网络·仿真加速·电池热管理
用户298698530143 小时前
Python 中 HTML 转 PDF 的实现方法
python·html·api
答案是你3 小时前
YOLOE 开放词汇检测 + ONNX / TensorRT 推理,开源了!
python·深度学习·yolo·目标检测·计算机视觉·视觉检测
2601_962885723 小时前
AlphaFeed 支持哪些 K 线周期?period参数怎么传?
前端·数据库·python
老歌老听老掉牙3 小时前
基于 Python 的顺次文本拼接:以 MCD 数控文件为例
python·数控·mcd
朝朝辞暮i3 小时前
VLA 系统学习第 7 课:loss.backward() 到底做了什么?——从计算图到反向传播
人工智能·python·深度学习·神经网络·vla
外收内放3 小时前
Python基础语法练习题(53-54)
python·学习