二維碼的秘密
最近在做一個項目,需要做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