QR 码内部结构:版本、模块与编码模式
QR 码(ISO/IEC 18004)有 40 种尺寸,称为版本。版本 1 是 21×21 的模块(黑白方块)网格;版本每升一级,每边增加 4 个模块,版本 40 达到 177×177。数据越多,版本越高,同样打印尺寸下模块越小,扫描越困难。三个大方块是定位图案,较小的是校正图案,定位图案之间的黑白相间线是时序图案——它们都不承载你的数据,也都不能被遮挡。
容量取决于按片段自动选择的编码模式。数字模式把 3 个数字装进 10 比特;字母数字模式把 2 个字符装进 11 比特,但只支持 45 个字符(数字、大写 A–Z、空格以及 $ % * + - . / :);字节模式每字节用满 8 比特,文本编码事实上是 UTF-8;汉字模式把 Shift-JIS 字符装进 13 比特。版本 40、最低纠错级别下的上限是:7,089 个数字、4,296 个字母数字字符或 2,953 个字节。
两个实用结论。第一,小写字母会强制进入字节模式——像 HTTPS://EXAMPLE.COM/ABC 这样的大写 URL 可以用字母数字模式编码,生成的码明显比小写版本稀疏(协议和主机名不区分大小写,路径通常区分,所以只把你能控制的部分大写)。第二,30–50 个字符的典型短 URL 落在版本 2–4(25×25 到 33×33),这是海报上可靠扫描的最佳区间。
Alphanumeric capacity by version and EC level:
Version 1 (21×21): L 25 / M 20 / Q 16 / H 10
Version 5 (37×37): L 154 / M 122 / Q 87 / H 64
Version 10 (57×57): L 395 / M 311 / Q 221 / H 174
Version 40 (177×177): L 4296 / M 3391 / Q 2420 / H 1852
Same 60-char URL: EC L -> version 3, EC H -> version 5
(more correction = physically denser code)
HTTPS://SDK.IS/QR -> alphanumeric mode (sparser)
https://sdk.is/qr -> byte mode (denser, same length)纠错级别:L、M、Q、H 究竟用什么换什么
QR 码用里德-所罗门纠错保护载荷——与 CD 和深空通信使用的是同一族编码。四个级别表示多少码字被毁后仍可恢复:L 约 7%、M 约 15%、Q 约 25%、H 约 30%。代价是容量:纠错字节与数据共享同一网格,同一个 URL 在 H 级别需要的模块数约为 L 级别的两倍,版本随之上升一到两级。
这些百分比说的是码字而非打印面积,恢复能力还取决于损伤落在哪里。里德-所罗门擅长应付分散的错误,但一道横穿码面的反光条或折痕摧毁交错块的速度,可能比表面数字暗示的更快。而功能图案——定位、时序、格式信息——完全不受纠错保护:一张贴纸盖住某个角落的定位图案,不管什么级别扫描都会失败。
Logo 叠加的原理是有意消耗纠错预算:解码器把被遮住的模块当作损伤来重建。如果要叠 logo,请用 H 级别,并让 logo 面积控制在码面约 10% 以内、居中放置、远离三个定位图案,这样在 logo 的开销之上还给现实中的磨损和反光留有余量。务实的默认值:屏幕和干净印刷用 M,菜单等会被摸来摸去的东西用 Q,只有叠加品牌标识或印在易磨损表面时才用 H——并记住每升一级,码就物理上更密一分。
Level Recoverable codewords Overhead Typical use
L ~7% lowest controlled screens
M ~15% default posters, docs
Q ~25% higher menus, packaging
H ~30% ~2x vs L logo overlay, harsh env
NOT protected by any level:
- the 3 corner finder patterns
- timing patterns / format info
- a missing quiet zone around the code打印的物理学:静区、对比度与距离法则
现实中的 QR 失败大多是物理问题而非数字问题。规范要求静区——四周至少 4 个模块宽的空白边框。设计师总嫌它浪费空间而裁掉,结果扫描器无法把符号从繁杂背景中分离出来,码就间歇性失灵——而且恰恰是那种在你桌上永远复现不了的失灵。
尺寸遵循一条简单的现场法则:码的最小宽度约等于扫描距离除以 10。10 厘米距离扫的名片码 1 厘米就够;3 米外阅读的海报需要 30 厘米的码。再叠加模块数:同样的打印宽度下,版本 10 的码每边 57 个模块,每个模块只有版本 2 模块的三分之一大——高版本码需要的打印面积不成比例地更大。打印分辨率方面,每个模块保持至少约 4 个打印点;办公打印机上模块小于约 0.4 毫米时,墨水扩散会开始把模块糊在一起。
对比度是另一个沉默杀手。解码器会把图像二值化,所以需要浅色背景上的深色模块和足够的亮度差——白底深藏青可以,白底黄色不行。反色码(深底浅模块)违反规范;iOS 和许多 Android 扫描器能读其中一部分,但大量扫码应用和嵌入式读头读不了——没测试过就不要发布。亮面覆膜会产生镜面反光,整片区域直接失效;曲面(瓶子、马克杯)会扭曲几何形状:尽量放在最平整的位置,并且在印一千份之前,务必用 iPhone 和 Android 相机各测一遍。
Field rules of thumb:
min code width ≈ scan distance / 10
business card (10 cm) -> 1 cm
shelf label (50 cm) -> 5 cm
poster (3 m) -> 30 cm
quiet zone >= 4 modules, all four sides
module size >= 0.4 mm on office printers (>= 4 dots)
contrast dark-on-light; inverted codes fail many readers
surfaces flat > gently curved; avoid gloss + direct light值得掌握的载荷格式:Wi-Fi、vCard、otpauth 与动态 QR
QR 码只是编码字节;手机拿这些字节做什么,是由前缀决定的约定。URL 会打开浏览器。WIFI: 字符串用于加入网络:WIFI:T:WPA;S:MyNetwork;P:secret;;——T 是认证类型(WPA 覆盖 WPA2/WPA3 个人版,开放网络用 nopass),S 是 SSID,P 是密码,可选的 H:true 表示隐藏 SSID。值中的反斜杠、分号、逗号、冒号和双引号必须用反斜杠转义——Wi-Fi 密码里没转义的分号,是 Wi-Fi 二维码"时灵时不灵"的经典原因。
联系人数据有两种约定:vCard(BEGIN:VCARD ... END:VCARD)冗长但处处可识别,多行地址也能完好保留;MeCard(MECARD:N:...;TEL:...;;)是 NTT DoCoMo 的紧凑格式,生成的码小得多,但支持的字段更少。双因素认证的 TOTP 密钥以 otpauth://totp/Issuer:account?secret=BASE32&issuer=Issuer 形式传递——请把那个二维码本身当作密钥:谁扫到它,谁就能永远生成你的验证码。其他有用前缀:tel:、mailto:、sms:、geo:37.5665,126.9780。
静态与动态之分是产品决策而非格式。静态码直接嵌入最终 URL:零维护、永久有效,但印出去就改不了。"动态"码只是编码了一个你可控的短重定向 URL,换来印后可改、扫描统计和更短的载荷(码也更稀疏)——代价是依赖:重定向服务停摆或订阅到期,所有已印的码全部报废。凡是要在纸上长期存在的东西,宁可用自己拥有的重定向域名,也别用第三方短链服务。
WIFI:T:WPA;S:Cafe Guest;P:s3cret;;
WIFI:T:WPA;S:My\;Cafe;P:pa\:ss\,word;; <- ; : , escaped
WIFI:T:WPA;S:BackOffice;P:secret;H:true;; <- hidden SSID
otpauth://totp/GitHub:octocat?secret=JBSWY3DPEHPK3PXP&issuer=GitHub
MECARD:N:Doe,Jane;TEL:+821012345678;EMAIL:[email protected];;
BEGIN:VCARD
VERSION:3.0
N:Doe;Jane
TEL;TYPE=CELL:+82-10-1234-5678
EMAIL:[email protected]
END:VCARD安全与隐私:Quishing、机密信息与你的数据去向
QR 码不加密、不签名、无法用肉眼验证。这三个"没有"定义了它的威胁模型。
Quishing——借 QR 码钓鱼——利用的正是人在扫描前读不出载荷这一点。标准攻击是把恶意码贴纸覆盖在停车咪表、餐桌、账单上的正规码之上;支付欺诈变种则重定向到高仿收银页面。防御手段朴素但有效:在码旁边印出人类可读的 URL,让人能与扫描器预览对照;用品牌域名而不是通用短链(预览显示 pay.yourbrand.com 可以核对,随机短码则无从核对);对长期张贴的公共码定期检查是否被贴改。现代手机相机在打开前会显示目标域名——要训练用户真的去读它。
不要把机密放进会被相机拍到的展示或印刷 QR 码里。Wi-Fi 码中的密码是明文,拍下海报的任何人都能拿到;otpauth 码就是 2FA 种子本身;活动票和登机牌里的信息足以克隆或取消预订——把它们晒到社交媒体的人都交过学费。
最后,搞清楚生成发生在哪里。有些在线生成器(包括本页)通过调用服务端 API 生成图片,意味着载荷离开了你的设备;另一些则用 JavaScript 库完全在浏览器内渲染,或者用 qrencode 命令行这类离线工具。编码公开网站的 URL 时两者无差别;编码 Wi-Fi 凭证、TOTP 种子或任何内部信息时,请离线生成——qrencode 命令或本地库能给你同样的 PNG,而无需任何网络往返。
# offline generation — payload never leaves the machine
qrencode -o wifi.png -l H 'WIFI:T:WPA;S:BackOffice;P:secret;;'
# checklist for public, long-lived codes
[ ] human-readable URL printed next to the code
[ ] branded domain (pay.yourbrand.com), not a generic shortener
[ ] HTTPS destination — camera previews show the domain
[ ] periodic physical check for sticker-over-sticker tampering
[ ] no secrets in the payload (Wi-Fi passwords ARE plaintext)