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会立刻失效,保证只有掌握当前密钥的人才能解密。页面底部会显示密钥指纹,方便核对。

此仅为教学示例参考用。

相关推荐
小静AI工程实验室1 小时前
Claude Code 实用教程:Windows、macOS、Linux 从安装到项目调试与验收
人工智能·python·开发工具·claude code
OKkankan1 小时前
Python常用容器与导入语法详解(二)
数据结构·python
维基框架1 小时前
坊间传闻 OpenAI 将要发布GPT-6 Sol 模型
人工智能·pytorch·python
读研的武1 小时前
Python异步编程
python
宸津-代码粉碎机2 小时前
Java面试核心:JVM三大GC垃圾回收算法原理与生产场景落地分析
java·大数据·人工智能·python·spring
小静AI工程实验室2 小时前
Codex 实用教程:Windows、macOS、Linux 安装配置到首个可验收项目
人工智能·python·开发工具·codex
黑马水牛2 小时前
Carla仿真系列:17_利用Carla仿真环境跑通3DGS——从采集360张图到高斯泼溅建模
python·ros·colmap·3dgs·新视角合成·carla仿真·三维建图
赵大仁4 小时前
极空间 NAS 没有命令行,我逆向了它的桌面客户端
python·ai编程·nas·mcp·极空间
空白4 小时前
Power BI +VBA 【数据分析】
python