惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
Lohrmann on Cybersecurity
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
S
Schneier on Security
I
Intezer
Latest news
Latest news
N
News and Events Feed by Topic
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
U
Unit 42
量子位
博客园 - 【当耐特】
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
博客园 - 叶小钗
酷 壳 – CoolShell
酷 壳 – CoolShell
NISL@THU
NISL@THU
The Cloudflare Blog
李成银的技术随笔
T
ThreatConnect
L
LINUX DO - 最新话题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
有赞技术团队
有赞技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Jina AI
Jina AI
T
Tor Project blog
The Hacker News
The Hacker News
人人都是产品经理
人人都是产品经理
小众软件
小众软件
S
Security Archives - TechRepublic
美团技术团队
博客园 - Franky
Security Latest
Security Latest
J
Java Code Geeks
P
Proofpoint News Feed
V
V2EX
The GitHub Blog
The GitHub Blog
WordPress大学
WordPress大学
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Help Net Security
PCI Perspectives
PCI Perspectives
Cyberwarzone
Cyberwarzone
Hugging Face - Blog
Hugging Face - Blog
N
Netflix TechBlog - Medium
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
SecWiki News
SecWiki News
腾讯CDC
爱范儿
爱范儿
D
Docker

Все публикации подряд на Хабре

Святой Августин и GAN: почему борьба добра и зла — это генеративная состязательная сеть Я открываю автомат ключом, меняю рулон бумаги и зарабатываю 180 тысяч в месяц с точки Мастер восстановления. Культура достиженства и выгорание Недельный геймдев: #279 — 24 мая, 2026 Защита от дублирования кода агентами: семантические концепции Frontend Status: свежий дайджест фронтенда и AI — 25.05.2026 Где искать IT-работу кроме HH: подборка платформ 2026 Почему простые числа собираются в спирали? OCR для Data Lakehouse: от Apache Tika к собственному решению на базе Docling Jira — Тьюринг-полная Kubernetes-аудит после Wiz и Prisma: как живут без CNAPP в 2026 «Тестируем MVP в 4 раза быстрее»: как нейросети изменили жизнь предпринимателей На каком стеке и железе работает умное наблюдение в вашем городе: обзор технологий от разработчиков видеоаналитики Как мы ускорили согласования на двух заводах в 24 раза Heartbeat-мониторинг cron-job'ов: dead-man-switch на FastAPI [Перевод] Сегодня нет джуниоров, а в 2031 году не станет и синьоров Профайлер для PostgreSQL: от идеи до работающего MVP за сутки [Перевод] Ограничения размера cookie в ASP.NET Core в продакшене: причины и способы решения Проблема «божественного» Obsidian: почему я отказался от централизованного подхода в работе Лицензии GNU GPL: как пройти проверку Минцифры и заказчика для госзакупок и КИИ Хакатон Samsung IT Academy Hack 2026: как студенты оптимизировали поиск в корпоративном мессенджере Хакатон Samsung IT Academy Hack 2026: как студенты оптимизировали поиск в корпоративном мессенджере MTProxy jumper — делаем автоматическое переключение прокси-серверов Telegram Ты уже используешь агента. Просто не заметил Книжный салон. Послевкусие и благодарности Как отлаживать мини‑приложения в MAX и почему без DevTools это боль Cбор биометрических данных. Как защищается наша биометрия на практике Как запустить учет активов без цифровой свалки: первые 90 дней CGE: визуализация кравлера и скрытых связей между поддоменами Зачем банки тратят миллиарды на науку (спойлер: не благотворительности ради) Книга: «Современный Java Concurrency. Глубокое погружение в Virtual Threads, Structured Concurrency и Scoped Values» Как использовать подписку ChatGPT и Claude в Cursor без оплаты за API токены Специализированная ИСУП или модуль в универсальной платформе: вот в чем вопрос Обход белых списков через WebRTC на стероидах (с поддержкой iOS и десктопа) Регата INFOSTART CIO CAMP: когда команда проверяется не в переговорной, а на воде Пет-проект, который не умер: система бронирования устройств как полигон для AI-разработки Не надо встраивать ИИ в каждую корпоративную систему, это архитектурная ошибка Нейросети для дизайна интерьера: Выбираем лучший ИИ для генерации концептов и планировок квартиры Что там с Ил-114-300 Что такое DAS: как и зачем продукт-менеджеры саботируют запуск новых продуктов 8% компаний измеряют критическое мышление руководителей. Что делают остальные 92% CVE, Shell и побег из контейнера: испытываем возможности PT Cloud Application Firewall Как я научил Алису петь: генерация музыки по голосовой команде Восстановление данных с помощью бесплатной утилиты Easy Disk Checker Как мы построили сквозную аналитику в Power BI Год разработки iOS-игры, 266 тысяч показов и $33: как я делал Vault и почти ничего не заработал Ты прокрастинируешь потому, что избегаешь напрасных усилий, а не чрезмерных нагрузок Я построила диагностику «стоит ли это автоматизировать» — и она трижды говорила глупости. Разбор ошибок Как устроены world models, что показал Google на прошлой неделе и где это меняет gamedev и робототехнику Двухдневная рабочая неделя — будущий стандарт CPU не умер, он просто ждал. Китай строит двухэксафлопсный суперкомпьютер без единого GPU — прорыв, необходимость, фейк? 3Sound: поиск бесплатных звуков для игр больше не боль? 3 Тбит/с по-русски: почему DDoS в 2026 году стал угрозой для любого бизнеса 10 Гбит/с — зачем вам такая скорость передачи данных в облаке Ремонтируем аналоговый XY-самописец Endim 622 [Перевод] IPO компании SpaceX: хорошая попытка, но нет «Ща будет шрифт»: история одного русского embedded‑шрифта Как аквариум на подоконнике превратился в full-stack платформу с AI GiftsHub — из чат-бота в полноценный backend-продукт Пиратство, копирайт и DMCA: как Napster, The Pirate Bay и YouTube изменили закон. Часть II Как найти внутренние резервы для развития предприятия Как один французский чиновник от безысходности начал платил зарплаты картами и практически изобрёл банкноты RAG в энтерпрайзе: почему демо работает, а прод нет AI-агент для финансовых процессов: как мы научили ИИ считать числа из базе данных без галлюцинаций Автопостинг на 8 платформах: архитектура waterfall, custom publisher'ы и API-ловушки Кинетика против бронзы: Почему Голиаф был обречен в дуэли с Давидом [Перевод] Масштабирование LLM: от одного чипа до ЦОДа. Глава 2. Шардинг LLM не работает за вас. Она работает с вами Чем лучше защищает минеральный SPF, тем страшнее он выглядит Стимпанк как часть жизни. История паровых двигателей и место, которое они занимали в мире в XIX-XX веках. Часть 1 Гастарбайтеры ворвались в IT и зарабатывают на рекламе: тут вам не снег лопатой кидать Новые методы и инструменты: как мы обновили курсы по тестированию в Яндекс Практикуме Java 21 в стиле «клятый энтерпрайз» на одноплатном компьютере возрастом 13 лет Ваши секреты внутри LLM. Куда уходят промпты и чего стоит опасаться? 10× труда. 10% к бонусу. Главный риск AI-эпохи — это сениор AI-инженер, который умеет считать Сапожник с сапогами Минимум, который удержит тебя на плаву в период дедлайнов Как без проблем переносить курсы между платформами? Обзор формата SCORM Когда Claude Code ошибается не по своей вине: документационный долг в соло-проектах 70% кода с AI — и ни на день быстрее qrrot — база данных со встроенным ИИ Шахматные программы V. Оценочная функция Восстание масс в обществе спектакля и отчуждение труда в царстве количества: что делать во времена всеобщего упадка? Не умеешь работать с ИИ? Тебя заменит тот, кто умеет Как интеллект становится уязвимостью под давлением Не надо так: три типичные ошибки, которые приводят ко взлому Заметки про код-стайл в C++ Забытый мультиколор (часть 1) Культура ест стратегию на завтрак: почему не работает долгосрочное планирование Советское ИИ: Забытые гении Как оплатить iCloud в России в 2026 году без смены региона Apple ID Глубокая интеграция месседжинга с бизнес процессами в фреймворке NodaLogic Контекстные менеджеры в Python за пределами with open(): пишем свои и упрощаем код Пароль против уборщицы Выяснились детали мега-IPO SpaceX, а также первый прибыльный квартал Anthropic Люди с психическими расстройствами – новая нефть? Когда нейросети перестанут галлюцинировать? И почему на «что за дичь» они несут ещё большую дичь? Мессенджер HalChat теперь в Google Play: 3 года разработки, ИИ в браузере и квест с модерацией Реверс-инжиниринг Xiaomi Smart Band 10 Когда памяти мало
В каждом QR-коде зашита половина лишней информации. Намеренно
inkedsymon · 2026-05-25 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение8 мин

Охват и читатели0

Обзор

TL;DR QR-код продолжает работать, если замазать маркером четверть его площади, наклеить сверху логотип или оторвать угол. Это математически точная избыточность, которая на максимальном уровне коррекции занимает около 60% всех модулей кода. Под катом — почему так, как это устроено, немного кода на Python и при чём здесь Toyota 1994 года.


Маленький эксперимент

Если вы возьмете обычный QR-код, ведущий на habr.com. Откроете редактор и нарисуете поверх него кошку. Кривую, фломастером — какую угодно, то сканер всё равно прочитает ссылку.

Закрасите четверть площади сплошным чёрным? Прочитает.

Вырежете угол ножницами? Прочитает.

Это легко проверить: возьмите телефон, любой генератор QR-кодов, и постепенно увеличивайте площадь поверхностного шума. В какой-то момент сканер сдастся, но «какой-то момент» наступит сильно позже, чем кажется.

Почему? Потому что значительная часть того, что вы видите в QR-коде, — это перестраховка.

Сколько именно «лишнего»?

У QR-кода есть четыре уровня коррекции ошибок:

Уровень

Можно повредить

Доля под избыточность

L (Low)

~7%

~14%

M (Medium)

~15%

~30%

Q (Quartile)

~25%

~50%

H (High)

~30%

~60%

Цифра «можно повредить N%» означает: вы можете стереть, замазать или испортить эту долю кодовых слов (байт), и код всё равно будет прочитан корректно. Чтобы алгоритм исправил одну ошибку, ему нужно держать в коде два дополнительных байта-страховщика. То есть на уровне H собственно полезной информации в коде — около 40%. Остальное — служебка, маркеры и Reed-Solomon.

Именно поэтому маркетологам разрешают вставлять логотипы посреди QR-кодов: задают уровень H, накрывают центр чем угодно, и оно работает.

Reed-Solomon на пальцах

В основе коррекции лежит код Рида-Соломона над конечным полем GF(2⁸) = GF(256). Звучит зловеще, но идея на пальцах формулируется без потерь.

Представьте, что вы хотите передать число 5. Можно передать его восемь раз: 5 5 5 5 5 5 5 5. Если две пятёрки превратятся в шум — большинство голосует, и мы восстанавливаем. Работает, но избыточно: одно число превратилось в восемь.

Рид-Соломон — это толковое обобщение. Вместо дефолтного повторения мы:

  1. Берём k исходных байт данных и интерпретируем их как коэффициенты многочлена степени k-1.

  2. Вычисляем значения этого многочлена в n > k точках.

  3. Передаём все n значений.

Теперь школьная теорема: многочлен степени k-1 однозначно восстанавливается по любым k своим значениям. Значит, получатель может потерять до n-k значений из n — и всё равно восстановить полином, а с ним и данные.

Если же значения не потеряны, а искажены (мы не знаем, какие из них правильные) — потребуются более хитрые алгоритмы: Berlekamp-Massey, метод Форни. Они находят, какие байты «врут», и исправляют их. Лимит: до (n-k)/2 искажений.

То есть чем больше «лишних» значений добавили, тем больше потерь стерпит код.

GF(256), или почему оно вообще конечное

Тут резонный вопрос: мы считаем значения многочлена — но байт-то всего 256 разных, а значения многочлена с ростом x улетают в бесконечность. Что делать?

Ответ: работать в конечном поле. В GF(256) есть ровно 256 элементов (все возможные значения байта), и в этом поле определены сложение, вычитание, умножение и деление так, что они «замкнуты» — результат любой операции снова попадает в эти же 256 значений.

Конкретно для QR-кода используют поле GF(2⁸), построенное по неприводимому многочлену x⁸ + x⁴ + x³ + x² + 1. Реализуется тривиально. Сложение в GF(2⁸) — это просто XOR. А умножение можно сделать через две таблицы: экспонент и логарифмов.

# Построение таблиц GF(256) для QR-кода (полином 0x11D)
gf_exp = [0] * 512
gf_log = [0] * 256
x = 1
for i in range(255):
    gf_exp[i] = x
    gf_log[x] = i
    x <<= 1
    if x & 0x100:           # если "вышли за байт" — приводим по модулю
        x ^= 0x11D
for i in range(255, 512):   # дублируем для удобства, чтобы не делать % 255
    gf_exp[i] = gf_exp[i - 255]
def gf_mul(a, b):
    if a == 0 or b == 0:
        return 0
    return gf_exp[gf_log[a] + gf_log[b]]
# Проверка: 0x80 * 0x02 — это сдвиг на разряд влево, дающий 0x100;
# вылезший девятый бит убираем XOR'ом с полиномом 0x11D, остаётся 0x1D.
print(hex(gf_mul(0x80, 0x02)))   # 0x1d

Двенадцать строк, и у вас есть всё умножение в поле, на котором держится коррекция ошибок не только QR, но и CD, DVD, Blu-ray, спутникового телевидения и связи с «Вояджерами». Семейство таких полей (GF(2⁸) с разными полиномами редукции) вообще на удивление вездесущее: MixColumns в AES работает в GF(2⁸), только с полиномом 0x11B вместо QR-шного 0x11D. То есть когда вы открываете HTTPS-страницу, под капотом несколько раз в секунду прокручивается арифметика того же типа.

Что ещё занимает место (кроме коррекции)

Помимо избыточности под Reed-Solomon, в QR-коде довольно много вещей вообще не несут пользовательских данных:

  • Позиционные маркеры — три больших квадрата по углам. По ним сканер находит код в кадре и ориентирует его (в любом повороте). Внутри каждого маркера — соотношение 1:1:3:1:1 чёрно-белых полос; именно его сканер ищет в кадре по горизонтали и вертикали.

  • Маркеры выравнивания — маленькие квадратики (их количество растёт с размером кода). Компенсируют перспективные искажения, если код сфотографирован под углом.

  • Тайминг-паттерны — чередующиеся черно-белые полоски между угловыми маркерами. По ним вычисляется размер одного модуля и линейные искажения.

  • Формат-информация — 15 бит, кодирующих уровень коррекции и номер маски. И они тоже защищены отдельным BCH-кодом, восстанавливающимся при трёх ошибках из пятнадцати.

  • Версия-информация — для версий 7 и старше: ещё 18 бит, говорящих сканеру размер кода. Тоже BCH-защищённые.

  • Маска — XOR-паттерн, накладываемый поверх всего кода (об этом ниже).

В сумме на «версии 1» (сетка 21×21, итого 441 модуль) при уровне L под собственно данные остаётся 152 бита. Это 19 байт. Меньше длины твита 2007 года.

Четыре режима кодирования

Чтобы выжать из этих 19 байт максимум, QR умеет кодировать данные четырьмя разными способами:

Режим

Битов на символ

Кому это надо

Numeric (0–9)

3.33 бит/символ

Чисто цифровые ID, телефоны, номера счетов

Alphanumeric

5.5 бит/символ

Заглавная латиница + цифры + $ % * + - . / : и пробел

Byte

8 бит/символ

Произвольные байты (UTF-8 идёт сюда)

Kanji

13 бит/символ

Двухбайтовые JIS-символы

Заметили: «3.33 бита на цифру»? Это потому что цифры кодируются группами по три: три десятичные цифры — это число от 0 до 999, которое влезает в 10 бит. Получается 10/3 ≈ 3.33 бита на цифру.

Аналогично для alphanumeric: символы кодируются парами, каждая пара — число от 0 до 44² = 1935, влезает в 11 бит. То есть 5.5 бит на символ.

В один QR-код можно складывать сегменты разных режимов подряд. Поэтому для строки вида HABR42 оптимальный кодировщик использует alphanumeric (а не byte) и экономит 30% бит. Энкодеры по умолчанию обычно делают это автоматически.

Маски: зачем они и почему их восемь

Если бы QR-код выводился «как есть», часто получались бы длинные одноцветные полосы, большие чёрные блоки или совпадения с позиционными маркерами. Сканеру было бы плохо: он не сможет надёжно отличить модули данных от служебной разметки.

Решение: после сборки всего кода поверх него накладывается XOR-маска — регулярный шахматный или полосчатый паттерн. Из восьми разных масок энкодер выбирает ту, что даёт «лучшее выглядящий» код, по специальной системе штрафов:

  • штраф за длинные одноцветные ряды и столбцы (5 модулей и больше);

  • штраф за блоки 2×2 одного цвета;

  • штраф за паттерны, похожие на позиционные маркеры (соотношение 1:1:3:1:1);

  • штраф за общий перекос баланса чёрного и белого.

Энкодер генерирует код восемь раз, по разу с каждой маской, считает штраф для каждого варианта и оставляет лучший. Поэтому два QR-кода с одной и той же ссылкой могут выглядеть по-разному в зависимости от того, как лёг сегмент данных под маску.

Номер выбранной маски записывается в формат-инфрмацию, чтобы декодер знал, что снимать.

Практика: посмотрим разницу на коде

Установим библиотеку и сгенерируем один и тот же URL с четырьмя разными уровнями коррекции:

# pip install qrcode[pil]
import qrcode
from qrcode.constants import (
    ERROR_CORRECT_L, ERROR_CORRECT_M,
    ERROR_CORRECT_Q, ERROR_CORRECT_H,
)
URL = "https://habr.com/ru/articles/000000/"
levels = {
    "L": ERROR_CORRECT_L,
    "M": ERROR_CORRECT_M,
    "Q": ERROR_CORRECT_Q,
    "H": ERROR_CORRECT_H,
}
for name, ecc in levels.items():
    qr = qrcode.QRCode(error_correction=ecc, box_size=10, border=2)
    qr.add_data(URL)
    qr.make(fit=True)
    side = qr.modules_count            # сторона матрицы в модулях
    total = side * side
    img = qr.make_image()
    img.save(f"qr_{name}.png")
    print(f"{name}: версия {qr.version}, {side}×{side} = {total} модулей")

L: версия 3, 29×29 = 841 модулей

M: версия 3, 29×29 = 841 модулей

Q: версия 4, 33×33 = 1089 модулей

H: версия 5, 37×37 = 1369 модулей

Одна и та же ссылка в 42 символа. При L и M помещается в версию 3 (29×29). При Q уже нужна версия 4. При H — версия 5, в полтора раза больше по площади. Эти «лишние» модули — и есть та самая страховка.

А вот вторая половина эксперимента: возьмите получившийся qr_H.png, откройте в Paint и закрасьте чёрным любую четверть. Сканер вашего телефона всё равно его прочтёт.

Любопытные следствия

1. Логотип в центре — это by design. При уровне H центральные ~25% можно безболезненно закрасить чем угодно. Большинство онлайн-генераторов делают это автоматически: ставят H, рисуют поверх лого. Тут никакого особого алгоритма — одна избыточность.

2. QR-арт. Существуют алгоритмы (а в последнее время — и нейросети) которые подбирают значения «свободных» модулей так, чтобы код визуально превращался в осмысленное изображение, оставаясь валидным. Stable Diffusion + ControlNet — самый известный из современных подходов. Получаются фотореалистичные QR-коды, которые работают.

3. Атаки подменой. Reed-Solomon защищает от случайного шума, но не от целенаправленных правок. Если злоумышленник аккуратно изменит несколько модулей данных так, что результат станет указывать на другой URL, — сканер прочитает «исправленные» данные как валидные и не заметит подмены.

4. Малюсенький QR-код — миф. Чем меньше код, тем большую долю его площади занимает обязательная служебка (маркеры, тайминги, формат-информация). Поэтому «маленький код с одной короткой ссылкой» на практике почти всегда не такой уж и маленький.

5. Старший брат: версия 40. Самый большой стандартный QR-код имеет размер 177×177 модулей и вмещает до 2953 байт двоичных данных (на уровне L). На уровне H — уже 1273 байта. Разница ровно того порядка, что мы обсуждаем.

6. Чёрно-белая инверсия. Стандарт допускает инверсию: белые модули на чёрном фоне. Современные сканеры это понимают, хотя старые могли спотыкаться.

Соседи по семейству

QR — не единственный 2D-код в мире, просто самый известный. Соседи решают похожие задачи с разными компромиссами:

  • Data Matrix — квадратный или прямоугольный код, тоже на Reed-Solomon, но компактнее на коротких данных. Стандарт в фарме и электронике (маркировка деталей).

  • Aztec Code — концентрический, без quiet zone (свободного поля по краям), хорошо читается на билетах и пропусках. Используется в авиа- и ж/д-билетах.

  • PDF417 — полосчатый (stack-based), читается линейными сканерами. На задней стороне водительских прав в США.

  • MaxiCode — фирменный код UPS, шестиугольные модули. Оптимизирован под чтение на быстром конвейере.

  • HCCB (High Capacity Color Barcode) — короткоживущий проект Microsoft с цветными модулями, чтобы запихнуть больше данных. Не взлетел.

Но QR победил потому, что Denso Wave открыли спецификацию и не стали брать роялти. Все остальные изобретатели 2D-кодов теперь тихо сидят в сторонке.

История и почему именно столько

Цифры 7/15/25/30% — компромисс между:

  • размером кода (больше избыточности, больше модулей, больше места на упаковке);

  • надёжностью в «грязных» условиях (отпечатки пальцев, выгоревшая краска, помятая бумага, плохое освещение);

  • скоростью декодирования (в 1994 году процессоры были, мягко говоря, не современные).

QR разрабатывался в 1994 году в компании Denso Wave — дочке Toyota — для маркировки автомобильных деталей на производственной линии. Деталь могла прийти в масле, царапинах и грязи; сканер должен был успеть прочитать её за доли секунды. Команда из двух человек под руководством Масахиро Хары потратила на разработку полтора года. Идея с позиционными маркерами в углах, говорят, пришла Харе в обед, когда он играл в го: фишки на доске — это, в каком-то смысле, тоже двоичная сетка.

Особое внимание уделили устойчивости. Поэтому в стандарт заложили четыре уровня коррекции — чтобы пользователь сам выбирал между плотностью и надёжностью под свой сценарий. И заложили её щедро: на максимуме надёжность важнее размера, чуть ли не половина кода уходит «в страховку».

Зато ваш QR-код на меню в кафе всё ещё читается, после того как официант пролил на него соус и протёр салфеткой. А промышленные коды на автозапчастях читаются после поездки в контейнере через половину Тихого океана.