Clipzy 在线剪贴板

分类介绍

由第三方服务 Clipzy™ 提供的端到端加密文本片段服务。详情请参阅其隐私协议

Clipzy 的核心是 “端到端加密”,意思是加密和解密的“钥匙”只存在于您和接收者的设备上,服务器只负责保管一个谁也打不开的“加密保险箱”,从而确保了内容的绝对隐私。

如何使用:两种方法

根据您的安全需求和使用场景,我们提供两种使用方法。

方法一:最高安全等级 (推荐)

此方法遵循严格的端到端加密模型,您的原始文本和密钥永远不会发送到服务器。

适用场景:在网页应用中分享敏感信息,构建安全的在线便签等。

工作流程

  • 第一步:创建和加密 (在你的应用中)

    1. 生成密钥:在客户端本地创造一把独一无二的钥匙。
    2. 加密内容:用这把钥匙将你的原文锁进一个保险箱。
    3. 上传保险箱:调用 POST /api/store 接口,把锁好的保险箱(密文)交给服务器保管,服务器会给你一个储物柜编号 (ID)。
    4. 组合分享链接:将储物柜编号 (ID) 和你的钥匙 (密钥) 组合成一个特殊的分享链接。
  • 第二步:分享和解密 (在接收者的应用中)

    1. 解析链接:接收者打开链接,应用自动从链接中分离出储物柜编号和钥匙。
    2. 取回保险箱:调用 GET /api/get 接口,凭编号取回加密的保险箱。
    3. 开锁:用钥匙在本地打开保险箱,看到原始内容。

下面的 JavaScript 完整示例详细演示了此方法。

方法二:服务器端解密 (方便自动化)

此方法安全性较低,因为它需要您将密钥作为参数发送给服务器。但对于无法执行复杂解密操作的自动化脚本(如 curl 或 Python 脚本)来说,非常方便。

适用场景:在命令行或后端服务中,快速获取之前存储的配置或秘密信息。

工作流程

  1. 准备工作:你首先需要知道一个加密片段的 ID (储物柜编号) 和它的密钥 (钥匙)。这通常是你之前通过方法一创建并自己保存下来的。
  2. 请求解密:调用 GET /api/raw/{id} 接口,把储物柜编号 (ID) 和钥匙 (密钥) 同时交给服务器。
  3. 获取原文:服务器用你给的钥匙打开保险箱,然后直接把里面的东西(原始纯文本)递给你。
注意

请谨慎使用! 虽然服务器承诺不记录密钥或解密后的内容,但此操作破坏了严格的端到端加密模型。请仅在您信任服务器或内容敏感度不高时使用。


代码示例

示例1 (最高安全): 完整的JavaScript实现

在线API测试工具无法模拟完整的端到端加密流程。下面的示例展示了如何在您的网站或应用中,通过JavaScript实现从生成密钥到最终解密的全部步骤。您可以将此代码保存为一个HTML文件直接在浏览器中运行。

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和密钥:

  • ID: tzy4AllxQP
  • 密钥: a1b2c3d4e5f6g7h8 (这里只是一个例子,实际密钥是一长串Base64字符)

现在,您可以在任何地方的命令行中,使用 curl 直接获取原始文本:

BASH
# 将 {id} 和 {key} 替换成你自己的
curl "https://paste.sdjz.wiki/api/raw/tzy4AllxQP?key=a1b2c3d4e5f6g7h8"

服务器会返回解密后的纯文本,非常适合在脚本中使用。

分类介绍 - UAPI免费API平台文档 | UAPI免费API接口平台 | UAPI