二维码的秘密
二维码的秘密
最近在做一个项目,需要做App扫码登录,前端利用websocket,监听后端扫码成功的响应来实现登录。我用了qrcodejs来生成二维码,二维码生成出来的时候,我有一些疑惑:
- 它的容量有多大?
- 二维码里面三个大方块是干什么的?
- 能存储视频/音频吗?
- 破损了为什么还能被识别?
- 生成原理是怎样的?
带着这些疑问,查阅相关资料,得到了我想要的答案
诞生和普及
上个世纪60年代,日本迎来高速增长期,卖食品、衣服等种类繁多的超市开始在城市中出现,当时超市使用的现金出纳机要靠手动输入商品价格,因此负责现金出纳的人常常会因手腕的麻木和“腱鞘炎”而苦恼。
技术是第一生产力,条形码的出现解决了这个问题,因此,条形码得以普及,条形码也叫一维码,缺点是,它的容量非常有限,最多能容纳20个英文字符和数字。怎么办?有个日本人,他叫原昌宏,投入了二维码的研发,一年半后,几经曲折,二维码诞生了。
二维码共有40个版本,每个版本有固定的码元数。码元是什么?就是位,1码元等于1bits。第一个版本(version 1)的码元数为21码元×21码元,version 2的码元数为25码元×25码元,每个版本横向和纵向各自以4码元为单位递增,以此类推
一维码和二维码
二维码翻译成英文为QR Code,全称为Quick Response Code,即快速响应码,二维码研发之初,有两个重点,一个是可以容纳大量信息,另一个就是可以快速读取。二维码跟一维码比起来,优点显而易见,以下是两者之间的差异
类型 | 差异 |
---|---|
一维码 | 1、容量较小:30个字符左右 2、只能包含字母和数字 3、尺寸相对较大(空间利用率低) 4、遭到破坏后无法读取 |
二维码 | 1、数据容量大 2、超越了数字字母的限制 3、尺寸相对较小 4、被破坏依然可以被读取 |
二维码图形拆解
在继续深入了解二维码之前,先观察二维码图片,了解一些基本概念
二维码左上角、右上角以及左下角都有一个回形方块,右下方有一个小的回形方块,他们的作用是什么?剩下的都是一些类似于像素点的黑白方块,他们是怎么来的?
三个大方块,即位置探测图形,用来标记二维码的大小和方向
定位图形:二维码图形过大时,扫码容易畸形,定位图形就来防止畸形的产生
校正图形:版本2及以上才有
格式信息:包括纠错等级、掩码类别
版本信息:二维码的版本
数据和纠错码字:数据码和纠错码
出现了一些新概念,暂时不理解不要紧,往下看
二维码优点
1、存储大容量信息
一图胜千言,一个小小的二维码,可以存储成百上千个字符,具体容量跟二维码的版本有关,版本越大,容量越大
最小的版本1能存储41个数字 || 25个英文+数字 || 10个汉字
最大的版本40能存储7089个数字 || 4296个英文+数字 || 1817个汉字
2、在小空间内打印
相对而言,二维码占用的空间比一维码小得多
3、有效处理各种文字
二维码可以存储各种文字,因为最终都是转换成二进制
4、具备纠错能力
二维码小部分破损,仍然可以被读取,因为二维码有纠错能力,纠错能力还分为4个级别,分别是:
- L级,恢复率7%
- M级,恢复率15%
- Q级,恢复率25%
- H级,恢复率30%
级别越高,纠错能力越强,但由于数据量会随之增加,二维码尺寸也会变大。
纠错级别的恢复率,是指全部码字与可以纠错的码字的比例。例如,一共有100个码字(数据码),要对其中50个进行纠错,纠错级别为Q,则需要纠错码的个数等于50/0.25 - 100 = 100,也就是需要100个纠错码,加上数据码一共200个码字。
码字是什么概念?1个码字 = 8个码元,一个码元 = 1bits
5、360度方向读取
二维码可以从任何角度读取
6、支持数据合并
一个二维码可以拆分为多个,多个也可以合并为一个
安全性
2018年9月,出现了一些二维码,iPhone扫码后,立刻重启,这是因为二维码指向一个包含成千上万个div,设置了特定的样式,耗尽了iPhone的资源,而触发了iPhone的自我保护机制,重启了。这说明二维码带来便利的同时,也存在一些安全问题。
扫描二维码可以跳转到指定URL,是因为URL转换为二进制存储在二维码里面,因此所有基于URL的攻击,都有可能存在二维码中,什么网络钓鱼、传播恶意软件、SQL注入、XSS之类,甚至可能二维码本身就是恶意URL编码生成的,也可能是正常的二维码生成后被篡改,针对前者,要做的就是不去扫描来源不明的二维码,针对后者,解决方式无外乎两种:
第一种:非对称加密,二维码生产者用私钥加密,解码工具用生产者的公钥解密,类似于证书校验。
第二种:将二维码生成hash值,和URL一起编码到二维码内容里,解码工具将扫描到的二维码内容生成hash,与二维码内自带的hash比对一致,意味着二维码没有被篡改过。
二维码生成原理
生成原理,重点是数据码和纠错码的生成
数据码的生成
先上4个表
表1,模式编号指示器
Mode | Indicator |
---|---|
数字 | 0001 |
字母数字 | 0010 |
8位字节 | 0100 |
日文 | 1000 |
中文 | 1101 |
…… | …… |
表2,字符计数指示器中的位数
表3,字符映射表
表4,二维码版本对应的码字数(第2列)和纠错码数(第4列)
案例:将数字01234567编码,指定版本为1,纠错级别为Q
1、将数字3位为1组,分为3组:012,345,67
2、分别将3组数字转换成二进制,二进制长度为10(查表2,可知版本1-9,数字位数为10),012转成0000001100,345转成0101011001,67转成1000011,最后的67转换的长度为什么不是10位而是7位?分组后,不足3位的组,转换为4位或7位
3、把数字的个数转成二进制:01234567长度为8,8的二进制是0000001000,长度同样是10位
4、把数字编码的标志0001(查表1可得)和第3步的编码加到前面,得到如下编码
模式编号 | 字符数 | 数据编码 |
---|---|---|
0001 | 0000001000 | 0000001100 0101011001 1000011 |
5、在末尾加上结束符,结束符固定为4个0,得到如下编码
模式编号 | 字符数 | 数据编码 | 结束符 |
---|---|---|---|
0001 | 0000001000 | 0000001100 0101011001 1000011 | 0000 |
如果编码长度不是8的倍数,需要在后面继续补0,目前一共是83bits,需要补5个0,得到
模式编号 | 字符数 | 数据编码 | 结束符 | 不是8的倍数补0 |
---|---|---|---|---|
0001 | 0000001000 | 0000001100 0101011001 1000011 | 0000 | 00000 |
6、未达到最大bits数限制(每个版本都有最大bits上限,查表4可得,版本1,纠错级别Q,总码字数26,纠错码占了13个码字,那么数据码为26 - 13 = 13个码字,最大bits数限制为13*8 = 104bits),末尾加补齐码,重复11101100 00010001直到达到最大限制,得到
模式编号 | 字符数 | 数据编码 | 结束符 | 不是8的倍数补0 | 补齐码 |
---|---|---|---|---|---|
0001 | 0000001000 | 0000001100 0101011001 1000011 | 0000 | 00000 | 11101100 00010001 |
以上编码即为数字01234567的数据码,字符(英文+数字)的编码略有不同,相同的是编码都很繁琐
纠错码的生成
纠错码,使得有些二维码污损了也能扫码解析,主要是通过里德-所罗门纠错算法(Reed-Solomon Error Correction)实现的,这个算法,比较复杂,感兴趣的可以看看相关链接
- 纠错码的数学背景
- 纠错码的生成
二维码绘制
有了数据码和纠错码,就可以开始绘制二维码了,对照上文的二维码拆解图,绘制分为以下几步
1、 位置探测图形:不管二维码版本是多少,位置探测图形固定为7×7码元
2、 定位图形
3、 校正图形:不管二维码版本是多少,位置探测图形固定为5×5码元
4、 格式信息,包括纠错等级、掩码类别:格式信息固定为15bits,其中5bits数据位,10bits纠错位
- 5bits数据位中,2bits表示纠错等级,3bits表示掩码类别
纠错等级编码表
纠错等级 | 二进制编码 |
---|---|
L | 01 |
M | 00 |
Q | 11 |
H | 10 |
掩码,是在填充完数据码和纠错码之后,和数据区(包括数据码和纠错码)进行异或运算,让二维码中黑色块和白色块分布得更均匀一些,便于解码
掩码类别共有8种,二进制编码分别是000、001、010、011、100、101、110、111,下面的图片印刷有误,最后两种编码写成一样,计算表达式也写成一样,实际最后一个编码为111,计算表达式为 ((ij)mod 3 + (i + j)mod 2) mod 2 = 0,很多文章都没有纠正这一点
5、版本信息:版本7及以上的二维码才需要加入版本信息
6、填充数据码和纠错码:数据码和纠错码都是一串长长的二进制数,每8bits为一块,即一个码字,填充顺序从右下角往上逐个填充,到顶后再从上往下填充,以此类推,如下图
以下是一个版本2,纠错级别为M的二维码数据区示意图,先填充数据码,数据码填充完再填充纠错码,D开头的是数据码,E开头为纠错码
7、掩码:第4点讲到过,填充完数据区后,黑白点可能不均衡,会有大面积空白或黑色块,解码困难,掩码就是用来解决这个问题。将数据区的二进制数,和掩码的二进制数进行异或运算,最终得到的图形,才是我们看到的二维码。
结语
回到本文开头,还有一个问题:二维码能存储视频/音频吗?答案当然是可以的,因为最终都是转换成二进制,但是,二维码容量实际只有1KB,要存储视频/音频,只能说理论上可以。
觉得不错,点个star吧Github