Clipzy 的核心是 “端到端加密”,意思是加密和解密的“钥匙”只存在于您和接收者的设备上,服务器只负责保管一个谁也打不开的“加密保险箱”,从而确保了内容的绝对隐私。
如何使用:两种方法
根据您的安全需求和使用场景,我们提供两种使用方法。
方法一:最高安全等级 (推荐)
此方法遵循严格的端到端加密模型,您的原始文本和密钥永远不会发送到服务器。
适用场景:在网页应用中分享敏感信息,构建安全的在线便签等。
工作流程:
第一步:创建和加密 (在你的应用中)
生成密钥:在客户端本地创造一把独一无二的钥匙。
加密内容:用这把钥匙将你的原文锁进一个保险箱。
组合分享链接:将储物柜编号 (ID) 和你的钥匙 (密钥) 组合成一个特殊的分享链接。
第二步:分享和解密 (在接收者的应用中)
解析链接:接收者打开链接,应用自动从链接中分离出储物柜编号和钥匙。
开锁:用钥匙在本地打开保险箱,看到原始内容。
下面的 JavaScript 完整示例详细演示了此方法。
方法二:服务器端解密 (方便自动化)
此方法安全性较低,因为它需要您将密钥作为参数发送给服务器。但对于无法执行复杂解密操作的自动化脚本(如 curl 或 Python 脚本)来说,非常方便。
适用场景:在命令行或后端服务中,快速获取之前存储的配置或秘密信息。
工作流程:
准备工作:你首先需要知道一个加密片段的 ID (储物柜编号) 和它的密钥 (钥匙)。这通常是你之前通过方法一创建并自己保存下来的。
获取原文:服务器用你给的钥匙打开保险箱,然后直接把里面的东西(原始纯文本)递给你。
请谨慎使用! 虽然服务器承诺不记录密钥或解密后的内容,但此操作破坏了严格的端到端加密模型。请仅在您信任服务器或内容敏感度不高时使用。
代码示例
示例1 (最高安全): 完整的JavaScript实现
在线API测试工具无法模拟完整的端到端加密流程。下面的示例展示了如何在您的网站或应用中,通过JavaScript实现从生成密钥到最终解密的全部步骤。您可以将此代码保存为一个HTML文件直接在浏览器中运行。
<!DOCTYPE html>
<html>
<head>
<title>Clipzy E2EE Demo</title>
<!-- 引入 lz-string 库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js"></script>
</head>
<body>
<h1>Clipzy 端到端加密示例</h1>
<textarea id="originalText" rows="10" cols="80" placeholder="在此输入您想加密分享的文本..."></textarea>
<br>
<button onclick="createEncryptedPaste()">创建加密链接</button>
<hr>
<h2>生成的分享链接:</h2>
<input id="shareableLink" type="text" size="100" readonly>
<hr>
<h2>从链接解析内容:</h2>
<input id="pasteLink" type="text" size="100" placeholder="将上面生成的链接粘贴到此处进行解密">
<button onclick="decryptPasteFromLink()">解密</button>
<h3>解密后的内容:</h3>
<pre id="decryptedText" style="background-color: #f0f0f0; border: 1px solid #ccc; padding: 10px;"></pre>
<script>
// --- 辅助函数:ArrayBuffer 与 Base64 互相转换 ---
function arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
function base64ToArrayBuffer(base64) {
const binary_string = window.atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
// --- 核心加密函数 ---
async function createEncryptedPaste() {
try {
document.getElementById('shareableLink').value = '正在创建,请稍候...';
const originalText = document.getElementById('originalText').value;
if (!originalText) {
alert('请输入内容!');
return;
}
// 1. 在客户端生成一个 AES-GCM 密钥
const key = await window.crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true, // a boolean value indicating whether the key can be extracted from the CryptoKey object
["encrypt", "decrypt"]
);
// 2. 使用 LZ-String 压缩原始文本
const compressedText = LZString.compressToUTF16(originalText);
// 3. 使用密钥加密压缩后的数据
const iv = window.crypto.getRandomValues(new Uint8Array(12)); // GCM 推荐使用 12 字节的 IV
const encryptedData = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
key,
new TextEncoder().encode(compressedText)
);
// 将 IV 和加密数据合并,并转为 Base64
const combined = new Uint8Array(iv.length + encryptedData.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(encryptedData), iv.length);
const dataToSend = arrayBufferToBase64(combined);
// 4. 调用 POST /api/store 上传加密数据
const response = await fetch('https://paste.sdjz.wiki/api/store', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ compressedData: dataToSend, ttl: 3600 })
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || '上传失败');
// 5. 将 ID 和 Base64 密钥组合成 URL 片段
const exportedKey = await window.crypto.subtle.exportKey("raw", key);
const keyB64 = arrayBufferToBase64(exportedKey);
const fragment = `${result.id}!${keyB64}`;
// 6. 构建完整的分享链接
const shareableLink = `https://paste.sdjz.wiki/#${fragment}`;
document.getElementById('shareableLink').value = shareableLink;
} catch (error) {
console.error('加密失败:', error);
document.getElementById('shareableLink').value = `错误: ${error.message}`;
}
}
// --- 核心解密函数 ---
async function decryptPasteFromLink() {
try {
document.getElementById('decryptedText').innerText = '正在解密,请稍候...';
const pasteLink = document.getElementById('pasteLink').value;
const fragment = new URL(pasteLink).hash.substring(1);
if (!fragment.includes('!')) throw new Error('无效的链接格式');
// 1. 从 URL 片段中解析出 ID 和密钥
const [id, keyB64] = fragment.split('!');
const keyBuffer = base64ToArrayBuffer(keyB64);
const key = await window.crypto.subtle.importKey(
"raw",
keyBuffer,
{ name: "AES-GCM" },
true,
["decrypt"]
);
// 2. 调用 GET /api/get 获取加密数据
const response = await fetch(`https://paste.sdjz.wiki/api/get?id=${id}`);
const result = await response.json();
if (!response.ok) throw new Error(result.error || '获取数据失败');
const combined = base64ToArrayBuffer(result.compressedData);
const iv = combined.slice(0, 12);
const data = combined.slice(12);
// 3. 使用密钥在客户端解密数据
const decryptedData = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
key,
data
);
// 4. 解压解密后的数据
const decompressedText = LZString.decompressFromUTF16(new TextDecoder().decode(decryptedData));
// 5. 成功,显示原始文本
document.getElementById('decryptedText').innerText = decompressedText;
} catch (error) {
console.error('解密失败:', error);
document.getElementById('decryptedText').innerText = `错误: ${error.message}`;
}
}
</script>
</body>
</html>
示例2 (方便自动化): 使用cURL直接获取明文
假设您已经通过某种方式(例如运行了上面的JS示例)创建了一个加密片段,并得到了ID和密钥:
现在,您可以在任何地方的命令行中,使用 curl 直接获取原始文本:
# 将 {id} 和 {key} 替换成你自己的
curl "https://paste.sdjz.wiki/api/raw/tzy4AllxQP?key=a1b2c3d4e5f6g7h8"
服务器会返回解密后的纯文本,非常适合在脚本中使用。