零寬度字元:和諧?遮蔽?不存在的

yuanfux發表於2018-09-03

對零寬度字元完全沒有頭緒的可以先玩下這個Demo

什麼是零寬度字元?

零寬度字元是一些不可見的,不可列印的字元。它們存在於頁面中主要用於調整字元的顯示格式,下面就是一些常見的零寬度字元及它們的unicode碼和原本用途:

  1. 零寬度空格符 (zero-width space) U+200B : 用於較長單詞的換行分隔
  2. 零寬度非斷空格符 (zero width no-break space) U+FEFF : 用於阻止特定位置的換行分隔
  3. 零寬度連字元 (zero-width joiner) U+200D : 用於阿拉伯文與印度語系等文字中,使不會發生連字的字元間產生連字效果
  4. 零寬度斷字元 (zero-width non-joiner) U+200C : 用於阿拉伯文,德文,印度語系等文字中,阻止會發生連字的字元間的連字效果
  5. 左至右符 (left-to-right mark) U+200E : 用於在混合文字方向的多種語言文字中(例:混合左至右書寫的英語與右至左書寫的希伯來語),規定排版文字書寫方向為左至右
  6. 右至左符 (right-to-left mark) U+200F : 用於在混合文字方向的多種語言文字中,規定排版文字書寫方向為右至左

零寬度字元能做什麼?

1. 傳遞隱密資訊

利用零寬度字元不可見的特性,我們可以用零寬度字元在任何未對零寬度字元做過濾的網頁內插入不可見的隱形文字。下面是一個簡單的利用零寬度字元對文字進行加密解密JavaScript例子:

加密
// 為了程式碼的簡潔與易讀性,以下程式碼會忽略效能方面考量

const text = `123?`;

// Array.from 能讓我們正確讀取寬度為2的Unicode字元,例:?
const textArray = Array.from(text);

// 用codePointAt讀取所有字元的十進位制Unicode碼
// 用toString將十進位制Unicode碼轉化成二進位制(除了二進位制,我們也可以使用更大的進位制來縮短加密後的資訊長度,以此提升效率)
const binarify = textArray.map(c => c.codePointAt(0).toString(2));

// 此時binarify中的值是 ["110001", "110010", "110011", "11111011000000000"],下一步我們需要將"1","0"和分隔符對映到響應的零寬度字元上去

// 我們用零寬度連字元來代表1,零寬度斷字元來代表0,零寬度空格符來代表分隔符
// 下面的``看上去像是空字串,但其實都是長度為1,包含零寬度字元的字串
const encoded = binarify.map(c => Array.from(c).map(b => b === `1` ? `‍` : `‌`).join(``)).join(`​`);

// 此時encoded中包含的就是一串不可見的加密文字了

複製程式碼

注:在使用零寬度字元進行加密時,請儘量避免將加密後的隱形文字插入在明文的開頭或者結尾處,以此來避免隱形文字在複製時被遺漏

解密
// 接著上面的encoded
// 用分隔符(零寬度空格符)提取加密文字中的字元
const split = encoded.split(`​`);

// 將文字轉回成二進位制陣列
const binary = split.map(c => Array.from(c).map(z => z === `‍` ? `1` : `0`).join(``));

// 此時binary中的值再次回到開始的 ["110001", "110010", "110011", "11111011000000000"]

// 最後一部只需要將二進位制文字轉回十進位制,再使用 String.fromCodePoint 就可以得到原文字了
const decoded = binary.map(b => String.fromCodePoint(parseInt(b, 2))).join(``);

// 此時decoded中的值即是 "123?"

複製程式碼
應用
  1. 隱形水印

    通過零寬度字元我們可以對內部檔案新增隱形水印。在瀏覽者登入頁面對內部檔案進行瀏覽時,我們可以在檔案的各處插入使用零寬度字元加密的瀏覽者資訊,如果瀏覽者又恰好使用複製貼上的方式在公共媒體上匿名分享了這個檔案,我們就能通過嵌入在檔案中的隱形水印輕鬆找到分享者了。

  2. 加密資訊分享

    通過零寬度字元我們可以在任何網站上分享任何資訊。敏感資訊的稽核與過濾在當今的網際網路社群中扮演著至關重要的角色,但是零寬度字元卻能如入無人之境一般輕鬆地穿透這兩層資訊分享的屏障。對比明文雜湊表加密資訊的方式,零寬度字元加密在網上的隱蔽性可以說是達到了一個新的高度。僅僅需要一個簡單的識別/解密零寬度字元的瀏覽器外掛,任何網站都可以成為資訊分享的遊樂場。

2. 逃脫詞匹配

// 利用零寬度字元來分隔敏感詞
const censored = `敏感詞`;

let censor = censored.replace(/敏感詞/g, ``); // ``

// 使用零寬度空格符對字串進行分隔
const uncensored  = Array.from(censored).join(`​`);

censor = uncensored.replace(/敏感詞/g, ``); // `敏​感​詞`

複製程式碼
應用
  1. 逃脫敏感詞過濾

    通過零寬度字元我們可以輕鬆逃脫敏感詞過濾。敏感詞自動過濾是維持網際網路社群秩序的一項重要工具,只需倒入敏感詞庫和匹配相應敏感詞,即可將大量的非法詞彙拒之門外。使用諧音與拼音來逃脫敏感詞過濾會讓語言傳遞資訊的效率降低,而使用零寬度字元可以在逃脫敏感詞過濾的同時將詞義原封不動地傳達給接受者,大大提高資訊傳播者與接受者之間交流的效率。

示例與小結

為了更好地理解與使用零寬度字元,我為大家提供了一個Demo
工具庫,庫中提供了一些應用零寬度字元的常見方法(加密解密逃脫匹配…)。零寬度字元在頁面中的存在可能是一個好事,但也可能是一個壞事,一切都取決於你如何去使用零寬度字元。如果你不想在你的頁面中看到這些零寬度字元,你可以選擇完全過濾這些字元,但是這樣會造成一些特殊語言的排版問題。所以,請酌情謹慎處理這些隱形的字元。

最後給各位留一個小彩蛋

“I‏‍‏‌‏‎‍​‏‏‌‏‌‏​‏‌‌‌‍‎‍​‏‏‏‎‎​‏‏‌‏‌‏​‌‎‏‏‍‍​‏‏‎‎‎‍​‌‎‌​‏‍​‏‍​‌‏‎​‌‏‎​‌‎​‏​‌‎‌t’s not who I am underneath, but what I do that defines me.” -Bruce Wayne

reference

Be careful what you copy: Invisibly inserting usernames into text with Zero-Width Characters by umpox

相關文章