行走在陽光下的那些不可見字元
假設我們已經知道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 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”
參考資料:
- Control Character
- String MDN
- Be careful what you copy: Invisible inserting usernames into text width zero width characters
- Zero width non joiner
- Zero width space