閱後即焚連結讀完一次後,伺服器還剩什麼

維運把資料庫口令丟進 LINE、同事把復原碼貼進工單,事後最常問的不是「演算法叫什麼」,而是:對方開啟之後,這串內容還躺在哪一台機器上。閱後即焚回答的是後半句:伺服器上本來就沒有明文;密文在被取回並達到設定次數後刪除。連結字串可能還在聊天紀錄裡,那是另一層風險。

先記一句能核對的結論:閱讀頁第一次開啟,應當只載入殼頁面,還不向伺服器取密文。點「開啟並查看」之後,Network 裡才會出現按編號取密文的請求;成功且次數用盡後,再請求同一編號應看到 410。網址列可以有 # 後面的金鑰,請求行裡不該出現同一段。

「閱後即焚」常被理解成:連結著火了,內容就沒了。這個比喻容易說反。LINE 裡的藍色文字不會自己消失;瀏覽器歷史、郵件原文、工單留言也還在。燒掉的是伺服器上那一段暫存的密文。人手上的完整 URL 仍是一串字元,只是再開啟時,編號對應的物件已經不在了。

所以要先拆開兩件東西。一件是定位憑證:問號後面的編號,用來告訴伺服器取哪一條紀錄。一件是解密憑證:井號後面的金鑰,只留給目前分頁。伺服器依設計能看見前者,看不見後者。讀完一次之後,前者可能還指向一個已經空掉的位置;後者如果還在聊天紀錄裡,也解不開任何東西——因為密文已經沒了。

這和雲端硬碟分享連結不同。分享連結有效,檔案通常還在;你刪的是權限,不是物件本身。閱後即焚把「讀」和「刪」綁在同一次取回上。建立時可以設定閱讀次數(1–10,預設 1)和過期時間(1 小時、24 小時、7 天,或只在閱讀後焚毀)。預設路徑是:第一次成功取回,密文從伺服器刪除。未讀而過期,同樣刪除。無論哪一種,都沒有明文備份可找回。

伺服器上從來就沒有明文

「讀完才刪明文」是另一種常見誤讀。正確順序是:明文只在建立方的分頁出現;瀏覽器用 Web Crypto 抽出 256 位元隨機金鑰,按 AES-256-GCM 加密;出站欄位是密文、過期時間和最大閱讀次數。伺服器回傳一個不可猜測的編號。頁面再把金鑰接到 s.html?id={編號}#{金鑰} 的片段上。全程沒有「先把口令存進資料庫再加密」這一步。

演算法參數可以當場對照,不是口號。NIST SP 800-38D 建議 GCM 使用 96 位元(12 位元組)IV,以兼顧互通和實作簡單;驗證標籤常用 128 位元(16 位元組)。Web Crypto 的 AesGcmParams 與此對齊。MakePwd 建立時的密文形態是 Base64(12 位元組 IV + 密文 + 16 位元組標籤),金鑰 32 位元組,單條上限 32 KB。這些數字寫在建立頁上,也可以在開發者工具裡核對自己剛產生的連結:井號後是金鑰的 Base64URL,不是你剛輸入的那句口令。

GCM 還提供完整性。密文或標籤被改過,本機解密會失敗,不會給出一段「看起來差不多」的明文。這擋的是傳輸中的竄改,不是「對方開啟後截圖」。伺服器自始至終拿不到金鑰,也就不能代為解密、不能做內容審核、不能在你讀完之後再補一份明文副本。它能做的只有:按編號交出密文,然後按次數或 TTL 刪掉它。

階段 瀏覽器裡有什麼 伺服器上有什麼
建立完成 明文(你剛輸入的)、完整連結 密文、編號、TTL、剩餘次數
閱讀頁剛開啟 網址列裡的編號和 # 金鑰 仍是密文;尚未因這次開啟而刪除
點確認並取回成功 本機解密出的明文 次數用盡則密文刪除;未用盡則次數減一
再次開啟同一編號 金鑰可能還在網址列 已焚毀或已過期,回傳 410 一類狀態

編號走問號,金鑰走井號

一次性連結必須同時完成兩件事:告訴伺服器取哪一條,以及告訴接收方瀏覽器用哪一把鑰匙。這兩件事不該擠進同一條 HTTP 欄位。問號後面的查詢會寫進請求行,反向代理和存取紀錄依設計都能看見;井號後面的片段依規範留給用戶端。

RFC 9110 第 7.1 節寫明:目標 URI 不含 fragment,因為片段識別留給用戶端處理。瀏覽器向伺服器要的是 s.html?id=…,不是整串網址列。金鑰若改成 ?key=,所謂「只存密文」在紀錄這一層就不成立:維運開啟當天的 access log,就能看到解密所需的第二半。

這一層在上一篇已經展開過,這裡只收成邊界:拆開是為了讓託管方看不見金鑰,不是為了讓完整連結可以隨便轉傳。把含 # 的整串貼進 LINE,接收方和聊天服務商都看得到金鑰。fragment 擋的是 HTTP,擋不住剪貼簿。細節和當場核對步驟見URL 的井號後面為什麼適合放金鑰,以及什麼時候會失效

取回密文的那一次,才是焚毀點

「開啟閱讀頁」和「消耗一次閱讀」不是同一事件。閱讀頁的 HTML 可以先畫出來:核對網址裡有沒有 ?id=,有沒有 # 金鑰,然後停在確認按鈕上。這一步只消耗靜態資源。真正讓伺服器刪密文的,是隨後那次按編號取密文的請求。點了按鈕、取回成功、次數用盡,紀錄才從「仍可取」變成「已焚毀」。

少金鑰時不該去取。網址裡只有編號、井號被即時通訊截斷,閱讀頁應提示缺少片段,而不是先向伺服器要密文。否則你會浪費僅有的一次:伺服器交出密文並刪除,瀏覽器卻解不開,傳送方還以為對方已經讀到了。可核對的行為是:缺 # 時,Network 裡不應出現取密文的介面;有完整連結並點確認後,才應出現。

次數不是永遠等於 1。建立時可以設 1 到 10。設成 3,意味著前兩次成功取回後,伺服器上的密文還在,只是剩餘次數減一;第三次成功後才刪除。預設 1 是為了「傳一次口令」這個任務,不是因為協定只能讀一次。TTL 是另一條獨立的刪除條件:24 小時到期未讀,密文同樣刪掉,狀態應能和「已被人讀過」區分開。MakePwd 的閱讀頁在 410 回應裡用 expired 表示過期,其餘焚毀態按已燒毀處理。

不要用真實口令、正式環境 API Key 或未遮罩連線字串做實驗。準備一句可丟棄的金絲雀,例如 canary-burn-2026-do-not-reuse。核對的是狀態碼和請求行,不是把秘密再傳播一遍。

410 和 404:燒掉、過期、本來就沒有

密文刪掉之後,伺服器必須回答下一次請求。RFC 9110 第 15.5.11 節410 Gone 寫成:目標資源在來源伺服器上已不再可用,且這一狀態很可能永久。若來源伺服器無法判斷是否永久,應當改用 404。MDN 對 410 的說明補充:用戶端不該反覆重試;站點應撤掉仍指向該資源的連結。

用在閱後即焚上,410 比 404 更貼切:這個編號曾經對應過一條密文,現在被故意刪掉了,不會再回來。過期和讀盡都可以走 410,再用回應本文區分 expired 與普通焚毀,避免接收方以為「連結寫錯了」。404 仍然可能出現,例如編號格式無效、紀錄已被清理到連「曾經存在」都不再聲明。對傳送方來說,這兩種都意味著:不要指望從伺服器把內容撈回來。

410 還有一層容易忽略的語意:它告訴爬蟲「別再來了」。閱讀頁本身是臨時密文場景,應對搜尋引擎 noindex,也不該進 sitemap。你不會希望一次性口令頁被收錄。建立頁可以收錄,因為它解釋怎麼產生連結;閱讀落地頁只服務持有完整 URL 的人。

LINE 預覽為什麼會先燒掉一次

一次性連結最常見的事故,不是密碼學家破了 AES,而是預覽機器人比同事手快。你把完整 URL 貼進 LINE,軟體為了畫出卡片,會先向這個位址發一次請求。Slack 的連結展開文件寫得很直:預設情況下,訊息裡出現連結時,Slack 會抓取它並提供預覽。Teams、Discord、部分郵件安全掃描也有同類行為。LINE 同樣會為貼上的網址產生預覽卡。它們要的是標題和摘要,不是你的口令;但若「第一次 GET 就取密文並刪除」,這次抓取就會把僅有的一次用掉。同事再點進去,看到的是已被焚毀。

金鑰在 # 後面,對這類伺服器端抓取是一層保護:機器人請求到的是 s.html?id=…,井號後的金鑰依規範不隨 HTTP 發出。預覽通常解不開明文,卡片上也沒有口令。它燒掉的是取回次數,不是金鑰本身。如果你的實作在載入閱讀頁時就自動取密文,預覽等於替接收方點了一次「開啟」。接收方拿到的是 410,傳送方以為對方已經讀過。

可執行的對策不是禁止貼連結,而是把「畫頁面」和「取密文」拆開。閱讀頁先渲染確認介面,說明這次點擊會消耗次數;等人動手,再發取回請求。只抓 HTML、不執行按鈕點擊的預覽,會停在確認步。這擋不住執行完整指令碼並模擬點擊的掃描器,也擋不住人自己點錯。它把最常見的「卡片先開啟一次」從預設失敗改成預設存活。

另一條做法是:在會自動展開的群組裡,先發「口令走閱後即焚,連結另發」,或把完整連結放到不預覽的私訊。完整 URL 仍然是持有即用的憑證,預覽問題只是額外消耗次數,不改變「誰拿到整串誰就能解密」這一層。

當場核對:先停在確認頁,再搜 Network

口號寫「閱後即焚、零知識」無法自證。能當場看到的是四件事:建立時出站有沒有明文;閱讀頁第一次載入有沒有取密文;點確認後請求行有沒有金鑰;次數用盡後再開啟是不是 410。

先準備金絲雀。開啟閱後即焚建立頁,輸入一句可丟棄的測試句,TTL 選 1 小時,閱讀次數保持 1。產生後不要發給任何人。看結果區的拆分:查詢一側只有編號,片段一側才是金鑰。然後開啟開發者工具 Network,勾選 Preserve log,用測試句全文搜尋。建立請求的 body 應是密文,不應出現那句原文;分析回報同樣不該出現原文。

  1. 用同一條完整連結開啟閱讀頁,先不要點確認。Network 裡應只有文件和靜態資源,不應出現按編號取密文的介面。
  2. 對照網址列和文件請求的請求行。網址列保留 # 及之後;請求行只應看到 path 和 ?id=
  3. 點確認。此時才應出現取密文的請求;成功後頁面顯示明文。再用同一連結開啟第二次,應進入已焚毀或 410,而不是再給出同一句金絲雀。

三次都符合,只能支持很窄的結論:在你使用的這個瀏覽器、這一次操作裡,明文沒有作為已觀察到的業務欄位出站,金鑰沒有進請求行,密文在一次成功取回後不再可取。它不證明擴充功能沒有讀輸入框,也不證明伺服器磁碟被攻破後歷史密文絕對不存在。換瀏覽器之後,值得再跑一遍金絲雀。若還要核對「任意工具頁的明文有沒有離開本機」,步驟見線上加密安全嗎?怎麼當場核對明文沒有離開本機

它攔不住複製、截圖和完整連結外發

閱後即焚減少的是兩類風險:伺服器長期保存明文,以及同一條密文被反覆開啟。它不減少第三類:接收方已經看見明文之後做什麼。對方可以複製、截圖、轉傳、讀給旁邊的人聽。連結本身也可以被完整轉傳——那時金鑰和編號一起走,下一個人同樣能在閱讀頁解密,只要次數還沒用盡。

這也是為什麼「對方必須登入才能開啟」解決不了交接問題。存取控制就是完整 URL。給閱讀頁加帳號,並不能讓伺服器看不見金鑰——金鑰本來就不該發給伺服器;它只會讓接收方多一道門,並把「誰持有連結」改成「誰擁有帳號」。MakePwd 沒有帳號和密碼庫。建立頁和閱讀頁都開啟即用,閱讀頁對接收方公開。

容量也有邊界。32 KB 夠放口令、API Key、復原碼和一小段說明,不夠放資料庫傾印或整份憑證包。整份檔案應在本機做成 .lock / .enc,走雲端硬碟或郵件;檔案加密盒單檔上限 5 GB,同樣開啟即用,明文預設不上傳。把大檔塞進閱後即焚,不是「更安全」,只是超出這條短文本通道的設計。

用開啟即用的建立頁把焚毀態練熟

若你希望用一個把拆分寫在結果區的工具來練習,可以從 MakePwd 的閱後即焚開始。開啟即可使用,雙方都不用註冊。建立時加密發生在目前分頁,演算法是 AES-256-GCM;伺服器只收到密文、ttl_hours(0–168,預設 24)和 max_reads(1–10,預設 1)。產生的連結形態固定為 s.html?id={編號}#{金鑰}。閱讀頁先停在確認步,再取密文、在本機解密。

練習時用上面的金絲雀,閱讀次數保持 1。跑完同時看三處:建立結果區的查詢 / 片段拆分,閱讀頁第一次載入時 Network 有沒有提前取密文,以及第二次開啟是否進入焚毀。拆分用來確認金鑰沒進問號;第一次載入用來確認預覽不會誤燒;第二次開啟用來確認「讀完即焚」不是文案。

若內容還要先去掉追蹤參數再發出去,先走隱私清洗,再把真正需要保密的短文本放進閱後即焚。清洗解決的是查詢字串裡的 UTM 和點擊 ID,不替代一次性密文通道。這些步驟都不要求登入,也不展示未接入的客服信箱。

常見問題

開啟一次後,伺服器上還剩明文嗎?

從來就沒有明文。建立時瀏覽器先加密,伺服器只收到密文。成功取回並達到設定次數後,密文被刪除。再請求同一編號會看到 410 或已被焚毀。連結字串可能還在 LINE 紀錄裡,那不是伺服器上的明文副本。

LINE 預覽會不會先把內容燒掉?

會燒掉次數,通常燒不掉明文。預覽抓的是頁面,金鑰在 # 後不進 HTTP。若閱讀頁在載入時就取密文,預覽會消耗僅有的一次。正確實作是先確認再取回。Slack 等產品會預設抓取訊息裡的連結以產生預覽;LINE 貼上網址時同樣會畫卡片。

410 和 404 有什麼差別?

410 表示這個資源曾經可用,現在被永久去掉,用戶端不該重試。404 更含糊,可能是編號無效,也可能是紀錄已被清掉。閱後即焚用 410 區分焚毀和過期更清楚。無論哪一種,都沒有明文可恢復。

開啟閱讀頁需要登入嗎?

不需要。建立和閱讀都開啟即用。接收方靠編號取密文,靠井號裡的金鑰在本機解密。MakePwd 沒有帳號和密碼庫。存取控制就是完整連結本身。

下次傳一次性秘密時記住的三件事

第一,問「讀完還剩什麼」時,先問伺服器上有沒有過明文。沒有過,刪的就是密文;連結還在 LINE 裡,解不開任何東西。第二,焚毀點是取回密文的那一次請求,不是閱讀頁的第一次繪製。確認按鈕擋的是預覽機器人,不是已經拿到整串的人。第三,核對看 Network:建立時搜金絲雀原文,閱讀時對照請求行與 #,第二次開啟看 410。

若你還要繼續追問金鑰為什麼可以放在井號後面,請讀URL 的井號後面為什麼適合放金鑰,以及什麼時候會失效。本文只把「讀完一次之後,伺服器還剩什麼」收成可以對照狀態碼和請求時機的範圍。