§3.3. Кодирование

Важно различать понятия шифрование и кодирование.

Целью шифрования является защита информации от её восприятия всеми, кто может её увидеть, кроме непосредственно тех, кому она предназначается. Даже если принцип шифрования общеизвестен, для расшифровки зашифрованной информации необходимо обладать ключом.

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

Непонимание этого различия, помимо терминологической неточности, может создать настоящие уязвимости: так, методы кодирования, в том числе описанные в этой главе, иногда используют с намерением защитить информацию от прочтения и изменения, однако эта защита оказывается мнимой.

Термином кодировка обозначают метод кодирования текста теми или иными байтами. Мы рассматривали кодировки в §1.3.

Кибершеф

В этой главе, в курсе и вообще по жизни вам пригодится Кибершеф (англ. CyberChef). Этот инструмент создали и поддерживают в GCHQ — Центре правительственной связи Великобритании, спецслужбе, существующей под нынешним названием с конца Второй мировой войны.

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

Кибершеф можно использовать прямо с сайта (при этом он выполняет вычисления прямо в браузере на вашем компьютере, и вводимые данные не передаются по сети). Также его можно скачать и открыть с компьютера.

Попробуйте, например, преобразовать в текст шестнадцатеричную запись байт: d0 9c d0 be d0 bb d0 be d0 b4 d0 b5 d1 86 21. В Operations найдите пункт Data format → From Hex, перетащите его в область Recipe, а в поле Input введите исходные данные.

base64 и другие системы счисления

Как мы убедились в предыдущих главах, произвольные двоичные данные не всегда могут быть нормально отображены в виде текста: при попытке сделать это непосредственно некоторые байты могут превратиться в невидимые или специальные символы; при работе с таким текстом они могут быть повреждены. Запись без специальных символов — то есть ограниченная лишь печатными символами ASCII — удобна для разных целей: такой текст можно без потерь, например, переслать по электронной почте, распечатать и сосканировать или даже записать под диктовку. Нужен способ получать такую запись для произвольных данных.

Запись каждого байта в виде шестнадцатеричного числа (как в примере выше) решает эту задачу: в ней используется алфавит, состоящий из цифр от 0 до 9 и букв от a до f. Так как каждый байт кодируется двумя символами, после кодирования размер данных удвоится.

Однако символов ASCII, пригодных для записи информации в виде текста, существует гораздо больше, чем 16, поэтому придуманы более эффективные способы кодирования, такие как base64.

В base64 каждые три байта преобразуются в четыре числа от 0 до 63. Каждому такому числу соответствует символ: прописная или строчная английская буква, цифра, либо символ + или /. Символ = записывают после неполных троек: =, если до тройки не хватает одного байта, и ==, если двух.

Для кодирования данных в терминале можно использовать команду base64. Режим декодирования включается аргументом: base64 -d.

Типичный результат base64-кодирования выглядит примерно так: 0KLQuNC/0LjRh9C90YvQuQ==. Поскольку достаточно часто бывает, что количество кодируемых байт не делится нацело на 3, base64 обычно легко узнать по знаку (или двум знакам) = в конце. Но даже без этого смесь букв разных регистров и цифр с изредка попадающимися + и / зачастую узнаваема.

Используемые в base64 неалфавитные символы иногда неудобны (например, символ / нельзя использовать в именах файлов), поэтому иногда при сохранении принципа кодирования вместо этих символов используются другие.

Вместо 64 может быть выбран другой размер алфавита. Например, base32, кодируя пятёрки байт восемью символами, позволяет обойтись алфавитом из английских букв в одном регистре и цифр от 2 до 7. А метод base85, также известный как ascii85, использует почти все доступные символы. По аналогии, обычную шестандцатеричную запись изредка называют base16, двоичную — base2 и так далее.

Теоретически можно не кодировать байты группами, а воспринимать все данные как запись очень большого числа — и переводить это число в другую систему счисления. Скажем, слово elephant в шестнадцатеричном виде записывается так: 65 6c 65 70 68 61 6e 74. Шестнадцатеричное число 656c657068616e74 — это десятичное 7 308 327 828 777 430 644. Заметим, что если записать исходные данные в двоичной, четверичной или даже двухсотпятидесятишестеричной (если мы придумаем для неё символы) системе счисления, десятичное число получится такое же.

Текст, но не совсем

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

В путях веб-страниц используется URL-кодирование (urlencode; также процентное кодирование). Все символы, кроме английских букв и цифр, а также символов -, _, . и ~, должны записываться в шестнадцатеричном виде, при этом перед каждым байтом ставится символ %. Например, символ ! кодируется как %21, а буква ж будет представлена как %D0%B6. Сам символ % превратится в %25.

Отметим, что никто не запрещает преобразовать так символы, для которых это не требуется, например, написать %7Ae%72o вместо zero. Процесс полного устранения этих и других избыточностей и неоднозначностей в записи называют нормализацией. Стандарт рекомендует программам проводить нормализацию перед дальнейшим анализом адреса, однако на практике поведение может отличаться, создавая тем самым проблемы и уязвимости.

В электронных письмах используют похожий механизм, Quoted-Printable. Он отличается от URL-кодирования только тем, что вместо символа % используют =, а также несколько другим алфавитом символов, не требующих кодирования.

По сравнению с base64, URL-кодирование и Quoted-Printable крайне неэффективны для данных, доля некодируемых символов в которых мала; так, каждая русская буква, занимающая два байта, после такого кодирования требует целых шесть. В доменных именах, где на счету каждый символ, используют более экономный, но довольно сложный метод punycode. Алгоритм такого кодирования описывается немаленьким блоком псевдокода. Доменное имя, закодированное так, начинается с букв xn-- — например, доменная зона .рф кодируется так: .xn--p1ai.

Выводы

  1. В отличие от шифрования, делающего информацию недоступной для всех, кодирование не защищает информацию, а лишь представляет её в необходимой форме.

  2. Существует Кибершеф, он полезен.

  3. Произвольные двоичные данные можно представить в виде текста, используя base64 или аналогичные методы, или даже в виде длинного десятичного числа.

  4. В адресах веб-страниц и электронных письмах кодирование влияет лишь на некоторые символы.

§3.4. Современная криптография ⟶