把文字藏進其他文字裡,意思是透過插入字型在視覺上沒有可見寬度的零寬度 Unicode 字元,把一段隱形的 UTF-8 訊息嵌入一句看似正常的句子,使得表面的句子用眼睛讀起來仍然一樣,卻在它的碼點之間偷偷夾帶最多 10,000 位元組的額外資料。這種技術是文字隱寫術 (text steganography) 的一種形式 —— 承載內容是被隱藏的,而不是被加密的 —— 它仰賴 Unicode 保留了若干格式化碼點這個事實,這些碼點存在時並不會移動游標。每個隱藏的 UTF-8 位元組會以剛好八個不可見的位元寫出,再由兩個不可見的標記框住,而整段被框住的序列會緊接在封面句子的第一個可見碼點之後插入。只要有同一套工具的接收者把那段字串原封不動貼回去,就能同時讀出秘密訊息和原始的封面句子。關鍵的瓶頸在於傳輸:任何會剝除預設可忽略 (default-ignorable) 碼點的清理器,都可能在讀者看到之前就把承載內容刪掉。因此,從封面到接收者的每一步,在依賴這段訊息之前,都應該從頭到尾徹底測試過。

hide text in text
hide text in text

零寬度隱寫術實際上在做什麼

文字隱寫術是把第二則訊息夾帶進一段文字中、卻仍讓它在螢幕上看起來正常的手法。Text Steganography 採用的版本,只利用零寬度 Unicode 字元,把一段短短的 UTF-8 字串藏進任意的封面句子裡。可見的句子讀起來仍然和原本的封面一樣,但它底層的碼點序列如今多了一段不可見的、被標記框住的小區塊,任何相容的解碼器都能把它取出來。

這個轉換是在本地、透明地進行的。所有編碼與解碼都在瀏覽器內發生,因此不論封面或秘密都不會上傳到任何地方。這個工具並未宣稱與其他隱寫術網站相容;它反過來用一份明確的命名標記、兩種位元符號與固定的插入點的文件化慣例來說明自己。因為對應關係是公開的,所以它做到的是隱藏,而非密碼學。任何知道或偵測到這套慣例的人都能讀到隱藏的訊息,任何編輯過這段字串的人也能改動它。如果同時需要機密性與合理的否認空間,請先把敏感的資料加密,再去藏加密後的密文。

標記慣例如何運作

這套慣例重複使用了四個 Unicode 碼點,在預設字型下呈現為零寬度。它們並非為了這個工具而發明;它們屬於 Unicode 標準的一般分類 (general category) 集合,而它們在這裡的語意角色,則來自這個工具所記錄的對應關係。

碼點名稱在此慣例中的角色
U+200BZero-Width Space代表二進位 0
U+200CZero-Width Non-Joiner代表二進位 1
U+2063Invisible Separator標記承載內容的起始
U+2064Invisible Plus標記承載內容的結束

每個隱藏的 UTF-8 位元組會以「最高位元在前」的順序展開成 8 個位元。例如位元組 0x41 代表 ASCII 字母 'A',因此展開後的位元序列為 0 1 0 0 0 0 0 1,呈現為不可見的字串 ZWS–ZWNJ–ZWS–ZWS–ZWS–ZWS–ZWS–ZWNJ。等完整的位元組序列寫完後,最前方會放一個 Invisible Separator,最末端會放一個 Invisible Plus。整段被框住的小區塊會直接插入封面第一個可見碼點之後。解碼模式則反向進行:先定位標記、確認標記之間每個字元都是兩種位元符號之一、要求位元數為 8 的倍數,並拒絕任何不是合法 UTF-8 的位元組序列。如果想更深入了解位元組一開始是如何對應到位元的,binary-to-text conversion guide 會逐步說明同一種「位元展開」的概念。

在封面文字中嵌入隱藏訊息

這個工具提供兩種模式 —— hide 與 reveal —— 而做一次完整的來回測試,是確認這套慣例在你的輸入上能運作的最簡單方式。

  1. 開啟 Text Steganography 並切換到 hide 模式。在可見欄位輸入一句普通的封面句子,在隱藏欄位輸入另一段簡短的訊息。
  2. 點擊 generate 控制項來產生隱寫後的字串。渲染出來的結果看起來應該和封面一樣,但它現在在第一個可見字元之後多了一段被標記框住的、不可見的碼點序列。
  3. 透過一個你已經事先測試過會保留零寬度字元的管道來複製這份精確的結果 —— 例如純文字編輯器、對程式碼友善的聊天程式,或是不會自動清理剪貼簿的剪貼機制。請避免截圖、紙本列印,以及任何可能剝除預設可忽略碼點的路徑。
  4. 把這段字串貼回 reveal 模式,不要重新輸入。確認工具同時回傳你的隱藏訊息,以及與原始封面完全一致的文字。如果取出的承載內容是空的、亂碼的,或被標為無效,就比對原始碼點,找出是哪個清理器移除了這些不可見字元。

為什麼承載內容會在傳輸後消失

這套慣例在這個工具內是確定且可逆的,但一旦字串離開瀏覽器,就會跨越那些把隱形 Unicode 當成雜訊處理的系統。社群網路、電子郵件閘道、聊天程式、內容管理系統以及剪貼簿管理工具,經常會對貼上的文字做正規化、篩選或重新序列化。有些會把預設可忽略字元整個剝除;有些則會替換換行符號、對易混淆的碼點做正規化,或直接拒收標記字元。根據 Unicode security considerations,預設可忽略的碼點可以在不改變可見外觀的情況下,被渲染與處理管線忽略 —— 這正是讓這個手法得以運作的特性,也是它在無預警下失敗的原因。

即使在傳輸過程中只少了一個隱藏的位元,承載內容的八位元對齊就會被打壞。中間少掉一個位元,會讓它之後的每個位元組都位移一個位置,於是解碼器看到的會是不同的序列,可能回傳亂碼,也可能因為不是合法 UTF-8 而拒絕解讀。補救之道是把每一條管道在測試通過之前都視為可疑:先把來回測試過的字串傳給自己,在實際送達的內容上跑 reveal 模式,直到該管道被證明能逐位元組同時保留秘密與封面,才去信任它。截圖和紙本列印根本無法承載隱形碼點,因此即使底層字串能存活,這些方式也不適合作為傳遞手段。

限制、安全性,以及務實的期待

這個工具在隱藏欄位最多接受 10,000 個 UTF-8 位元組,在封面最多接受 100,000 個碼點。這些限制存在,是因為每個被隱藏的位元組會在底層字串中額外加入 8 個碼點,外加 2 個框住用的標記,而過長的承載內容會拖慢瀏覽器內的 DOM 渲染。表情符號與非拉丁文字往往一個字元就需要好幾個 UTF-8 位元組,因此能藏的字元數會遠低於位元組上限。如果封面已經含有本慣例的起始或結束標記,工具會拒絕 —— 因為收下任一者都會讓承載內容的邊界變得模稜兩可。封面其他位置出現的純零寬度字元,則仍然是可見句子的一部分,不會被當作承載位元解讀。

Reveal 模式只會取出第一段合規的、被框住的片段,並把格式不對的框標記視為錯誤而非去猜測,同時會回傳還原後的可見封面與秘密。在一般字型下,可見的句子和還原後的封面看起來一模一樣,但它們底層的碼點序列並不相等:字元計數器、diff 工具、原始碼檢視器或資安掃描器,都能立刻把多出來的隱形字元抓出來。請把這個工具的成果視為「用於示範、謎題、清理測試的隱藏」,而不是一條秘密通訊管道。如果要做長久可靠的資料交換,請改用可見的編碼方式或經認證的檔案格式,別讓接收端必須依賴傳輸鏈上每一個環節都忠實保留某套已記錄的零寬度對應關係。

這個工具內的測試涵蓋了 ASCII、CJK、表情符號輸入、Tab 與換行字元、單一碼點的封面、空輸入、被保留的標記、缺少承載內容、以及不完整的位元組。每個測試都會同時斷言取回的隱藏文字與封面的完全還原,因此來回測試只能證明這個工具內部的慣例實作正確,無法證明它能在外部服務中存活。同樣的來回測試紀律,正是你在任何打算使用的管道上應該套用的紀律:確認目的地收到的是完整無缺的框住序列,而不只是可見的句子。

想更深入了解,請參閱 Describe How to Convert Text to Binary Numbers Step by Step