艺术鬼才,Unicode 字符还能这么玩?

上周的时候,朋友圈的直升飞机不知道为何就火了,不少朋友开着各类花式飞机带着起飞。git

图片来自网络

还没来得及了解咋回事来着,这个直升飞机就🔥到的微博热搜。程序员

图片来自网络

后面愈来愈多人开来他们的直升飞机,盘旋在朋友圈上方。因而不少朋友开来他们的坦克,专打直升飞机,一轰一个准。github

图片来自网络

好了,说回正题!web

程序员朋友应该都很熟悉 Unicode (万国码),它几乎包含世界上全部符号,好比组成直升飞机这几个特殊符号对应的 Unicode 码分别为:算法

ps:推荐一个网站,能够根据符号搜对应的 Unicode 码: https://unicode.yunser.com/un...

除了这些正常字符之外,Unicode 还包含着各类各样的奇葩字符。json

奇葩字符

除了正常的咱们熟知的文字之外,Unicode 中还有一些奇怪的文字,好比下面这些文字后端

这咋读?某少?

世代?

恩?超出认知范围

除了这些奇怪文字之外,Unicode 还有一些奇葩的的符号。浏览器

例以下面一整套麻将牌:网络

一整套的扑克牌:ide

一整套国际象棋:

image-20200725215319183

除了这些,经过组合符合,咱们还能够造出各类各样的颜文字(๑•̀ㅂ•́)و✧、

另外 Unicode 还收录着咱们经常使用的 Emoji

除了这些以外,Unicode 中还有一些特殊字符的,利用这些字符,咱们还能够玩出不少有趣的骚操做。

组合字符

Unicode 有一类字符称为组合字符,它能够附加在前一个非组合字符上,从而使总体看起来像是一个字符。

组合字符原来目的是为了解决一些地区语言、文字特殊的须要,好比说泰文声调符号与母音符号。

正常使用的状况下,这些组合字符数量都会有一些限制。可是在 Unicode 组合字符设计上,并无加这种限制,这样使咱们能够无限加这类组合字符。

利用这个特性,能够达到一些恶搞效果,好比「击穿天花板」与「凿穿地板」的效果。

上面实现原理其是利用如下两个组合字符:

上翻字符

下翻字符

只要复制这两个字符相应的 HTML 代码,跟在正常的字符后面,就可使这两个字符附加在普通字符上,好比下面实现效果为

黑̮̑

Unicode 码值一般使用 U+N(16 进制N 表明码值),好比 A 的码值为 U+0041。

在 HTML 中 Unicode 可使用 &#N;(十进制,N 表明码值)表示

在 JS 中 Unicode 中须要使用] \uN(16 进制N 表明码值)表示

只要咱们在普通字符多复制几个这类附加字符,就能够造成上述「击穿」效果。

还记得上面说的泰文吗,曾经有一段时间贴吧,很流行一种喷射文,好比下面的效果。

向左喷

向右喷

左右互喷

这种喷射文实际原理就是利用泰文中声调符号附加在其余正常符号上。

不过如今这个效果貌似已经没办法再复现了,如今咱们只能看到这样的效果:

在一些老版本的系统/浏览器可能还能看到这种效果,知道的小伙伴留言区能够告知一下。

零宽字符

Unicode 中还有一类格式字符,不可见,不可打印,主要做用于调整字符的显示格式,因此咱们将其称为零宽字符。

零宽字符主要有如下几类:

零宽度空格符 (zero-width space) U+200B : 用于较长单词的换行分隔

零宽度非断空格符 (zero width no-break space) U+FEFF : 用于阻止特定位置的换行分隔

零宽度连字符 (zero-width joiner) U+200D : 用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果

零宽度断字符 (zero-width non-joiner) U+200C : 用于阿拉伯文,德文,印度语系等文字中,阻止会发生连字的字符间的连字效果

左至右符 (left-to-right mark) U+200E : 用于在混合文字方向的多种语言文本中(例:混合左至右书写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右

右至左符 (right-to-left mark) U+200F : 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左

利用零宽字符不不可见的特性,咱们也能够玩出一些骚效果。

空白微博

发布微博的时候,若是内容都是空格,将没办法发布。

可是若是咱们将零宽字符,好比说「零宽度空格符 U+200B」复制到微博,这样咱们就能够发布空白微博。

咱们能够利用 Chrome 浏览器的控制台复制零宽字符,操做方式以下:

发布效果以下:

真的没有改 HTML 致使的.jpg

隐形水印

对于一些内部论坛或者说小说网站来讲,能够经过零宽字符在帖子或小说内容嵌入隐形水印。

当这些内容被一些爬虫复制到其余网站时,咱们就能够经过隐形水印,轻松查找时那位用户泄漏内容。

隐形水印主要原理就是将用户信息好比用户名,经过必定算法转成零宽字符,这样普通用户浏览时彻底看不到这个水印。

若是内容被复制到其余网站,隐形谁赢也被复制,只要找到这个水印,将这些零宽字符反转成用户名便可。

下面展现一种转换方法,JS 代码主要参考如下 Github 项目:

https://github.com/umpox/zero...

隐形水印生成方法

第一步咱们须要将明文字符串每一个字符都转成二进制串。

// 每一个字符转为二进制,用空格分隔
    const textToBinary = username => (
      username
      .split('')
      // charCodeAt 将字符转成相应的 Unicode 码值
      .map(char => char.charCodeAt(0).toString(2))
      .join(' ')
    );

示例以下:

第二步,将二进制串转为零度字符串,转换规则以下:

  • 1 转换为 u200b 零宽度字符(zero-width space)
  • 0 转换为 u200c 零宽度断字符(zero-width non-joiner)
  • 其余(剩余就是空格) 转换为 u200d 零宽度连字符 (zero-width joiner)
  • 最后使用 ufeff 零宽度非断空格符 (zero width no-break space) 做为分隔符
const binaryToZeroWidth = binary => (
  binary.split('').map((binaryNum) => {
    const num = parseInt(binaryNum, 10);
    if (num === 1) {
      return '\u200b'; // \u200b 零宽度字符(zero-width space)
    } else if(num===0) {
      return '\u200c'; // \u200c 零宽度断字符(zero-width non-joiner)
    }
    return '\u200d'; // \u200d 零宽度连字符 (zero-width joiner)

  }).join('\ufeff') // \ufeff 零宽度非断空格符 (zero width no-break space)
);

最终加密方法以下:

const encode = username => {
  const binaryUsername = textToBinary(username);
  const zeroWidthUsername = binaryToZeroWidth(binaryUsername);
  return zeroWidthUsername;
};

使用加密方法将明文字符串加密以后,加密字符串肉眼是看不到了,可是实际仍是存在的。

实际上,若是咱们将加密以后字符串复制到 BEJSON 网站,就能够看到字符。

image-20200722083507869

另外你还能够把加密字符串复制到 IDEA 中,能够看到相应的 Unicode 编码值。

解密隐形水印

知道了加密的方式,解密其实就很简单,咱们只要按照相反步骤的来就能够了。

第一步,将隐形水印按照如下规则转换为二进制串。转换规则以下:

  • 使用 ufeff 分隔字符串
  • u200b 转为 1
  • u200c 转为 0
  • 其余字符使用空格
const zeroWidthToBinary = string => (
  string.split('\ufeff').map((char) => { // \ufeff 零宽度非断空格符 (zero width no-break space)
    if (char === '\u200b') { // \u200b 零宽度字符(zero-width space)
      return '1';
    } else if(char === '\u200c') { // \u200c 零宽度断字符(zero-width non-joiner)
      return '0';
    }
    return ' ';
  }).join('')
);

调用该方法,隐形水印转成二进制串。

第二步,将二进制再转为相应的字符。

const binaryToText = string => (
  // fromCharCode 二进制转化
  string.split(' ').map(num => String.fromCharCode(parseInt(num, 2))).join('')
);

最终解密方法以下:

const decode = zeroWidthUsername => {
  const binaryUsername = zeroWidthToBinary(zeroWidthUsername);
  const textUsername = binaryToText(binaryUsername);
  return textUsername;
};

解密示例以下:

短网址

咱们经常使用的短网址,域名后面会跟上一串随机串,从而实现短网址到长网址的映射。好比如下网址:

https://sourl.cn/iLyn9S

然而咱们能够利用零宽字符也能够实现短网址的效果,,好比下面这个网站,就能够生成这类短网址。

https://zws.im/

能够看到这个短网址后面看不到任何字符,实际上这后面跟着一串零宽字符。当浏览器访问该短网址时,后端程序只要反解密的后面零宽字符,拿到相应的网址,而后在作跳转就能够到指定的网站。

反解密的原理能够参考上面隐形水印的代码

当心零宽字符

平常开发过程当中,咱们有时须要从一些文件中读取文本内容,而后作相应的处理。

有时候咱们可能会碰到一些诡异的现象,好比咱们以前碰到的例子。

后台程序从 Excel 读取文本内容,而后程序中判断是读取的文本内容是否与指定的字符串相等。

而后当咱们读取一份 Excel 内容后,返现这段比较逻辑怎么也经过不了。原本觉得是 Excel 内容存在空格什么的,可是打开 Excel 仔细一看,跟指定字符串如出一辙,并无什么其余字符。

第一次碰到这种例子,没有什么经验,真的排查了好久,到最后都有点怀疑人生了。最后无心间将文本内容复制到了 IDEA 中,才发现整理混杂着零宽字符!

若是各位小伙伴也碰到这类问题,不妨将复制文本内容,而后到 IDEA 中查看是否存在某些看不见字符~

最后(点个赞呗!)

这两个星期一直很忙,一直都在 9106 的节奏,真的是累,因此断更了一周!

所幸最近项目提测,稍微轻松了一点,能有点划水时间来写写文章。不过再提起笔来写文章,就有点断节奏了!

这篇文章墨迹了好久才水出来,下周开始再次恢复周更的节奏,再忙再累,每周都来一篇。

欢迎各位小伙伴,每周来这里蹲我,Gank 我!!!

好了,我是楼下小黑哥,下周见!!!

参考连接

  1. https://juejin.im/post/5d3f01...
  2. http://zero.rovelast.com/
  3. https://zws.im/
  4. https://imweb.io/topic/5a08a5...
欢迎关注个人公众号:程序通事,得到平常干货推送。若是您对个人专题内容感兴趣,也能够关注个人博客: studyidea.cn

相关文章
相关标签/搜索