艺术鬼才,Unicode 字符还能这么玩?
上周的时候,朋友圈的直升飞机不知道为什么就火了,很多朋友开着各种花式飞机带着起飞。
还没来得及了解咋回事来着,这个直升飞机就到的微博热搜。
后面越来越多人开来他们的直升飞机,盘旋在朋友圈上方。于是很多朋友开来他们的坦克,专打直升飞机,一轰一个准。
好了,说回正题!
程序员朋友应该都很熟悉 Unicode (万国码),它几乎包含世界上所有符号,比如组成直升飞机这几个特殊符号对应的 Unicode 码分别为:
ps:推荐一个网站,可以根据符号搜对应的 Unicode 码:https://unicode.yunser.com/unicode
除了这些正常字符以外,Unicode 还包含着各种各样的奇葩字符。
奇葩字符
除了正常的我们熟知的文字以外,Unicode 中还有一些奇怪的文字,比如下面这些文字
除了这些奇怪文字以外,Unicode 还有一些奇葩的的符号。
例如下面一整套麻将牌:
一整套的扑克牌:
一整套国际象棋:
除了这些,通过组合符合,我们还可以造出各种各样的颜文字(๑•̀ㅂ•́)و✧、
另外 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 浏览器的控制台复制零宽字符,操作方式如下:
发布效果如下:
隐形水印
对于一些内部论坛或者说小说网站来说,可以通过零宽字符在帖子或小说内容嵌入隐形水印。
当这些内容被一些爬虫复制到其他网站时,我们就可以通过隐形水印,轻松查找时那位用户泄漏内容。
隐形水印主要原理就是将用户信息比如用户名,通过一定算法转成零宽字符,这样普通用户浏览时完全看不到这个水印。
如果内容被复制到其他网站,隐形谁赢也被复制,只要找到这个水印,将这些零宽字符反转成用户名即可。
下面展示一种转换方法,JS 代码主要参考以下 Github 项目:
https://github.com/umpox/zero-width-detection
隐形水印生成方法
第一步我们需要将明文字符串每个字符都转成二进制串。
// 每个字符转为二进制,用空格分隔
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 网站,就可以看到字符。
另外你还可以把加密字符串复制到 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;
};
解密示例如下:
短网址
我们常用的短网址,域名后面会跟上一串随机串,从而实现短网址到长网址的映射。比如以下网址:
然而我们可以利用零宽字符也可以实现短网址的效果,,比如下面这个网站,就可以生成这类短网址。
可以看到这个短网址后面看不到任何字符,实际上这后面跟着一串零宽字符。当浏览器访问该短网址时,后端程序只要反解密的后面零宽字符,拿到相应的网址,然后在做跳转就可以到指定的网站。
反解密的原理可以参考上面隐形水印的代码
小心零宽字符
日常开发过程中,我们有时需要从一些文件中读取文本内容,然后做相应的处理。
有时候我们可能会碰到一些诡异的现象,比如我们之前碰到的例子。
后台程序从 Excel 读取文本内容,然后程序中判断是读取的文本内容是否与指定的字符串相等。
然后当我们读取一份 Excel 内容后,返现这段比较逻辑怎么也通过不了。本来以为是 Excel 内容存在空格什么的,但是打开 Excel 仔细一看,跟指定字符串一模一样,并没有什么其他字符。
第一次碰到这种例子,没有什么经验,真的排查了很久,到最后都有点怀疑人生了。最后无意间将文本内容复制到了 IDEA 中,才发现整理混杂着零宽字符!
如果各位小伙伴也碰到这类问题,不妨将复制文本内容,然后到 IDEA 中查看是否存在某些看不见字符~
最后(点个赞呗!)
这两个星期一直很忙,一直都在 9106 的节奏,真的是累,所以断更了一周!
所幸最近项目提测,稍微轻松了一点,能有点划水时间来写写文章。不过再提起笔来写文章,就有点断节奏了!
这篇文章墨迹了很久才水出来,下周开始再次恢复周更的节奏,再忙再累,每周都来一篇。
欢迎各位小伙伴,每周来这里蹲我,Gank 我!!!
好了,我是楼下小黑哥,下周见!!!
参考链接
- https://juejin.im/post/5d3f01e7f265da03c23ead69
- http://zero.rovelast.com/
- https://zws.im/
- https://imweb.io/topic/5a08a5c7ef79bc941c30d8dd
欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn
艺术鬼才,Unicode 字符还能这么玩?的更多相关文章
- 转:Unicode字符集和多字节字符集关系
原文地址: http://my.oschina.net/alphajay/blog/5691 unicode.ucs-2.ucs-4.utf-16.utf-32.utf-8 http://stallm ...
- Java 字符编码(一)Unicode 字符编码
Java 字符编码(一)Unicode 字符编码 Unicode(http://www.unicode.org/versions/#TUS_Latest_Version) 是一个编码方案,说白了希望给 ...
- Unicode字符编码表(转)
Unicode字符编码表 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zhenyu5211314/article/details/5153 ...
- Unicode字符需要几个字节来存储?
0)学习笔记: 我们常说的这句话“Unicode字符是2个字节”这句话有毛病 Unicode目前规划的总空间有17个平面, 0x0000---0x10FFFF,每个平面有 65536 个码点. Uni ...
- Unicode字符集和多字节字符集关系
在计算机中字符通常并不是保存为图像,每个字符都是使用一个编码来表示的,而每个字符究竟使用哪个编码代表,要取决于使用哪个字符集(charset). 在最初的时候,Internet上只有一种字符集—— ...
- unicode字符和多字节字符的相互转换接口
作者:朱金灿 来源:http://blog.csdn.net/clever101 发现开源代码的可利用资源真多,从sqlite3的源码中抠出了几个字符转换接口,稍微改造下了发现还挺好用的.下面是实现代 ...
- 在2005年,Unicode 的第十万个字符被采纳且认可成为标准之一(超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现)
在计算机科学领域中,Unicode(统一码.万国码.单一码.标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统.Unicode 是基于通用字符集(Universal Charac ...
- HTML、CSS、JS对unicode字符的不同处理
unicode字符的不同表示法 unicode字符在html.css和js中的表示方法均不相同,下面分别作介绍. 原文发表于这里 css表示法 首先来一段很常见的bootstrap的字体图标代码: . ...
- Java 经典实例: Unicode字符和String之间的转换
在Java诞生之际,Unicode码是一个16位的字符集,因此char值似乎顺其自然为16位宽,多年来一个char变量几乎可以表示任何Unicode字符. /** * Created by Frank ...
随机推荐
- 洛谷 P5683 【[CSPJX2019]道路拆除】
先用做的暴力,因为n最多才3000嘛,但是后来发现时间复杂度不止\(O\)(\({n}^2\)),然后就放弃了. 讲讲我的暴力+错误思路吧: 把1到s1和s2的最短路算出来,用SPFA,然后用DFS求 ...
- Spring Security(三) —— 核心配置解读
摘要: 原创出处 https://www.cnkirito.moe/spring-security-3/ 「老徐」欢迎转载,保留摘要,谢谢! 3 核心配置解读 上一篇文章<Spring Secu ...
- C++版的网络数据包解析策略(升级版)
初版:http://www.cnblogs.com/wjshan0808/p/6580638.html 说明:在实现了对应的接口后该策略可以适合绝大多数的网络数据包结构 首先,是三个接口 IProdu ...
- 在linux上安装jdk(转载)
软件环境: 虚拟机:VMware Workstation 10 操作系统:Ubuntu-12.04-desktop-amd64 JAVA版本:jdk-7u55-linux-x64 软件下载地址: JD ...
- Selenium WebDriver使用
目录 介绍 selenium webdriver chromedriver下载安装 1.下载 2.使用 3.测试 WebDriver常用操作 1.浏览器操作 2.窗口和弹框操作 3.cookies 操 ...
- VScode和IntelliJ IDEA设置自动换行
VScode自动换行 点击左上角的File-->Auto Save即可实现多文件的自动换行; IDEA自动换行 点击左侧空白处,选择Soft-Wrap就是当前文件自动换行,选择Configure ...
- Python线程池与进程池
Python线程池与进程池 前言 前面我们已经将线程并发编程与进程并行编程全部摸了个透,其实我第一次学习他们的时候感觉非常困难甚至是吃力.因为概念实在是太多了,各种锁,数据共享同步,各种方法等等让人十 ...
- Least Cost Bracket Sequence,题解
题目链接 题意: 给你一个含有(,),?的序列,每个?变成(或)有一定的花费,问变成课匹配的括号的最小花费. 分析: 首先如果能变成匹配的,那么就有右括号的个数始终不多于左括号且左右括号数量相等,那就 ...
- c++ 数字与字符串的相互转换
首先推荐用用C++的stringstream. 主要原因是操作简单. 0x00 字符串转数字 // zcj_14.cpp : //该程序是一个注册机,原理是对输入的字符每个与2求异或的结果取低位即为注 ...
- SaaS 系统架构,Spring Boot 动态数据源实现!
这段时候在准备从零开始做一套SaaS系统,之前的经验都是开发单数据库系统并没有接触过SaaS系统,所以接到这个任务的时候也有也些头疼,不过办法部比困难多,难得的机会. 在网上找了很多关于SaaS的资料 ...