免費隱私工具

密封檔案

用一把全新的一次性金鑰加密檔案,再透過你喜歡的任何管道送出。然後拿到金鑰的人,就在這個頁面上把它打開。沒有任何東西被上傳,沒有可破解的密碼短語,而且在存下任何內容之前,你就先看到裡面是什麼。

完全在這個分頁內執行。這個頁面完全無法與任何伺服器通訊。

每個參數,公開陳述

加密演算法
AES-256-GCM(WebCrypto),每個分塊 128 位元標籤
金鑰
256 位元,來自瀏覽器 CSPRNG,每次密封都重新產生,只用一次,不由任何東西推導而來
分塊方式
先是一份加密的內容清單(檔名、大小、類型),接著是切成 1 MiB 分塊的檔案——每一塊各自認證
Nonce
32 位元隨機密封鹽值 ‖ 64 位元分塊計數器——在同一個檔案內絕不重複,何況金鑰本來就只用一次
認證資料
完整的 16 位元組標頭,加上每一分塊的索引與分塊總數——重新排序、截斷或拼接分塊都會使解密失敗
金鑰字串
base64url,47 個字元:32 位元組金鑰加上 3 位元組的 SHA-256 校驗值,所以打錯字會乾脆失敗,而不是產生一堆亂碼

關於這個工具做了什麼的誠實回答

一切都發生在你的瀏覽器裡。

密封與開啟都是把檔案讀進這個頁面的記憶體,在那裡用你瀏覽器內建的 WebCrypto 引擎執行 AES-256-GCM,再把結果以下載的形式交還給你。沒有任何東西被上傳。沒有帳號,也沒有伺服器端處理。與本站其他頁面不同,這個頁面連那個無 cookie 的訪問計數器都不載入。沒有任何形式的分析。

這不只是一句承諾。這個頁面帶著 Content-Security-Policy 送出,該政策只允許瀏覽器取得本站自己的靜態檔案。它沒有任何辦法把你的檔案、你的金鑰或其他任何東西送到任何伺服器,包括我們的。密封時打開瀏覽器的開發者工具,你會看到零個請求。想要最直白的證明,就安裝工具箱,然後在飛航模式下密封。網路關閉時,沒有東西能外流。

金鑰就是一切——而它從來不在我們手上。

每次密封都會在你的瀏覽器裡產生一把全新的隨機 256 位元金鑰。它只向你顯示一次,之後不留在任何地方:不在我們的系統裡(根本沒有系統參與)、不在你瀏覽器的儲存空間裡,也不在密封檔案裡。沒有可猜的密碼短語,也沒有可暴力破解的東西。握有金鑰與檔案的人就打得開。沒有的人就打不開。

這帶來一個殘酷的後果:金鑰弄丟了,檔案就沒了。沒有重設、沒有客服工單、沒有任何復原途徑。這不是我們故意不幫忙。一份我們救得回來的副本,也就是一份別人偷得走、要得走的副本。

金鑰連結永遠不會抵達伺服器。

分享連結會把金鑰放在網址中 # 之後。那一段,也就是片段(fragment),由瀏覽器自己保留。它永遠不會被納入瀏覽器送往任何伺服器的內容,不論那是我們的還是別人的伺服器。當本頁帶著連結裡的金鑰載入時,它會讀出金鑰、切換到「開啟」頁籤,並立刻把金鑰從網址列移除。這樣它就不會殘留在可見的網址或瀏覽紀錄中。

話雖如此,同時拿到連結與密封檔案的人都能打開它。這正是本頁要你把檔案和金鑰分不同管道傳送的原因。

在存下任何東西之前,你會先看到裡面有什麼。

開啟分成兩個步驟。頁面首先只解密一份小小的內容清單:原始檔名、大小與類型。它會顯示給你看,若檔案類型可以執行程式碼(程式、指令碼、安裝程式、含巨集的文件、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 BE 密文長度,接著是 AES-GCM 密文 + 16 位元組標籤

第 0 塊是內容清單(manifest):UTF-8 JSON {"name":…,"size":…,"type":…}。第 1…N−1 塊是切成 1 MiB 的檔案內容。對 N 塊中的第 i 塊:nonce = salt ‖ uint64 BE i;認證資料 = header ‖ uint64 BE i ‖ uint64 BE 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+ · 免費