Почему для кодировки base64 требуется заполнить, если входная длина не делится на 3?

Какова цель заполнения в кодировке base64. Ниже приводится выдержка из википедии:

"Выделен дополнительный символ пэда, который может использоваться для принудительного преобразования кодированного вывода в целое число, кратное 4 символам (или, что эквивалентно, когда некодированный двоичный текст не является кратным 3 байтам), тогда эти отступы должны быть отброшены при декодировании, но все же позволяет вычислять эффективную длину некодированного текста, когда его входная двоичная длина не будет не кратной 3 байтам (последний символ без клавиатуры обычно кодируется так, что последний 6-битовый блок, который он представляет будет иметь нулевое значение на младших значащих битах, в конце закодированного потока может появиться не более двух символов пэда).

Я написал программу, которая могла бы base64 кодировать любую строку и декодировать любую кодированную base64 строку. Какая проблема решена?

Ответ 1

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

Однако заполнение полезно в ситуациях, когда закодированные строки base64 объединены таким образом, что длины отдельных последовательностей теряются, например, в очень простом сетевом протоколе.

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

Изменить: Иллюстрация

Предположим, что у нас есть программа, которая кодирует слова base64, объединяет их и отправляет по сети. Он кодирует "I", "AM" и "TJM", сэндвичит результаты вместе без заполнения и передает их.

  • I кодируется до SQ (SQ== с заполнением)
  • AM кодируется до QU0 (QU0= с заполнением)
  • TJM кодируется до VEpN (VEpN с заполнением)

Таким образом, передаваемые данные SQQU0VEpN. Приемник base64-декодирует это как I\x04\x14\xd1Q) вместо предполагаемого IAMTJM. Результат - бессмыслица, потому что отправитель разрушил информацию о том, где заканчивается каждое слово в закодированной последовательности. Если отправитель отправил SQ==QU0=VEpN вместо этого, приемник мог бы декодировать это как три отдельные последовательности base64, которые могли бы конкатенировать, чтобы дать IAMTJM.

Зачем беспокоиться о заполнении?

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

Это отличная идея, если мы знаем длину данных, которые мы кодируем, прежде чем мы начнем ее кодировать. Но что, если вместо слов мы кодировали фрагменты видео с живой камеры? Мы могли бы не знать длину каждого фрагмента заранее.

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

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

Ответ 2

Что такое символы заполнения?

Заполняющие символы помогают удовлетворить требованиям длины и не имеют никакого значения.

Десятичный пример заполнения: Учитывая произвольное требование, все строки имеют длину 8 символов, число 640 может удовлетворять этому требованию, используя предыдущие 0 в качестве отступов, поскольку они не имеют значения "00000640".

Двоичное кодирование

Байт-парадигма: Байт - это стандартная единица измерения де-факто, и любая схема кодирования должна относиться к байтам.

Base256 точно соответствует этой парадигме. Один байт равен одному символу в базе 256.

Base16, шестнадцатеричный или шестнадцатеричный, использует 4 бита для каждого символа. Один байт может представлять два символа base16.

Base64 не вписывается равномерно в байтовую парадигму, в отличие от base256 и base16. Все символы base64 могут быть представлены в 6 бит, 2 бита, не превышающих полный байт.

Мы можем представить кодировку base64 по сравнению с байтовой парадигмой как фракцию: 6 бит на символ по 8 бит на байт. Уменьшенная эта фракция составляет 3 байта на 4 символа.

Это отношение, 3 байта для каждых 4 символов base64, является правилом, которое мы хотим соблюдать при кодировании base64. Кодирование Base64 может только обещать даже измерение с помощью 3 байтовых пакетов, в отличие от base16 и base256, где каждый байт может стоять на нем.

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

Примеры

Вот пример формы RFC 4648 (http://tools.ietf.org/html/rfc4648#section-8)

Каждый символ внутри функции "BASE64" использует один байт (base256). Затем мы переведем это на base64.

BASE64("")       = ""           (No bytes used. 0%3=0.)
BASE64("f")      = "Zg=="       (One byte used. 1%3=1.)
BASE64("fo")     = "Zm8="       (Two bytes. 2%3=2.)
BASE64("foo")    = "Zm9v"       (Three bytes. 3%3=0.)
BASE64("foob")   = "Zm9vYg=="   (Four bytes. 4%3=1.)
BASE64("fooba")  = "Zm9vYmE="   (Five bytes. 5%3=2.)
BASE64("foobar") = "Zm9vYmFy"   (Six bytes. 6%3=0.)

Здесь кодер, с которым вы можете играть: http://www.motobit.com/util/base64-decoder-encoder.asp

Ответ 3

Это только моя теория, и я не могу предоставить какие-либо источники, но я думаю, что прописные символы служат только для того, чтобы сделать некоторые реализации алгоритма декодирования более простым. В частности, если алгоритм помещает закодированную строку в нечто вроде int[], тогда конечное значение будет иногда слишком длинным.

Если заполнение уже присутствует на входе, ничего больше не нужно делать - алгоритм может просто читать и декодировать ввод.

Если алгоритму не разрешено принимать заполнение, которое должно присутствовать, и оно использует int[] -подобную структуру данных, тогда ему необходимо вручную поместить окончательное целое число перед декодированием или сделать некоторую дополнительную учетную запись на исходном оригинале длина.

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