免費隱私工具
密封檔案
用一把全新的一次性金鑰加密檔案,再透過你喜歡的任何管道送出。然後拿到金鑰的人,就在這個頁面上把它打開。沒有任何東西被上傳,沒有可破解的密碼短語,而且在存下任何內容之前,你就先看到裡面是什麼。
完全在這個分頁內執行。這個頁面完全無法與任何伺服器通訊。
密封中…
已密封。
這就是你的加密檔案。沒有金鑰,沒有人能讀取它。我們不行,任何人都不行。
金鑰——只在此刻顯示
這把金鑰只存在於此刻的這個頁面。它不會被儲存,也不會被送到任何地方。一旦弄丟,沒有人能打開這個檔案,包括我們。離開前請先存下來。
或以連結分享金鑰
金鑰放在連結中 # 之後。瀏覽器從不會把那一段送給任何伺服器。這個連結會直接開到本頁,「開啟」頁籤已就緒、金鑰也已填好,接著就把金鑰從網址列清掉。
檔案和金鑰請分開傳。
任何同時握有密封檔案與金鑰的人都能打開它。請透過不同管道分別傳送,例如檔案用電子郵件、金鑰連結用簡訊。這樣就不會有任何一個收件匣同時擁有兩者。
金鑰已從你的連結讀出,並已從網址列移除,因此不會留在你的瀏覽紀錄裡。
檢查中…
這個密封檔案裡有什麼
目前還沒有存下任何東西。以下是這個密封檔案自稱包含的內容:
- 名稱
- 大小
- 類型
小心——這種檔案類型可以在你的裝置上執行程式碼
已解密。
檔案已在你的裝置上解密,並以下載的形式提供給你。與它有關的任何內容都不曾離開這個頁面。
來自 Sealby 團隊——iPhone 的加密保險庫
Sealby 把照片、檔案與筆記收在你 iPhone 上的加密保險庫裡。沒有帳號、沒有伺服器,除了你,沒有人能讀取。
每個參數,公開陳述
- 加密演算法
- 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 檔案:
| bytes | field | contents |
|---|---|---|
| 0–7 | magic | ASCII "SEALBY-W" |
| 8 | version | 0x01——其他任何值都會以「由較新版本產生」為由遭到拒絕 |
| 9 | algorithm | 0x01 = AES-256-GCM |
| 10–13 | 封印鹽值 | 4 位元組,CSPRNG,每次密封皆新產生 |
| 14–15 | reserved | 必須為零 |
| 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 個字元。打錯字會校驗失敗,而不是產生一堆亂碼。
完整的位元組級規格(FORMAT-WEB-v1) ·已知答案測試向量(JSON)
參考開啟程式(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 上加密保險庫裡的照片、檔案與筆記。沒有帳號、沒有伺服器,除了你,沒有人能讀取。
iPhone 與 iPad · iOS 17+ · 免費