Base32
Кодирует текст или шестнадцатеричный код в Base32 или base32hex, дополнение и регистр на выбор, и обратно в браузере, со строкой и столбцом лишнего символа.
2CP5DAGQXDILFUFV2GBA====
Байты в тридцати двух символах и цифры, которые в них не вошли
Base32 записывает байты в виде текста, используя тридцать два символа, каждый из которых обозначает пять бит, поэтому каждые пять байт — сорок бит — превращаются в восемь символов. Hello — это пять байт, и записывается это слово как JBSWY3DP. Выберите «Декодировать» и вставьте Base32 — страница вернёт байты, которые он обозначает: в виде текста или, если выбрать в переключателе «Байты как» вариант «Шестнадцатеричный код», в шестнадцатеричном коде.
RFC 4648, который его определяет, использует двадцать шесть прописных букв и цифры от 2 до 7. RFC объясняет, почему в алфавите нет 0 и 1: любой, кто работает с текстом, легко может принять 0 за O, а 1 — за l или I, поэтому алфавит сохраняет буквы и обходится без этих двух цифр. Кроме букв, ему нужны шесть цифр, и шесть, которые он берёт, — это цифры от 2 до 7, так что 8 и 9 в нём тоже нет. При декодировании в этом алфавите страница отклоняет 0, 1, 8 или 9 там, где они стоят, а не читает 0 как O или 1 как I: RFC разрешает декодеру читать их так и говорит, что по умолчанию ему так делать не следует.
Регистр не входит в значение. RFC 4648 задумал Base32 для текста, который нужно читать без учёта регистра, поэтому jbswy3dp — это Hello точно так же, как и JBSWY3DP. RFC записывает свой алфавит прописными, как и эта страница, если вы не включите опцию «строчные». Кроме того, при чтении пропускаются пробельные символы в любом месте, включая переводы строк, так что Base32, разбитый на группы или на несколько строк, читается так, будто записан одним куском.
Дополнение и длины, которые бывают у Base32
Байты редко идут ровно по пять. Оставшиеся в конце от одного до четырёх байт занимают два, четыре, пять или семь символов, и RFC 4648 дополняет эту последнюю группу знаками = до восьми символов — шестью, четырьмя, тремя или одним. Так, f — это MY======, fo — MZXQ====, foo — MZXW6===, а foob — MZXW6YQ=, тогда как fooba, пять байт, ровно заполняет свои восемь: MZXW6YTB.
Итак, если считать по восемь символы до первого знака = (если он есть), в остатке всегда будет 0, 2, 4, 5 или 7 и никогда — 1, 3 или 6, ведь никакое число байтов такого остатка не оставляет; страница отклоняет такой текст на его последнем символе, а не читает его как что-то более короткое. Дополнение не сообщает ничего, чего не сообщала бы длина, поэтому страница читает Base32 и без дополнения (JBUQ — это Hi точно так же, как JBUQ====) и отклоняет дополнение, только если оно есть и неверно: = в середине, после которого идёт ещё текст, или неверное число = в конце, и тогда отказ сообщает, сколько их там должно быть.
При кодировании опция «Дополнение» решает, записываются ли знаки =. Изначально она включена, потому что RFC 4648 просит дополнения, если использующая его спецификация не говорит иного, а некоторые говорят: Key Uri Format, описывающий otpauth:// URI в QR-коде для 2FA, говорит, что дополнение секрета следует опускать, а записи NSEC3 и IPFS не пишут его вовсе. Опция «строчные» рядом с ней пишет буквы строчными; у цифр и знака = нет регистра, который можно было бы сменить. Обе опции видны только при кодировании, поскольку при чтении принимается любое их сочетание.
Другие инструменты читают меньше, чем эта страница. GNU coreutils записывает Base32 командой base32, а base32hex, второй алфавит, — командой basenc --base32hex; в модуле base64 языка Python есть функция для каждого из них. Все они пишут то же, что эта страница пишет при открытии: с дополнением и прописными буквами. Но base32 -d отклоняет Base32, в котором нет дополнения или буквы строчные, и b32decode из Python тоже отклоняет и то и другое, а строчные читает, только если ему передать casefold=True:
# Запись: алфавит по умолчанию, затем другой
printf 'Hi' | base32 # JBUQ====
printf 'Hi' | basenc --base32hex # 91KG====
# Обратное чтение: с дополнением, затем без дополнения, затем строчными
printf 'JBUQ====' | base32 -d # Hi
printf 'JBUQ' | base32 -d # base32: invalid input
printf 'jbuq====' | base32 -d # base32: invalid input
# То же самое в скрипте
import base64
base64.b32encode(b'Hi') # b'JBUQ===='
base64.b32hexencode(b'Hi') # b'91KG===='
base64.b32decode('JBUQ') # binascii.Error: Incorrect padding
base64.b32decode('jbuq====', casefold=True) # b'Hi'Два алфавита: base32 и base32hex
RFC 4648 определяет второй алфавит, base32hex: цифры от 0 до 9, а за ними буквы от A до V, так что его символы идут в порядке значений, которые они обозначают, — от 0 для нуля до V для тридцати одного. Это даёт ему одно свойство, которого нет у первого и на которое указывает RFC: при посимвольном сравнении его текст сортируется в порядке байтов, которые он обозначает. Байт 00 — это AA====== в base32 и 00====== в base32hex, а байт FF — 74====== и VS======; при сортировке как текста base32 ставит FF первым, ведь цифры идут раньше букв, тогда как base32hex сохраняет порядок байтов.
Его используют записи NSEC3 из DNSSEC. Имя такой записи начинается с хеша доменного имени; хеш записан в base32hex без дополнения, и RFC 5155 отмечает, что, записанные так, хешированные имена сортируются в том же порядке, что и хеши по значению. В его собственной зоне-примере имя example хешируется в 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom. Когда выбран base32hex, страница читает эти 32 символа как 20 байт хеша; когда выбран base32, она отклоняет 0 — а замечание говорит, что в base32hex текст читается целиком, рядом с кнопкой, которая переключает алфавит.
Поскольку одни и те же байты — это разный текст в каждом из них (Hi — это JBUQ==== в base32 и 91KG==== в base32hex), выбранный вами алфавит действует в обоих направлениях, и страница никогда не меняет его за вас. Когда декодируемый текст содержит символ, которого нет в выбранном алфавите, или заканчивается лишними битами, отличными от нуля (что это такое, объясняют вопросы ниже), а другой алфавит читает его целиком так, как записал бы кодировщик, замечание сообщает об этом рядом с кнопкой, которая переключает алфавит; алфавит меняется, только когда вы нажмёте эту кнопку или выберете его сами. Текст, который оба читают без помех, например ABCDEFGH, читается в выбранном, поскольку ничто в нём не говорит, какой имелся в виду.
Где встречается Base32: секреты 2FA, адреса onion и IPFS
В Key Uri Format, описывающем otpauth:// URI, который может содержаться в QR-коде для настройки, секрет, на котором основаны коды приложения-аутентификатора, записывается в Base32: параметр secret этого URI — это Base32, а дополнение следует опускать. Вставьте такой секрет прописными или строчными буквами, группами через пробел или на нескольких строках, со знаками = или без них — дефис между группами отклоняется там, где стоит, — или вставьте URI целиком, и страница прочитает из него секрет и сообщит об этом. Что означают остальные параметры URI и как секрет превращается в коды, которые показывает приложение, — этим занимается Отладчик кодов TOTP / 2FA: его руководство объясняет URI и его параметры, а его страница вычисляет коды. Метка и издатель в URI закодированы процентным кодированием, как просит Key Uri Format, и их читает Кодировщик / декодировщик URL.
Адрес onion в версии 3 Tor — тоже Base32. Его 56 символов перед .onion — это Base32 от 35 байт: 32-байтового открытого ключа сервиса, двухбайтовой контрольной суммы и байта версии, 03. Спецификация Tor записывает свои примеры адресов строчными, а 35 байт заполняют целые группы, так что дополнения, которое можно было бы опустить, нет. Вставьте 56 символов без .onion, выберите в переключателе «Байты как» вариант «Шестнадцатеричный код», и последний байт прочитается как 03.
IPFS по умолчанию, начиная с версии 1, записывает свои идентификаторы содержимого, CID, в Base32: строчными и без дополнения, после одной буквы, b, которая не входит в Base32, а называет его, — это префикс multibase, который снимают перед тем, как декодировать остальное. Поэтому bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi, пример из собственной документации IPFS, здесь отклоняется целиком — как длина, которой не даёт никакая последовательность байтов; без своей b он читается как 36 байт — двоичный CID, который начинается с 01, номера своей версии.
Секрет 2FA — это байты, а не текст
Пример секрета в Key Uri Format — JBSWY3DPEHPK3PXP, и этот документ прямо расписывает его значение как десять байт: символы Hello!, затем DE AD BE EF. Первые восемь его символов, JBSWY3DP, сами по себе — это Hello. Если декодировать его здесь, когда в переключателе «Байты как» выбран «Текст» (это выбор страницы по умолчанию), получится Hello!, затем U+07AD — DE AD случайно образуют один символ, знак письма тана, — а затем два U+FFFD, каждый на месте последовательности, которая не является текстом. Замечание под результатом подсчитывает заменённые последовательности, говорит, что первая начинается с байта 9, биты которого начинаются в символе в строке 1, столбце 13, то есть в 3, и говорит, что байты, возможно, вовсе не являются текстом.
Настоящий секрет — это случайные байты, которые редко складываются в текст, поэтому, декодированный как текст, он превращается в россыпь посторонних символов и U+FFFD, которая ничего не говорит о том, верен ли секрет. Для этого и нужен вариант «Шестнадцатеричный код» в переключателе «Байты как». Выберите его или нажмите кнопку рядом с замечанием, и тот же секрет будет показан целиком как 48 65 6C 6C 6F 21 DE AD BE EF: сами байты, то есть то, что хранит сервер и из чего вычисляется каждый код. Страница никогда не переключается на шестнадцатеричный код сама, так что на экране всегда то, что выбрали вы.
Обратное направление — тот же выбор, сделанный при кодировании. Ключ, который сервер хранит в шестнадцатеричном коде, — это байты: выберите «Кодировать» и в переключателе «Байты как» — «Шестнадцатеричный код», вставьте ключ — через пробел или слитно, с 0x перед байтами, массивом чисел или дампом в формате hexdump -C или xxd, — и страница запишет Base32, который приложение-аутентификатор принимает. 48656C6C6F21DEADBEEF превращается в JBSWY3DPEHPK3PXP, секрет, приведённый выше; десять байт заполняют целые группы, а ключ, который их не заполняет, например ключ из шестнадцати байт, выходит со знаками = в конце, если не выключить опцию «Дополнение», как просит Key Uri Format. На шестнадцатеричный код, вставленный при декодировании по ошибке, — чётное число шестнадцатеричных цифр и ничего, кроме пробельных символов, в виде, который кодирование может прочитать как байты, — страница отвечает замечанием, что он похож на шестнадцатеричный код, рядом с кнопкой, которая переходит к его кодированию. А при декодировании кнопка «Скачать» сохраняет сами байты в файл с именем bytes.bin.
Base32 или Base64 и почему не алфавит Крокфорда
Base64 делает ту же работу шестьюдесятью четырьмя символами, по четыре на каждые три байта, поэтому его текст на треть длиннее байтов, тогда как текст Base32 длиннее их на три пятых. Взамен Base64 жертвует регистром: в его алфавите прописные и строчные буквы — разные значения, а кроме них есть + и /, так что SGk= — это Hi, а sgk= — два других байта. Base32 подходит для текста, который проходит через что-то, не различающее регистр, или который человек читает с одного экрана и набирает на другом. Когда вставленный сюда Base64 содержит + или /, которых не пишет ни один Base32, и устроен как Base64 от начала до конца, он отклоняется с замечанием, что похож на Base64, и ссылкой на инструмент Base64, который читает Base64 как текст.
Существуют и другие алфавиты из тридцати двух символов, а эта страница предлагает два алфавита RFC 4648 и никаких других. Один из них — алфавит Дугласа Крокфорда. В этом алфавите есть все десять цифр, нет I, L, O и U, и им пользуется формат ULID. Крокфорд определил его для записи чисел, и, если записывать в нём байты, у него два ответа: шестнадцать байт от 00 до 0F, упакованные по пять бит, как упаковывает байты RFC 4648, — это 000G40R40M30E209185GR38E1W, а прочитанные как одно 128-битное число, как читаются двадцать шесть символов ULID, — это 00041061050R3GG28A1C60T3GF. Страница, записывающая в нём байты, выбирала бы один из двух ответов за вас, и вы бы этого не видели.
Частые вопросы
- Почему в моём декодированном секрете стоит U+FFFD?
- Потому что секрет 2FA — это случайные байты, а случайные байты редко бывают текстом в UTF-8. Когда в переключателе «Байты как» выбран «Текст», страница записывает каждую последовательность, которая не является текстом, как U+FFFD, символ замены, а замечание говорит, сколько их было и где начинается первая — с какого байта и в какой строке и каком столбце стоит символ, в котором начинаются биты этого байта. Сами байты не затронуты: кнопка рядом с замечанием показывает их в шестнадцатеричном коде, а «Скачать» сохраняет их. U+FFFD, который действительно есть в тексте, — байты EF BF BD — читается как текст и не учитывается.
- Что значит «длина, которой не даёт никакая последовательность байтов»?
- Если считать символы Base32 по восемь, не учитывая знаки =, в остатке будет 0, 2, 4, 5 или 7, какими бы ни были байты, поэтому текст, у которого в остатке 1, 3 или 6, не может обозначать никакие байты, и страница отклоняет его на последнем символе. Кодировщик такой текст так не записывал: что-то потерялось или добавилось по дороге к вам. JBSWY3DPEHPK3P, секрет из Key Uri Format без двух символов, отклоняется именно там. Обрыв может прийтись и на длину, которая у байтов бывает, и тогда текст читается как меньшее число байтов — JBSWY3DPEHPK3PX как девять, — что страница может заметить, только если лишние биты последнего символа не нулевые, как у этого X. Обрыв на границе целой группы из восьми не оставляет ничего, что можно было бы заметить.
- Что такое лишние биты и почему страница говорит, что мои не нулевые?
- Пять бит на символ и восемь на байт редко сходятся ровно, поэтому, если байты не идут пятёрками, последний символ несёт от одного до четырёх бит, которых не хранит ни один байт, и кодировщик записывает их нулями. MZXW6YQ= и MZXW6YR= — оба это foob: последние три бита Q — 000, а у R — 001, и только первый — то, что пишет кодировщик. Страница читает оба, а для второго её замечание называет R, место, где он стоит, и Q, который кодировщик пишет на этом месте. Лишние биты, отличные от нуля, означают, что текст — не то, что записал кодировщик: его могли обрезать, исправить вручную или записать в другом алфавите, и последнее страница проверяет за вас.
- Является ли Base32 шифрованием?
- Нет. Base32 меняет то, как записаны байты, и ничего больше: декодировать его может кто угодно, и любой декодер, следующий RFC 4648, получает в одном и том же алфавите одни и те же байты. Секрет 2FA, записанный в Base32, — это сам секрет, так что всякий, кто видит ключ настройки, владеет вашим вторым фактором.
- Отправляется ли что-нибудь из вставленного на сервер?
- Нет. Оба направления работают на этой странице, на вашем собственном устройстве: ничто из вставленного — секрет, текст или шестнадцатеричный код — не загружается, а файл, который сохраняет кнопка «Скачать», создаётся в вашем браузере из байтов, которые уже там.
Похожие инструменты
- Base64
Base64 записывает каждые три байта четырьмя символами, а Base32 — каждые пять восемью, и регистр буквы в Base64 входит в её значение: abc там — YWJj, а YWJJ там читается как abI.
- Отладчик кодов TOTP / 2FA
Генерируйте и отлаживайте коды 2FA с полным выводом.
- Кодировщик / декодировщик URL
Процентное кодирование значения или целого URL — и обратно.
- Экранирование строк JSON
Экранируйте текст для строки JSON или прочитайте экранированный обратно.