проблемы кодирования / декодирования / распаковки ('m') ruby ​​base64 - PullRequest
4 голосов
/ 15 августа 2011

Странное столкновение с рубином:

ruby-1.9.2-p180 :618 > s = "a8dnsjg8aiw8jq".ljust(16,'=')
 => "a8dnsjg8aiw8jq==" 
ruby-1.9.2-p180 :619 > s.size
 => 16 

ruby-1.9.2-p180 :620 > s.unpack('m0')
ArgumentError: invalid base64
    from (irb):631:in `unpack'

ruby-1.9.2-p180 :621 > s.unpack('m')
 => ["k\xC7g\xB28<j,<\x8E"] 
ruby-1.9.2-p180 :622 > s.unpack('m').first.size
 => 10

ruby-1.9.2-p180 :623 > s.unpack('m').pack('m')
 => "a8dnsjg8aiw8jg==\n" 
ruby-1.9.2-p180 :624 > s.unpack('m').pack('m') == s
 => false 

Есть идеи, почему это не симметрично !? И почему 'm0' (decode64_strict) вообще не работает? Входная строка дополняется кратным 4 символам в алфавите base64. Здесь это 14 х 6 бит = 84 бита, что составляет 10 1/2 8-битных байтов, то есть 11 байтов. Но декодированная строка, кажется, отбрасывает последний кусочек?

Я что-то упускаю из виду или это ошибка? Обходной путь? ср http://www.ietf.org/rfc/rfc4648.txt

Ответы [ 4 ]

3 голосов
/ 15 августа 2011

Симметрии нет, потому что Base64 не является взаимно-однозначным отображением для дополненных строк. Давайте начнем с фактического декодированного контента. Если вы просматриваете свою расшифрованную строку в шестнадцатеричном формате (например, s.unpack('H*'), это будет так:

6B C7 67 | B2 38 3C | 6A 2C 3C | 8E

Я добавил границы для каждого входного блока в алгоритм Base64: он принимает 3 октета ввода и возвращает 4 символа вывода. Таким образом, наш последний блок содержит только один входной октет, поэтому результатом будет 4 символа, которые заканчиваются на "==" в соответствии со стандартом.

Посмотрим, какой будет каноническая кодировка этого последнего блока. В двоичном представлении 8E равно 10001110. RFC говорит нам заполнить недостающие биты нулями, пока мы не достигнем требуемых 24 битов:

100011 100000 000000 000000

Я сделал группы из 6 битов, потому что это то, что нам нужно, чтобы получить соответствующие символы из алфавита Base64. Первая группа (100011) переводится в десятичное число 35 и, таким образом, представляет собой j в алфавите Base64. Второе (100000) - это 32 знака после запятой и, следовательно, 'g' Два оставшихся символа должны быть дополнены как "==" в соответствии с правилами. Таким образом, каноническая кодировка

jg==

Если вы посмотрите на jq == сейчас, в двоичном виде это будет

100011 101010 000000 000000

Так что разница во второй группе. Но так как мы уже знаем, что нам интересны только первые 8 бит («==» говорит нам об этом -> мы получим только один декодированный октет из этих четырех символов), мы на самом деле заботимся только о первых двух битах вторая группа, потому что 6 битов группы 1 и 2 первых бита группы 2 образуют наш декодированный октет. 100011 10 вместе образуют наше начальное 8E значение байта. Остальные 16 бит не имеют к нам отношения и поэтому могут быть отброшены.

Это также означает, что понятие «строгого» кодирования Base64 имеет смысл: нестрогое декодирование отбрасывает весь мусор в конце, тогда как строгое декодирование проверяет, чтобы оставшиеся биты были равны нулю в последней группе из 6. Вот почему ваше неканоническое кодирование будет отклонено строгими правилами декодирования.

2 голосов
/ 15 августа 2011

Из раздел 3.5 Каноническое кодирование из RFC4648 :

Например, если для кодировки base 64 используется только один октет, тогда используются все шесть битов первого символа, но только первый используются два бита следующего символа. Эти биты заполнения ДОЛЖНЫ быть установлены в ноль с помощью соответствующих энкодеров ...

и

В некоторых средах изменение является критическим и, следовательно, декодеры МОГУТ выбрать отклонить кодировку, если биты пэда не имеют был установлен на ноль.

Ваши последние четыре байта (jq==) декодируются в следующие двоичные значения:

100011 101010
------ --****

Подчеркнутые биты используются для формирования последнего закодированного байта (шестнадцатеричный код 8E). Остальные биты (со звездочками под ними) должны быть равны нулю (что будет закодировано jg==, а не jq==).

Распаковка m прощает биты заполнения, которые должны быть равны нулю, но не равны. Распаковка m0 не так проста, как это разрешено (см. «МОЖЕТ» в цитируемом бите из RFC). Упаковка распакованного результата не является симметричной, потому что ваше закодированное значение неканоническое, но метод pack производит каноническое кодирование (биты заполнения равны нулю).

2 голосов
/ 15 августа 2011

RFC, который вы связали, ясно говорит о том, что последний квадрат формы xx== соответствует одному октету входной последовательности.Вы не можете сделать 16 бит информации (два произвольных октета) из 12, поэтому округление здесь недопустимо.

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

   j      q      =      =
|100011|101010|000000|000000|
|10001110|10100000|00000000|
          ^^^
0 голосов
/ 16 августа 2011

Спасибо за хорошие объяснения на b64.Я проголосовал за всех вас и принял ответ @ emboss.

Однако это не тот ответ, который я искал.Чтобы лучше сформулировать вопрос, это будет:

Как заполнить строку из символов b64, чтобы ее можно было декодировать в заполненные нулями 8-битные байты с помощью unpack ('m0')?

Из ваших объяснений теперь я вижу, что это будет работать для наших целей:

ruby-1.9.2-p180 :858 >   s = "a8dnsjg8aiw8jq".ljust(16,'A')
 => "a8dnsjg8aiw8jqAA" 
ruby-1.9.2-p180 :859 > s.unpack('m0')
 => ["k\xC7g\xB28<j,<\x8E\xA0\x00"] 
ruby-1.9.2-p180 :861 > s.unpack('m0').pack('m0') == s
 => true 

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

...