Чёрный кабинет EN

Урок №06 XX век 18 мин

Двоичный код, hex и Base64

Компьютер не знает букв — только нули и единицы. Разбираемся, как текст становится байтами, зачем нужны hex и Base64 и почему строка, похожая на абракадабру, ещё не шифр.

В этом уроке

  • что такое бит и байт и как считать в двоичной системе
  • зачем программистам шестнадцатеричный код
  • как буквы становятся числами: ASCII, кодовые страницы, UTF-8
  • как устроен Base64 и почему он ничего не прячет

Проверяющий открывает файл настроек небольшого интернет-магазина. В строке «пароль администратора» записано cXdlcnR5MTIz. «Пароли у нас зашифрованы», — с гордостью говорит программист. Проверяющий копирует строку, вставляет её в первый попавшийся онлайн-декодер, и через секунду на экране появляется qwerty123. Никакого шифра не было. Был Base64 — способ записать данные, а не спрятать их.

Эта история повторяется постоянно, потому что результат кодирования выглядит как абракадабра. Но абракадабра — не то же самое, что секрет. В этом уроке разберёмся, как текст превращается в числа, а числа — в разные записи: двоичную, шестнадцатеричную, Base64. И научимся с первого взгляда отличать кодировку от шифра.

Бит и байт

В прошлом уроке телеграф передавал два состояния: ток есть — тока нет. Компьютер устроен так же. Самая маленькая порция информации — бит: ноль или единица, выключено или включено. Одним битом можно ответить на вопрос «да или нет», и только.

Чтобы различать больше вариантов, биты собирают в группы. Восемь бит — это байт. У восьми переключателей 2 × 2 × … × 2 = 256 положений, поэтому байт хранит число от 0 до 255. Каждый переключатель весит своё: правый — 1, следующий — 2, потом 4, 8, 16, 32, 64 и левый — 128. Число в байте — сумма весов включённых переключателей.

Щёлкайте по переключателям. Под ними — сумма весов, то же число в шестнадцатеричной записи и буква, которую это число означает в таблице ASCII. Сейчас включены 64 и 1: это 65, буква A.

Это и есть двоичная система: та же запись чисел, что десятичная, только цифр две, а не десять, и каждый разряд вдвое «тяжелее» соседа справа, а не вдесятеро. Число 65 в двоичном виде — 01000001: 64 + 1.

Шестнадцатеричный код

Двоичная запись честная, но длинная: восемь знаков на каждый байт, и в них легко запутаться. Десятичная короче, но неудобна: по числу 200 не видно, какие биты в нём включены. Программисты нашли середину — шестнадцатеричную систему, или hex. В ней 16 цифр: 0–9 и буквы A–F, где A означает 10, а F — 15.

Главное удобство hex в том, что одна его цифра — ровно четыре бита. Байт делится пополам, и каждая половинка становится одной цифрой. 65 = 0100 0001 = 41. 255 = 1111 1111 = FF. Поэтому любой байт в hex — ровно две цифры, от 00 до FF, и по ним сразу видно биты. Посмотрите на виджет выше: две цифры hex подчёркнуты разными цветами — это левая и правая половины байта.

Hex часто встречается в жизни: цвета на сайтах записывают как #B8322A — три байта, красный, зелёный и синий. Аппаратные адреса сетевых карт пишут как 00:1A:2B:…. Если видите пары знаков из 0–9 и a–f, перед вами почти наверняка байты в hex.

Как буквы стали числами

Байт — это число. Чтобы хранить текст, нужно договориться, какое число означает какую букву. Такой договор называется кодировкой. Самая известная — ASCII, американская таблица 1963 года. В ней 128 знаков: латинские буквы, цифры, знаки препинания и служебные команды вроде «перевод строки». A — 65, B — 66, строчная a — 97, пробел — 32, цифра 0 — 48.

Для русских букв в ASCII места не нашлось. Поэтому каждая страна придумывала свою таблицу для верхней половины байта, чисел от 128 до 255. В СССР появилась КОИ-8, у DOS была своя кодовая страница, у Windows — Windows-1251. У КОИ-8 была хитрость: русские буквы стояли на местах похожих латинских, поэтому, если старый компьютер «отрезал» восьмой бит, текст превращался в читаемую латиницу с перепутанными заглавными и строчными: «Привет» становилось «pRIWET». Но таблиц было много, и одни и те же байты значили в них разное. Открыли письмо не в той кодировке — и вместо «Привет» видите «оПХБЕР» или «РџСЂРёРІРµС‚». Такие тексты в народе прозвали «кракозябрами».

Юникод и UTF-8

Чтобы покончить с путаницей, придумали Юникод — одну таблицу на весь мир. В ней у каждого знака свой номер: латинская A — 65, как и в ASCII, русская «я» — 1103, китайские иероглифы — десятки тысяч. Номер записывают как U+ и hex: «я» — это U+044F.

Но номер до миллиона в один байт не влезет. Как записать его байтами? Самый популярный способ — UTF-8. Его придумали в 1992 году Кен Томпсон и Роб Пайк — по легенде, за один вечер, набросав схему на бумажной салфетке в закусочной. Правило такое: знаки ASCII занимают один байт, как раньше; кириллица — два; большинство остальных — три или четыре. Первые биты каждого байта служат метками: они говорят, сколько байтов у знака и какой байт продолжает предыдущий.

Наберите русские и латинские буквы. Красным отмечены служебные биты UTF-8: у одиночного байта это ведущий 0, у первого из двух — 110, у продолжения — 10. Чёрные биты — это сам номер буквы, разрезанный на части.

Посмотрите на «К»: её номер 1050 не помещается в семь бит, поэтому UTF-8 берёт два байта — D0 9A. А латинская c осталась одним байтом 63, точно как в ASCII. Благодаря этому старые английские тексты — уже правильный UTF-8, а русский текст в UTF-8 занимает примерно вдвое больше байтов, чем букв.

Сколько байтов займёт слово «ёжик» в кодировке UTF-8? Ответьте числом.

Посмотрите, сколько байтов у каждой русской буквы в виджете выше. Можно просто набрать там это слово.

Каждая из четырёх букв — кириллица, а кириллица в UTF-8 занимает два байта: ё — D1 91, ж — D0 B6, и — D0 B8, к — D0 BA. Всего 8 байтов.

Заметьте: «ё» начинается с D1, а остальные — с D0. Номер ё (U+0451) больше, и его старшие биты попадают в другой первый байт.

Base64: байты в виде букв

Теперь у нас есть байты. Их можно хранить в файле и передавать по сети. Но есть места, где разрешён только текст, причём самый простой: латинские буквы, цифры и пара знаков. Так была устроена электронная почта: её протоколы создавали для английских писем, и байт с восьмым битом мог по дороге испортиться. А как тогда отправить фотографию или архив?

Решение — Base64. Он берёт любые байты и записывает их 64 «безопасными» знаками: 26 заглавных латинских букв, 26 строчных, 10 цифр, плюс «+» и «/». Получается текст, который пройдёт через любую почту, адресную строку браузера или файл настроек. Вложения в письмах до сих пор передаются именно так.

Почему 64? Потому что 64 = 2 × 2 × 2 × 2 × 2 × 2: один знак Base64 несёт ровно шесть бит. Байты же содержат по восемь. Наименьшее число, которое делится и на 6, и на 8, — 24. Поэтому Base64 работает тройками: берёт три байта (24 бита), режет их на четыре кусочка по 6 бит и каждый кусочек заменяет знаком из таблицы. Кусочек 000000 — это A, 000001 — B, и так до 111111 — «/».

Каждый блок — одна тройка байтов. Цветная черта под битами показывает, какому байту они принадлежат, а строка ниже режет те же биты на шестёрки. Наведите на шестёрку, и подсветятся её биты, число и знак. Если в последней тройке не хватает байтов, недостающие шестёрки превращаются в «=».

Посмотрите, что происходит в конце. В слове «Ёжик» восемь байтов: две полные тройки и ещё два байта. Последнюю тройку дополняют нулями, а на месте шестёрки, где не осталось ни одного настоящего бита, пишут знак =. Так получатель понимает, сколько байтов было на самом деле. По той же причине Base64 всегда примерно на треть длиннее исходных данных: три байта превращаются в четыре знака.

Есть вариант Base64 для адресов страниц, где «+» и «/» заменены на «-» и «_»: так строка не ломает ссылку. Им записаны, например, части токенов входа на сайтах. А Base32 режет биты по пять и обходится 32 знаками — заглавными буквами и цифрами 2–7; его можно продиктовать по телефону, не уточняя регистр.

Кодировка — не шифр

Вернёмся к программисту из начала урока. Его ошибка в том, что он спутал две разные вещи. Шифр зависит от ключа: без ключа текст не прочесть, даже зная алгоритм. Кодировка ключа не имеет. Её правила опубликованы, и любой, кто узнает формат, раскодирует строку за секунду. Двоичный код, hex, Base64, азбука Морзе — всё это способы записи, а не защиты.

Хорошая новость: кодировки легко узнать в лицо. Строка из нулей и единиц группами по восемь — двоичный код. Пары знаков 0–9 и a–f — hex. Латинские буквы обоих регистров с цифрами, длина кратна четырём, на конце может быть один-два знака «=» — почти наверняка Base64. Утилита «Определить шифр» делает такие проверки автоматически, а конвертер Base64 снимает слой за слоем, если данные завернули несколько раз — например, текст в Base64, его в hex, а hex — в двоичный код.

В тайнике нашли записку. Отправитель, видимо, был уверен, что «зашифровал» её. Под каким по счёту камнем ключ?

0JrQu9GO0Ycg0L/QvtC0INGC0YDQtdGC0YzQuNC8INC60LDQvNC90LXQvA==

Посмотрите на набор знаков: латиница разного регистра, цифры, «/», а в конце два «=». Это не шифр, а Base64.

Вставьте строку в конвертер Base64. Он сам поймёт формат и покажет текст.

Строка раскодируется в UTF-8 текст «Ключ под третьим камнем». Ответ — под третьим.

Обратите внимание, как часто в строке повторяются 0L и 0Y: это начала русских букв. У кириллицы в UTF-8 первый байт почти всегда D0 или D1, и в Base64 он даёт узнаваемый узор. Опытный глаз видит «русский текст в Base64» ещё до декодирования.

Кодировка меняет запись, а не смысл: у неё нет ключа, и любой может её обратить. Двоичный код, hex и Base64 — разные способы записать одни и те же байты. Прятать данные умеет только шифр.

Пароль в базе данных хранится как cXdlcnR5MTIz. Он защищён?

Base64 — кодировка, а не шифр. Эта строка за долю секунды превращается в qwerty123. Пароли хранят иначе: их пропускают через специальную хеш-функцию, о которой речь пойдёт в уроке о хешах.

Почему один байт всегда записывается в hex ровно двумя знаками?

Шестнадцать — это 2⁴, поэтому каждая цифра hex описывает ровно четыре бита. Байт из восьми бит — две половинки, две цифры: от 00 до FF.

Что взять с собой

  • Бит — ноль или единица, байт — восемь бит, число от 0 до 255. Двоичная запись показывает биты, hex — те же биты по четыре в цифре.
  • Кодировка текста — договор, какое число означает какую букву. ASCII знает только латиницу; UTF-8 записывает весь Юникод, латиницу одним байтом, кириллицу двумя.
  • Base64 записывает любые байты 64 безопасными знаками: три байта — четыре знака, «=» в конце показывает недостачу.
  • Кодировка — не шифр: у неё нет ключа. Если строка похожа на Base64 или hex, её прочтёт любой.

Шифр делает сообщение нечитаемым. Но можно поступить хитрее: сделать так, чтобы сообщения вообще не было видно. Буквы в младших битах пикселей, картинка внутри звука, невинная фраза с секретом. Дальше: стеганография →

Утилиты урока

Всё из урока — в рабочем виде: вставьте свой текст.