1. 程式人生 > >行走在陽光下的那些不可見字元

行走在陽光下的那些不可見字元

行走在陽光下的那些不可見字元

假設我們已經知道Unicode字符集,如果不清楚也可閱讀本文,然後等待下一篇主要介紹Unicode的文章。

 

背景

 

今天我們主要來聊聊這些行走在陽光下的不可見字元。不可見字元在電腦科學和通訊學中被稱為控制字元或非列印字元,是字符集中的一個碼位(code point),不是一個書面符號,也就是在一般的書面呈現環境中它是不可見字元。

在前端的世界裡,我們翻看MDN的文件就能看到相關資訊,比如String的 轉義字元(Escape Notation)模組就有介紹,

我們可以嘗試,在這些轉義字元中,如 '\f', '\b'等我們去新建一個這樣的字串變數然後console出來是看不見的,但是我們去看該字串的長度卻不等於0。

我們可以在ECMAScript標準中找到相關介紹

A string literal is zero or more characters enclosed in single or double quotes. Each character may be represented by an escape sequence. All characters may appear literally in a string literal except for the closing quote character, backslash, carriage return, line separator, paragraph separator, and line feed. Any character may appear in the form of an escape sequence.

就是除了"closing quote character, backslash, carriage return, line separator, paragraph separator, and line feed" 都能在字串中逐字的出現。

 

工具

 

總的來說不可見字元最大的作用就是不可見性,那麼我們可以利用這個生成一些帶有不可見字元的資訊展示在某些地方。那麼,怎麼去生成這一整套工具呢?讓我們來做一下任務拆解,

  1. 將特定的資訊生成不可見字元,即隱形加密
  2. 將不可見字元與需要顯示的資訊也就是明文資訊組合生成最後的展示文字資訊,即明文與密文的組合
  3. 能夠將上一步生成的展示文字資訊解析得到原始的可見的特定資訊,即反隱形加密

根據以上任務步驟,就能進行功能開發了,

 

隱形加密

 1   const zeroWidthSpace = '\u200B'
 2   const zeroWidthJoiner = '\u200D'
 3   const zeroWidthNonJoiner = '\u200C'
 4   const zeroWidthNonBreakSpace = '\uFEFF'
 5 
 6   function createEncryptionText(text) {
 7   if (!text || typeof text !== 'string') {
 8     throw new Error('invalid param, param must be string')
 9   }
10 
11   const binaryText = textToBinary(text)
12   return binaryText
13     .split('')
14     .map(b => {
15       const num = parseInt(b, 10)
16       if (num === 1) {
17         return zeroWidthSpace
18       }
19 
20       if (num === 0) {
21         return zeroWidthNonJoiner
22       }
23 
24       return zeroWidthJoiner
25     })
26     .join(zeroWidthNonBreakSpace)
27 }
28 
29   function charToBinary(char) {
30       return char.charCodeAt(0).toString(2)
31   }
32 
33   function textToBinary(text) {
34       return text
35        .split('')
36        .map(item => padStar(charToBinary(item)))
37        .join(' ')
38    }
39 
40    function padStar(text, length = 8, chars = '0') {
41     if (typeof text !== 'string') {
42       throw new Error('invalid params. text must be string')
43     }
44 
45     return (
46       Array(length)
47         .fill(chars)
48         .slice(text.length) + text
49     )
50   }
51 
52   console.log(createEncryptionText('wfsovereign')) // ""
53   console.log(createEncryptionText('wfsovereign').length) // 195

 

 

這裡首先準備了一些隱形的Unicode字元用於對要加密文字(後面稱之為簽名)的替換,然後定義好替換的規則,將簽名先轉換成二進位制然後逐位進行替換。上面我們可以看到加密後的文字輸出好似一個空字串,然而我們看到該字串的長度卻是195,由此證明我們成功的將簽名轉化為了隱形文字。

對於2、3點這裡我們就不展開詳說了,我將整個加解密以及隱形碼位的提取抽成了一個ZeroWidthCharacterEncryptionManager 類,然後將程式碼放到了我的GitHub,感興趣的同學可以移步查閱。其中需要的注意的兩點這裡我提一下,一個是反隱形加密的時候要按照加密的規則一一對應,這樣才能得到原始簽名;另一個是提取一段文字內容的時候,我採用的是正則,這個正則如何寫是根據我們採取的一些隱形的碼位來定的,比如上面我選擇的zeroWIdthSpace等,對應的正則就應該是*/[\u200B-\u200C\uFEFF]+/* 。

 

應用

 

根據上面的工具類,我們看到的一個應用場景就是在一段文字中加上隱形簽名或者水印,這樣我們生成的文字內容如果被他人傳播的話,就能通過隱形簽名來檢測是否是從我們這裡傳播出去的,感覺還能保護版權啥的,和在一些網站copy內容會自動帶上出處的做法有異曲同工之妙啊~

那麼,此外還有沒有其他作用?這就要看聰明的你的奇思妙想咯 :)

ps: 及時總結,靜心沉澱;如風少年,砥礪前行。

如想了解更多,請移步我的部落格

歡迎關注我的公眾號 “和F君一起xx”

參考資料:

  1. Control Character
  2. String MDN
  3. Be careful what you copy: Invisible inserting usernames into text width zero width characters
  4. Zero width non joiner
  5. Zero width space