Кодирование, экранирование и хеширование
Base64 не является шифрованием, btoa не является UTF-8, encodeURI не является encodeURIComponent, а SHA-256 не является хэшем пароля. Вот что на самом деле делает каждый из них, а также конкретные ошибки, которые следуют из предположения об обратном.
Кодирование — это не шифрование и не сжатие.
Кодирование меняет способ записи данных. Шифрование меняется, кто может его прочитать. Сжатие меняет объем занимаемого места. Это три разные задачи, и base64 выполняет только первую — плохо, если вы надеялись на любую из двух других.
Base64 принимает по три байта за раз и переписывает их как четыре символа, взятые из алфавита символов 64. Четыре символа, содержащие три байта, означают, что выходные данные всегда примерно на 33% больше, чем входные, плюс заполнение. Он существует потому, что большая часть инфраструктуры — заголовки электронных писем, заголовки HTTP, строковые значения JSON, URL-адреса, атрибуты XML — была разработана для текста и искажает или отклоняет произвольные байты. Base64 — это адаптер, который позволяет передавать байты через текстовый канал.
Любой может мгновенно отменить это, без ключа, потому что ключа нет. Если вы используете пароль base64, вы опубликовали пароль в слегка неудобном формате. Это имеет значение, поскольку для человеческого глаза вывод base64 выглядит зашифрованным, а это именно то свойство, которое заставляет людей доверять ему в том, что он не может сделать.
Почему btoa() ломается и два разных способа его поломки
Браузер предоставляет вам btoa() и atob(), и они старше современных текстовых API. btoa определяется как «двоичные строки»: строки, в которых каждая единица кода представляет собой один байт, от 0 до 255. Текст не тот.
Первый провал громкий. Вызовите btoa("世界") и получите ошибку InvalidCharacterError, поскольку U+4E16 не помещается в байт. Громкие неудачи — это хорошо: вы сразу замечаете их и начинаете искать решение.
Вторая неудача молчит, и именно она доходит до производства. Символ é — это U+00E9, который помещается в байт. Таким образом, btoa("café") успешно возвращает результат, кодируя é как одиночный байт 0xE9. Но é в UTF-8 — это два байта, 0xC3 0xA9. Только что созданный вами base64 декодирует в любой другой системе на земле что-то, что не является вашим текстом. Через несколько недель вы узнаете, когда имя в базе данных превратилось в замещающий символ.
Исправление состоит в том, чтобы перестать обрабатывать текст как байты и преобразовать его явно. TextEncoder создает байты UTF-8; закодируйте их. TextDecoder превращает байты обратно в текст, а его построение с помощью { Fatal: true } заставляет его выдавать недопустимые последовательности вместо спокойной замены U+FFFD, поэтому декодирование, которое не может быть правильным, терпит неудачу, а не возвращает правдоподобную ерунду. Именно этот конвейер использует этот инструментарий, поэтому смайлики, сочетающие метки и сценарии с письмом справа налево, точно туда и обратно.
- Преобразуйте текст в байты с помощью TextEncoder — никогда не индексируйте строку.
- Закодируйте байты в base64.
- Чтобы отменить: декодируйте base64 в байты, затем декодируйте байты как UTF-8 с фатальным: true.
- Если шаг UTF-8 завершается неудачно, полезные данные являются двоичными, а не текстовыми. Покажите это как проклятие, а не притворяйтесь.
base64 против base64url и вопрос о заполнении
Стандартный base64 использует + и / в качестве последних двух символов. Оба имеют смысл в URL-адресах: + можно читать как закодированный пробел в строках запроса, а / является разделителем пути. Итак, RFC 4648 определяет второй алфавит, base64url, который заменяет - и _. Его используют JWT, а также большинство форматов токенов и многие API.
Заполнение — это другая переменная. Стандартный base64 дополняется знаком =, поэтому длина вывода всегда кратна четырем. base64url обычно удаляет заполнение, потому что = сам по себе является неудобным символом в URL, и длину можно восстановить арифметически. Декодер, настаивающий на заполнении, отклонит абсолютно допустимые сегменты JWT.
Практический совет: ваш декодер должен принимать оба алфавита и допускать отсутствие заполнения, поскольку вы редко контролируете то, что вам вручают. Ваш кодер должен четко указывать, что он отправляет, потому что получателя, вероятно, это волнует. Утилита base64 делает именно это — она принимает все разумное и позволяет вам выбирать именно то, что она производит.
encodeURI и encodeURIComponent: разница в одном предложении
Оба процента кодируют с использованием UTF-8. Они отличаются только тем, какие символы они оставляют в покое, и в этой разнице вся история: encodeURIComponent экранирует зарезервированные разделители, а encodeURI — нет.
Зарезервированные разделители — это символы, которые определяют структуру URL: : / ? # [ ] @ ! $ & ' ( ) * + , ; =. encodeURI предполагает, что вы передали ему URL, который уже правильно структурирован и должен оставаться таким, поэтому он сохраняет их — он не превратит https:// в https%3A%2F%2F. encodeURIComponent предполагает, что вы передали ему один фрагмент, который будет помещен в слот, поэтому он избегает их, гарантируя, что этот фрагмент не сможет вырваться из слота.
Возникающая при этом ошибка является полностью механической. Возьмите значение поиска a&b=c. Закодируйте его с помощью encodeURI и добавьте как ?q=a&b=c, и вы молча создали два параметра: q теперь просто «a», и появился случайный b=c. Закодируйте его с помощью encodeURIComponent, и вы получите ?q=a%26b%3Dc, один параметр, правильное значение. Тот же класс ошибок позволяет созданному значению вставлять параметры в URL, который строит ваш код — вот почему «использовать форму компонента для значений» — это правило безопасности, а не только правило корректности.
Кодирование формы — третье правило, похожее на второе. application/x-www-form-urlencoded записывает пробел как +, а не %20. Если вы декодируете тело формы с помощью обычного decodeURIComponent, каждый знак плюса в данных становится пробелом. Каждое окно поиска, которое когда-либо искажало «C++» в «C», является этой ошибкой.
Объекты HTML и почему их декодирование с помощью InnerHTML — плохая привычка
Экранирование для HTML является узким и хорошо понятным: & становится &, < становится <, > становится >, а внутренние значения атрибутов " и ' тоже требуют экранирования. Пять символов. Экранирование большего количества символов — превращение каждой буквы с диакритическим знаком в именованный объект — было обходным решением в те времена, когда кодировки символов были неопределенными, и теперь это необязательный стиль. а не безопасность.
Декодирование – вот где живет вредная привычка. Однострочный трюк, который появляется в каждом ответе, заключается в том, чтобы присвоить строку внутреннему HTML-коду отдельного элемента и прочитать его textContent. Это работает, и это плохая идея. Вы передали ненадежные входные данные анализатору HTML, который создает из них настоящие узлы DOM. <img src=x onerror=...> в этой строке становится фактическим элементом изображения с прикрепленным реальным обработчиком ошибок; если это поддерево когда-либо вставлено в документ, оно запускается. Он также незаметно уничтожает ваши данные: теги во входных данных исчезают, а не повторяются, потому что синтаксический анализатор интерпретирует их как разметку, а не как текст.
Правильное декодирование сущностей вообще не требует синтаксического анализатора: сопоставьте ссылку, найдите имя в таблице или выполните арифметические действия для числовой ссылки. Это несколько десятков строк, он ничего не может выполнить и работает добросовестно. Этот инструментарий делает это именно таким образом, поэтому при вставке тега сценария в декодер объектов вы увидите тег сценария.
Выбор хеша и три вопроса, которые его решают
Криптографический хеш превращает любые входные данные в дайджест фиксированной длины, так что найти два входных данных с одним и тем же дайджестом становится невозможным. Именно это свойство позволяет дайджесту заменять данные — в подписи, проверке целостности или адресе контента.
Первый вопрос: вы защищаете от несчастного случая или от противника? Контрольная сумма, защищающая от поврежденной загрузки, должна улавливать только случайные изменения; CRC32 в порядке. Дайджест, от столкновения которого злоумышленник может получить выгоду, нуждается в хэше, который все еще существует. Именно это различие объясняет, почему SHA-1 не просто «старый».
SHA-1 конкретно сломан. В 2017 работа SHAttered создала два разных файла PDF с одним и тем же дайджестом SHA-1. В 2020 «SHA-1 is a Shambles» продемонстрировано столкновение выбранных префиксов — более сильный и гораздо более опасный вариант, поскольку он позволяет злоумышленнику столкнуться с двумя существенно разными документами, а не с двумя тщательно созданными каплями. Если безопасность системы опирается на SHA-1 устойчивость к коллизиям, эта безопасность исчезает. SHA-1 остается в этом наборе инструментов, поскольку идентификаторы объектов git и длинный хвост устаревших сигнатур API все еще используют его, и вам необходимо иметь возможность воспроизводить эти значения. Воспроизведение значения — это не то же самое, что полагаться на него.
Второй вопрос: является ли ввод паролем? Если да, то ни один из этих ответов не является ответом. SHA-256 спроектирован так, чтобы быть быстрым, а быстрота совершенно не подходит для паролей: это означает, что злоумышленник с вашей базой данных может делать миллиарды попыток в секунду. Для паролей требуется намеренно медленная, требовательная к памяти функция с солью для каждого пользователя — Argon2id, scrypt или bcrypt. Это не нюанс; использование SHA-256 для паролей — самая распространенная серьезная ошибка хеширования.
Третий вопрос: нужен ли дайджест с ключами? Если вы аутентифицируете сообщение, а не снимаете его отпечатки, вам нужен HMAC, а не голый хэш. Объединение секрета и его хеширование — это классическая автоцель против атак с увеличением длины; HMAC существует потому, что эту конструкцию сложнее сделать правильно, чем кажется.
Для всего остального — снятия отпечатков пальцев файла, адреса содержимого, атрибута целостности — SHA-256 является разумным значением по умолчанию, а SHA-512 часто работает быстрее на 64-битном оборудовании, обеспечивая при этом более широкий обзор.
Почему хеши здесь берутся из браузера
Дайджесты в этом наборе инструментов рассчитываются с помощью SubtleCrypto, собственной реализации Web Crypto в браузере, а не с помощью JavaScript, поставляемого с этого сайта. Это осознанный выбор: реализация браузера проверяется, поддерживается и обычно работает как оптимизированный собственный код. Написанный от руки SHA-256 в пакете страниц — это больше кода, которому можно доверять, но без всякой пользы.
Это имеет одно видимое последствие. Web Crypto доступен только в безопасном контексте, то есть https:// или localhost. Откройте эту страницу поверх обычного HTTP по адресу LAN, и crypto.subtle будет неопределенным, поэтому хеш-утилита сообщит вам об этом прямо, а не будет молча терпеть неудачу или заменять что-то более слабое.
Те же рассуждения управляют генератором UUID. crypto.randomUUID() также предназначен только для безопасного контекста, поэтому, когда он недоступен, набор инструментов возвращается к crypto.getRandomValues() — который по-прежнему остается криптографически безопасным источником — и сам устанавливает биты версии и варианта. Чего он никогда не сделает, так это вернется к Math.random(). Это быстрый некриптографический PRNG, внутреннее состояние которого можно восстановить по короткому периоду его выходных данных, а идентификаторы имеют досадную привычку превращаться в сеансовые ключи и ссылки для сброса пароля. Если безопасный источник не существует, этот инструмент ничего не генерирует и сообщает, почему.
Что происходит с тем, что вы вставляете
- Каждое преобразование, хеширование, декодирование и различие выполняются на вкладке вашего браузера. Никакие входные данные не загружаются, не регистрируются и не сохраняются на сервере, поскольку после загрузки страницы сервер не задействован.
- Хэши берутся из собственной реализации Web Crypto в браузере, а UUID — из криптографически безопасного генератора случайных чисел. Ни один из них не предполагает сетевой вызов.
- Ничего из того, что вы вводите, не записывается в локальное хранилище или файл cookie. Перезагрузка страницы удаляет ее; закрытие вкладки отменяет ее.
- Аналитика в масштабе всего сайта выполняется только на настроенном каноническом рабочем хосте и описана в Политике конфиденциальности; локальные и предварительные хосты отказываются от этого. Вставленные значения, токены, URL-адреса и содержимое файлов исключаются из собственных аналитических событий ToolAcre. Реклама отключена в текущей конфигурации.
- Тем не менее: ключ JWT или API — это действующие учетные данные. Безопасная привычка — никогда не вставлять его на веб-страницу, которую вы не писали, какими бы заслуживающими доверия ни были его утверждения, включая эту.
Вопросы
Является ли base64 способом скрыть данные?
Нет. Это обратимое текстовое представление без ключа, которое любой может декодировать за доли секунды. Это позволяет данным сохраняться только в текстовых каналах; это не делает это секретом. Все, что действительно конфиденциально, требует шифрования, и зашифрованный результат часто затем кодируется в формате Base64 для транспортировки, что и является источником путаницы.
Почему мой base64 длиннее ввода?
Поскольку четыре выходных символа содержат три входных байта, размер выходного файла составляет примерно 4/3 плюс до двух символов заполнения. Это особенность формата. Если размер имеет значение, сжимайте перед кодированием, а не после него, поскольку выходные данные base64 сжимаются плохо.
Какую функцию кодирования URL мне следует использовать?
Используйте encodeURIComponent для любой отдельной части, которую вы вставляете в URL: значение запроса, сегмент пути, фрагмент. Используйте encodeURI только в том случае, если у вас есть целый, уже структурированный URL, который содержит только пробелы или не ASCII. Если вы создаете строку запроса, отдайте предпочтение URLSearchParams, который применяет правильное правило и обрабатывает разницу «пробел как плюс».
Почему мой декодер выдает «URI искаженный»?
Потому что за % во входных данных не следуют две шестнадцатеричные цифры. Обычно текст содержит буквальный знак процента — «50% off» — который никогда не кодировался. Буквальный процент должен быть записан как %25. Утилита URL сообщает здесь точное положение побега, а не просто отказывается.
Могу ли я использовать SHA-256 для хранения паролей?
Нет. SHA-256 по своей конструкции быстрый, а это означает, что злоумышленник, похитивший вашу базу данных, может проверять миллиарды паролей-кандидатов в секунду на обычном оборудовании. Для паролей нужна медленная, требовательная к памяти функция с солью: Argon2id, scrypt или bcrypt. Это самая распространенная серьезная ошибка в этой области.
Почему SHA-1 все еще здесь, если он сломан?
Потому что вам все равно нужно воспроизвести уже существующие значения SHA-1: идентификаторы объектов git, старые отпечатки сертификатов TLS, устаревшие подписи запросов API. Возможность вычислить значение совместимости отличается от того, чтобы полагаться на него в целях безопасности. Каждое место SHA-1 в этом наборе инструментов помечено соответствующим образом.
Почему два инструмента дают разные хеши для одного и того же текста?
Почти всегда разница в байтах, а не в алгоритме. Обычными виновниками являются конечная новая строка (файл заканчивается ею; текстовое поле может не быть), другая кодировка текста или CRLF вместо окончания строки LF. Этот инструмент хеширует UTF-8 байтов именно того, что вы набрали, и показывает количество байтов, что обычно делает несоответствие очевидным.
Ограничения
- Именованная таблица сущностей HTML охватывает практическое подмножество — важные для разметки символы, типографику, валюту, стрелки, математические символы, греческий и латинский язык — 1 — а не все именованные ссылки 2,231 HTML5. Нераспознанные имена сообщаются и оставляются точно так, как они написаны, а не угадываются.
- Для декодирования объекта требуется завершающая точка с запятой. HTML5 допускает несколько устаревших ссылок без них, но их правильное декодирование зависит от окружающего контекста разметки, которого нет в автономном текстовом инструменте.
- Для хеширования и генерации UUID требуется безопасный контекст (https:// или localhost), поскольку в противном случае Web Crypto не раскрывается. Инструмент сообщает об этом, а не заменяет более слабую реализацию.
- Доступны только SHA-1, SHA-256, SHA-384 и SHA-512, потому что это то, что реализует SubtleCrypto. MD5 отсутствует как по выбору, так и по необходимости.
- Здесь нет ни HMAC, ни получения ключа, ни шифрования. Для них требуется управление ключами, а это не то, что должна обрабатывать страница, которую вы нашли в Интернете.
- Все ограничено памятью вашего устройства, поскольку все работает в одной вкладке браузера. Входные данные ограничены — несколько мегабайт на утилиту — и инструмент вместо зависания отказывается от работы с большими объемами.