Кодирование, хеширование и шифрование — три разные вещи
Почти все недоразумения вокруг этих инструментов возникают оттого, что эти понятия считают взаимозаменяемыми, поэтому их стоит как следует разделить.
Кодирование обратимо, и секрета в нём нет. Base64 нужен, чтобы передавать двоичные данные по каналам, принимающим только текст, — во вложениях писем, data URI, строках JSON: каждые три байта он представляет четырьмя символами, поэтому результат всегда примерно на треть больше. Ключа нет. Любой, кто видит строку, декодирует её за один шаг, в том числе на этом сайте. Base64 — это не защита, не сколько-нибудь серьёзная обфускация и не способ что-то спрятать от того, кто посмотрит. Процентное кодирование — то же самое для URL, и здесь живёт другая частая ошибка: закодировать одно значение для строки запроса — это не то же самое, что привести в порядок уже собранный URL целиком, ведь только в первом случае экранируются &, =, ? и /, которые задают структуру URL. Ошибётесь — и «Smith & Sons» придёт на сервер двумя параметрами вместо одного значения.
Хеширование одностороннее, и ключа в нём тоже нет. Хеш превращает любые входные данные в отпечаток фиксированной длины, и обратного пути нет — не потому, что он зашифрован, а потому, что информация утрачена. Можно лишь подбирать входные данные и сравнивать, и именно поэтому MD5, SHA-1 и SHA-256 не годятся для хранения паролей: они быстрые, видеокарта вычисляет миллиарды таких хешей в секунду, и украденную таблицу взламывают с той же скоростью. Для паролей нужна намеренно медленная функция с солью — bcrypt, scrypt или Argon2. Кроме того, MD5 и SHA-1 взломаны и по другой причине: теперь можно намеренно создать два разных файла с одинаковым хешем, поэтому ни один из них не докажет, что файл — именно тот, который вы ждали, хотя для обнаружения случайно повреждённой загрузки оба по-прежнему подходят.
Шифрование — единственное из трёх, где есть ключ, и ни один из этих инструментов им не занимается. Это не упущение. Правильное шифрование требует управления ключами, а веб-страница — неподходящее место для ключа.
JWT подписан, а не зашифрован, и на этом постоянно попадаются. Заголовок и полезная нагрузка токена закодированы в Base64URL — их может прочитать любой, у кого есть токен, ключ не нужен. Подпись не даёт изменить токен, но никак не мешает его прочитать, поэтому ничему конфиденциальному в полезной нагрузке не место. Отсюда следует, что декодирование токена ровным счётом ничего о нём не доказывает: кто угодно может написать любые утверждения и закодировать их в Base64. Проверка означает повторное вычисление подписи с ключом издателя — это делает ваш сервер, и сайт никогда не должен просить этот ключ. Поэтому наш декодер не создаёт видимости проверки и отмечает признаки проблем: истёкший токен, токен без срока действия вообще и алгоритм «none», который представляет собой задокументированный обход аутентификации, а не курьёз.
В JSON есть предел точности чисел, в который молча упирается большинство форматтеров. Сам JSON не ограничивает размер числа, но число в JavaScript — это 64-битное число с плавающей точкой, поэтому целые числа остаются точными только до 9 007 199 254 740 991. ID поста в Twitter, snowflake в Discord, 64-битный ключ базы данных или номер банковского счёта больше этого, и если пропустить их через обычный однострочный форматтер — parse, затем stringify, — последние цифры изменятся. Результат по-прежнему выглядит правдоподобным числом, и именно этим он опасен: 7205759403792793600 незаметно превращается в 7205759403792793000. Наши инструменты для JSON записывают обратно ровно те цифры, которые вы вставили, и сообщают, сколько чисел им пришлось защитить.
Версия UUID говорит о том, как он создан, а не о том, что он гарантирует. Никто не регистрирует UUID и не следит за уникальностью: уникальность вероятностная, а версия лишь говорит, откуда взялись биты. Версия 4 — это 122 бита случайности из криптографического генератора браузера, поэтому её невозможно угадать, и она подходит для ссылок сброса пароля, кодов приглашения и всего секретного — но не для ключа базы данных, потому что случайные ключи разбрасываются по отсортированному индексу и фрагментируют его. Версия 7 начинается с 48-битной метки времени в миллисекундах, поэтому ключи добавляются в конец индекса, как автоинкрементное целое, и при этом остаются глобально уникальными. Цена в том, что UUID v7 открыто сообщает любому, у кого он есть, когда он был создан — с точностью до миллисекунды.
Когда браузер действительно не подходит
Ни один из этих тринадцати инструментов ничего никуда не отправляет. Запросы не выполняются, ничего не записывается в журнал, и страницы работают с отключённой сетью — именно поэтому их можно использовать для ответа API с данными клиентов или токена, который вы никому не отправили бы по почте. Это честный довод за них. А вот честный довод против.
Вставлять действующий секрет из продакшена в веб-страницу — привычка, которой лучше не иметь, в том числе и с этой страницей. Наше заявление о конфиденциальности правдиво, но защищает вас не заявление, а поведение кода, и единственно разумный подход к любой веб-странице — проверять, а не доверять. Проверка ничего не стоит: отключитесь от интернета после загрузки страницы и убедитесь, что инструменты продолжают работать. Инструмент, которому нужен сервер, остановился бы. А если учётные данные ещё действуют и вы уже вставили их туда, где не можете ничего проверить, правильный шаг — заменить их, а не рассуждать. Для токена, который действует прямо сейчас, декодировать его локально — встроенной функцией Base64 вашего языка или двумя строками в терминале — лучше, чем на любом сайте, включая наш.
Проверка по схеме. Наши инструменты для JSON и XML проверяют, что документ корректно сформирован, а это другой вопрос, чем соответствие схеме. Проверка по JSON Schema или XSD требует загрузить и применить файл схемы, то есть сетевой запрос, который эти страницы намеренно никогда не делают. Для JSON Schema используйте ajv, для XSD и DTD — xmllint: оба бесплатны и справляются с этим лучше, чем могла бы веб-страница.
Всё действительно большое или потоковое. Документы целиком хранятся в памяти, а при форматировании иногда и в двух копиях, поэтому потолок — возможности вкладки. Несколько мегабайт обрабатываются мгновенно, несколько сотен — нет. jq потоково обрабатывает JSON-файл на несколько гигабайт, который не откроет ни один браузер, а ripgrep ищет по всему репозиторию быстрее, чем вы вставите один файл. У хеширования то же ограничение ещё жёстче: в криптографии браузера нет пошагового вычисления хеша, поэтому весь файл должен целиком поместиться в память — образ DVD не пройдёт, а sha256sum, shasum или certutil обработают его потоком и даже не заметят.
Автоматизация и CI. Эти инструменты работают, когда человек нажимает кнопку. Форматирование всех файлов репозитория, генерация десяти тысяч UUID для тестовых данных или проверка JSON в конвейере — задачи для скрипта: jq, xmllint, uuidgen, openssl и команда base64 уже установлены на большинстве компьютеров.
Проверка всего подписанного. Проверка подписей, цепочки сертификатов и всё, что требует закрытого ключа, должно выполняться на вашем сервере с помощью поддерживаемой библиотеки. Эта страница покажет, что утверждает токен, но никогда не скажет, что токен подлинный, — и не доверяйте сайту, который утверждает, что может сделать это без вашего ключа.
Как выбрать между похожими инструментами
Форматировать JSON или Проверить JSON? Парсер один, вопросы разные. Форматирование нужно, чтобы читать и менять вид документа, который уже парсится: расставить отступы, минифицировать, сохранить большие числа. Проверка — для документа, который не парсится: она показывает строку, столбец, проблемный символ с отметкой под ним и то, какая из четырёх обычных причин сработала. Если перед вами «Unexpected token», вам нужна проверка. То же разделение действует для «Форматировать XML» и «Проверить XML».
Декодировать Base64 или Декодировать JWT? JWT состоит из трёх частей в Base64URL, так что универсальный декодер покажет вам эти части. Страница JWT сама разделяет их, форматирует JSON, превращает утверждения о сроке действия, времени выпуска и «не ранее» из меток времени Unix в настоящие даты в вашем часовом поясе, подписывает зарегистрированные утверждения и предупреждает об истёкшем сроке и алгоритме «none». Для блока Base64 используйте универсальный декодер, для токена — страницу JWT.
Кодировать в Base64 или Кодировать URL? Разные задачи, и обе дают «безопасный» текст. Base64 превращает произвольные байты в текст ценой увеличения размера на 33 процента, а стандартный Base64 в URL совсем не безопасен: его +, / и = там имеют особый смысл, поэтому и существует Base64URL. Процентное кодирование оставляет читаемый текст читаемым и экранирует только то, что сломало бы URL, — это то, что нужно для поискового запроса, адреса электронной почты или цели перенаправления. Если приходится кодировать целый файл для строки запроса, это обычно признак того, что он должен был быть телом запроса.
Генератор хешей или Генератор UUID? Хеш выводится из входных данных, поэтому одни и те же данные всегда дают одно и то же значение — это и делает его отпечатком, который подходит для проверки загрузки или поиска одинакового содержимого. UUID — это свежая случайность, ни с чем не связанная, поэтому он никогда не повторяется. Если нужен одинаковый идентификатор для одинакового содержимого — хешируйте. Если нужен новый идентификатор — сгенерируйте UUID.
Генератор QR-кодов или Генератор штрихкодов? QR-код — это двумерная сетка, которая хранит произвольный текст: URL, данные Wi-Fi, номер телефона, — и считывается камерой телефона. Страница штрихкодов создаёт одномерные коды для торговли и логистики: EAN-13, EAN-8, UPC-A, ITF-14, Code 128 и Code 39. Контрольная цифра для розничных кодов вычисляется, а неверный или короткий номер отклоняется, а не дополняется молча до правильного штрихкода другого товара.