免费隐私工具

封存一个文件

用一把全新的一次性密钥加密文件,然后通过你喜欢的任何渠道发出去。拿到密钥的人就在这里、就在本页上把它打开。绝无上传,没有可破解的密码短语,而且在任何东西被保存之前,你就能看清里面是什么。

完全在此标签页中运行。本页根本无法与任何服务器通信。

每个参数,公开说明

加密算法
AES-256-GCM(WebCrypto),每个分块 128 位标签
密钥
256 位,取自浏览器 CSPRNG,每次封存新生成,只用一次,不由任何东西派生
分块方式
先是一份加密的清单(文件名、大小、类型),然后是切成 1 MiB 分块的文件——每一块单独认证
随机数
32 位随机封存盐值 ‖ 64 位分块计数器——在同一个文件内绝不重复,何况密钥本来就只用一次
认证数据
完整的 16 字节头部,加上每一块的序号和分块总数——重排、截断或拼接分块都会使解密失败
密钥字符串
base64url,47 个字符:32 字节密钥加上 3 字节 SHA-256 校验和,因此笔误会干净地失败,而不是产生一堆乱码

关于它到底做了什么的诚实回答

一切都在你的浏览器里发生。

封存和打开都会把文件读进本页的内存,在那里用你浏览器内建的 WebCrypto 引擎运行 AES-256-GCM,再把结果以下载的形式交还给你。绝无上传。没有账户,也没有服务器端处理。与本站其余部分不同,本页连那个无 Cookie 的访问计数器都不加载。不含任何形式的分析。

这不只是一句承诺。本页附带一份 Content-Security-Policy 送达,它只允许浏览器获取本站自己的静态文件。它没有任何办法把你的文件、你的密钥或别的什么发送给任何服务器,包括我们的。封存时打开浏览器的开发者工具,你会看到零个请求。要最直白的证明,就装上工具箱,在飞行模式下封存。断着网,什么都无法离开。

密钥就是一切——而我们从未拥有它。

每次封存都会在你的浏览器里生成一把全新的随机 256 位密钥。它只向你显示一次,并且不保存在任何地方:不在我们的系统里(根本没有系统参与),不在你浏览器的存储里,也不在封存文件里。没有可猜的密码短语,也没有可穷举的东西。谁同时握有密钥和文件,谁就能打开它。没有的人,打不开。

这带来一个硬邦邦的后果:密钥丢了,文件就没了。没有重置,没有工单,没有找回。这不是我们在故意为难。一份我们能恢复的副本,就是一份别人可以窃取或索要的副本。

密钥链接永远到不了服务器。

分享链接把密钥放在 URL 中 # 之后。这一段,也就是 URL 的片段部分,留在浏览器里。它绝不包含在浏览器发往任何服务器的内容里,无论是我们的还是别人的。当本页带着链接里的密钥加载时,它会读取密钥、切换到“打开”标签,并立即把密钥从地址栏移除。因此它不会滞留在可见的 URL 或浏览记录中。

不过,同时拿到链接和封存文件的人,仍然能打开它。这正是本页提醒你把文件和密钥走不同渠道发送的原因。

在任何东西被保存之前,你先看清里面是什么。

打开分两步进行。第一步,本页只解密一份很小的清单:原始文件名、大小和类型。它把这些展示给你,对可能运行代码的文件类型(程序、脚本、安装包、带宏的文档、HTML)以及像 invoice.pdf.exe 这样用双扩展名伪装的名称发出醒目的警告。在你点击解密之前,不会写入任何东西。本页从不自行打开或呈现解密后的内容。它只把文件交给你下载。

请分清加密能证明什么、不能证明什么。封存文件在传输途中是保密的。封存本身丝毫不能说明它出自谁手,也不能说明它的内容是否安全。对待一个意料之外的封存文件,请像对待一封意料之外的附件一样保持怀疑。

信任边界究竟画在哪里。

你运行的是本网站送到你手上的代码。如果 sealby.app 或者你通往它的连接被攻陷,一个恶意页面就能读到你放进去的东西。每一个基于网页的加密工具都是如此,我们宁愿说出来,也不愿假装不是这样。我们为此做了这些:本页是一个静态文件,我们让它不含任何第三方代码。安装后的工具箱让你可以继续使用已经加载过的那一版。而且格式在下方有文档,独立的工具可以照着它来实现。

你自己的设备同样在这条边界之内。拥有页面访问权的浏览器扩展,或机器上的恶意软件,能读到任何页面能读到的一切。封存文件隐藏的是它的内容,并非一切。任何拿到它的人都能看出它的大小(大致等于原件的大小),以及它是一个由 Sealby 封存的文件。

用大白话说局限

  • 密钥丢了 = 文件没了。没有例外,这是有意的设计。
  • 我们看不到你的文件和密钥,因此也无法替你扫描其中的恶意软件。文件类型警告只基于文件名。它不是安全保证。
  • 任何同时拥有封存文件和密钥的人都能打开它。本工具既不知道也不核实他们是谁。
  • 封存文件的大小会大致透露原文件的大小。
  • 每次封存一个文件,最大 100 MB。多个文件请先打包成 zip。

格式,逐字节

技术读者独立验证本页输出所需的一切,或者用三十行 Python 打开一个 .sealed 文件:

bytesfieldcontents
0–7magicASCII "SEALBY-W"
8version0x01——其他任何值都会以“由更新版本创建”为由被拒绝
9algorithm0x01 = AES-256-GCM
10–13密封盐值4 字节,CSPRNG,每次封存新生成
14–15reserved必须为零
16–end分块帧每一帧:uint32 大端序的密文长度,然后是 AES-GCM 密文 + 16 字节标签

第 0 块是清单:UTF-8 JSON {"name":…,"size":…,"type":…}。第 1…N−1 块是切成 1 MiB 的文件。对 N 块中的第 i 块:nonce = salt ‖ uint64 大端序 i;认证数据 = header ‖ uint64 大端序 i ‖ uint64 大端序 N。因此篡改、截断、重排、挪位以及跨文件拼接都会通不过认证。每一项都有已提交的负面测试为证。

密钥字符串:32 字节密钥后接其 SHA-256 的前 3 字节,整体做 base64url 编码(不带填充),共 47 个字符。笔误会在校验和上失败,而不是产生一堆乱码。

参考打开器(Python,cryptography 库)
import base64, hashlib, json, struct, sys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

raw = open(sys.argv[1], "rb").read()
assert raw[:8] == b"SEALBY-W" and raw[8] == 1 and raw[9] == 1
assert raw[14:16] == b"\x00\x00"
header, salt = raw[:16], raw[10:14]
frames, off = [], 16
while off < len(raw):
    (n,) = struct.unpack(">I", raw[off:off + 4]); off += 4
    frames.append(raw[off:off + n]); off += n
assert off == len(raw)

kb = base64.urlsafe_b64decode(sys.argv[2] + "=")   # 47-char key string
key, check = kb[:32], kb[32:35]
assert hashlib.sha256(key).digest()[:3] == check    # typo check

N = len(frames)
def chunk(i):
    nonce = salt + struct.pack(">Q", i)
    aad = header + struct.pack(">QQ", i, N)
    return AESGCM(key).decrypt(nonce, frames[i], aad)

manifest = json.loads(chunk(0))
data = b"".join(chunk(i) for i in range(1, N))
assert len(data) == manifest["size"]
open(manifest["name"], "wb").write(data)
print("decrypted ->", manifest["name"])

这是为路上的文件准备的。保险库是为留下来的文件准备的。

封存保护的是一个正在去往某处的文件。Sealby 保护的是留下来的一切:iPhone 上加密保险库里的照片、文件和笔记。无账户,无服务器,除你之外,无人能读。

在 App Store 下载

iPhone 和 iPad · iOS 17+ · 免费