Допустим, у меня есть следующее:

image_data = """iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="""

Это просто точечное изображение (из https://en.wikipedia.org/wiki/Data_URI_scheme ) . Но я не знаю, изображение это или текст и т. Д. Можно ли понять, что это за кодировка? Я пробую это на Python, но это также общий вопрос. Так что любые идеи в обоих случаях приветствуются.

5
george 15 Дек 2015 в 14:17

3 ответа

Лучший ответ

Вы не можете, по крайней мере, без декодирования, потому что байты, которые помогают идентифицировать тип файла, распределены по символам base64, которые не выровнены напрямую с целыми байтами. Каждый символ кодирует 6 битов , что означает, что на каждые 4 символа кодируется 3 байта.

Для идентификации типа файла требуется доступ к этим байтам с разными размерами блоков. Изображение JPEG, например, может быть идентифицировано из байтов FF D8 или FF D9, но это два байта; следующий третий байт также должен быть закодирован как часть 4-символьного блока.

Что вы можете сделать, так это декодировать достаточно строки base64, чтобы выполнить дактилоскопию вашего типа файла. Таким образом, вы можете декодировать первые 4 символа, чтобы получить 3 байта, а затем использовать первые два, чтобы увидеть, является ли объект изображением JPEG. Большое количество форматов файлов может быть идентифицировано только из первой или последней серии байтов (изображение PNG может быть идентифицировано первыми 8 байтами, GIF - первыми 6 и т. Д.). Декодирование только тех байтов из строки base64 тривиально.

Ваш образец - изображение PNG; Вы можете проверить типы изображений с помощью imghdr модуля:

>>> import imghdr
>>> image_data = """iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="""
>>> sample = image_data[:44].decode('base64')  # 33 bytes / 3 times 4 is 44 base64 chars
>>> for tf in imghdr.tests:
...     res = tf(sample, None)
...     if res:
...         break
...
>>> print res
png

Я использовал только первые 33 байта из данных base64, чтобы повторить то, что функция imghdr.what() будет читать из файла, который вы передаете (он читает 32 байта, но это число не делится на 3).

Существует эквивалентный soundhdr модуль, а также python-magic project, который позволяет передавать несколько байтов для определения типа файла.

7
Martijn Pieters 15 Дек 2015 в 11:39

Конечно вы можете. Есть несколько очень простых подходов к проблеме, о которых я могу думать:

Частичное декодирование

Каждый символ base64 кодирует 6 бит ввода, поэтому вы можете соотнести их следующим образом:

Base64: AAAAAABBBBBBCCCCCCDDDDDDEEEEEEFFFFFFGGGGGGHHHHHH
Data:   xxxxxxxxyyyyyyyyzzzzzzzzqqqqqqqqwwwwwwwweeeeeeee

Если вы хотите извлечь 4 байта данных, начиная со смещения 1, вот так:

                ................................
Base64: AAAAAABBBBBBCCCCCCDDDDDDEEEEEEFFFFFFGGGGGGHHHHHH
Data:   xxxxxxxxyyyyyyyyzzzzzzzzqqqqqqqqwwwwwwwweeeeeeee
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Затем, чтобы декодировать только те части, которые вы хотите, вам нужно знать битовые расстояния. Их легко вычислить, просто умножьте ваши байтовые расстояния на 8. Теперь, когда вы знаете, что хотите 32 бита, начиная с бита 8, вы можете найти, какой символ base64 содержит ваши начальные биты. Для этого разделите ваши offset и offset+length на 6:

start = bit  8 = char 1 + bit 2
end   = bit 40 = char 6 + bit 4

Что ж, это соответствует схеме выше - ваш диапазон начинается после 1 полного символа base64 и 2 битов и заканчивается после 6 полных символов base64 и 4 битов.

Теперь, после того как вы знаете точные символы base64, которые вам нужны, вам нужно их декодировать. Для этого имеет смысл использовать существующие декодеры base64, поэтому нам не нужно заниматься кодированием base64 самостоятельно. И чтобы сделать это, вы должны знать, что каждые 4 символа кода base64 соответствуют 3 байтам данных. Итак, вот в чем дело - вы можете добавлять и добавлять тарабарщину к извлеченному коду base64 до тех пор, пока base64 и байтовые границы не выровняются - и, зная, сколько недопустимого ввода произведет декодер base64, выкинуть лишнее.

Итак, сколько нужно добавить, зависит от значения остатка в битах. Если остаток начального бита равен 0, это означает, что A и x выровнены, поэтому никаких изменений не требуется:

           |==========================
Base64: ...AAAAAABBBBBBCCCCCCDDDDDD...
Data:   ...xxxxxxxxyyyyyyyyzzzzzzzz...
           |==========================

Если битовый остаток равен 2, вам необходимо добавить один символ base64 и выкинуть один начальный байт после декодирования:

                 ##|==================
Base64: ...AAAAAABBBBBBCCCCCCDDDDDD...
Data:   ...xxxxxxxxyyyyyyyyzzzzzzzz...
                   |==================

Если остаток по битам равен 4, вам необходимо добавить два символа base64 и выбросить два старших байта после декодирования:

                       ####|==========
Base64: ...AAAAAABBBBBBCCCCCCDDDDDD...
Data:   ...xxxxxxxxyyyyyyyyzzzzzzzz...
                           |==========

То же самое касается трейлинга. Если остаток конца бита равен нулю, изменений нет:

        ===|
Base64: ...AAAAAABBBBBBCCCCCCDDDDDD...
Data:   ...xxxxxxxxyyyyyyyyzzzzzzzz...
        ===|

Если конечный битовый остаток равен 2, вам нужно добавить два символа base64 и выбросить два завершающих байта:

        =========##|
Base64: ...AAAAAABBBBBBCCCCCCDDDDDD...
Data:   ...xxxxxxxxyyyyyyyyzzzzzzzz...
        ===========|

Если остаток конечного бита равен 4, вам нужно добавить один символ base64 и выбросить один завершающий байт:

        ===============####|
Base64: ...AAAAAABBBBBBCCCCCCDDDDDD...
Data:   ...xxxxxxxxyyyyyyyyzzzzzzzz...
        ===================|

Таким образом, для синтетического примера, приведенного выше, необходимо добавить один символ (вместо A) и добавить один символ (вместо H):

                ................................
Base64: ??????BBBBBBCCCCCCDDDDDDEEEEEEFFFFFFGGGGGG??????
Data:   ????????yyyyyyyyzzzzzzzzqqqqqqqqwwwwwwww????????
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

Практический пример

Представьте, что у вас есть волшебство, подобное ?PNG\r\n??????IHDR. Затем, чтобы проверить, соответствует ли строка в кодировке base64 вашей магии, вы можете определить байты в магии, которые известны, а также их битовые смещения и длины:

"PNG\r\n"  ->  offset =  8, length = 40
"IHDR"     ->  offset = 96, length = 32

Итак, используя наши идеи сверху:

"PNG\r\n"  ->  start =  8 ( char  1, bits = 2 ), end = 48  ( char 8, bits = 0 )
"IHDR"     ->  start = 96 ( char 16, bits = 0 ), end = 128 ( char 21, bits = 2 )

Чтобы декодировать часть "PNG\r\n", вам нужно взять 7 полных символов base64, начиная с символа 1, затем добавить 1 символ, декодировать, выбросить 1 начальный байт и сравнить.

Чтобы декодировать часть "IHDR", вам нужно взять 6 символов base64, начиная с символа 16, затем добавить 2 символа, декодировать, выбросить 2 конечных байта и сравнить.

Перевести магию

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

Итак, если у вас есть магия ?PNG\r\n??????IHDR (я заменил \r и \n для целей презентации), как в примере выше, при кодировании в base64, это выглядит так:

Data:   [?PN]  [Grn]  [???]  [???]  [IHD]  [R??]
Base64: (?~BO) (Rw0K) (????) (????) (SUhE) (Ug==)

В части ?~BO знак ~ является лишь частично случайным. Давайте посмотрим на эту конструкцию побитово:

Data:   ????????PPPPPPPPNNNNNNNN
Base64: ??????~~~~~~BBBBBBOOOOOO

Таким образом, только два младших бита ~ действительно неизвестны, и это означает, что вы можете использовать эту информацию при тестировании магии на данных, чтобы сузить область магии.

Для этого конкретного случая, вот полный список всех кодировок:

Data:   ??????00PPPPPPPPNNNNNNNN
Base64: ??????FFFFFFBBBBBBOOOOOO  => ?FBO

Data:   ??????01PPPPPPPPNNNNNNNN
Base64: ??????VVVVVVBBBBBBOOOOOO  => ?VBO

Data:   ??????10PPPPPPPPNNNNNNNN
Base64: ??????llllllBBBBBBOOOOOO  => ?lBO

Data:   ??????11PPPPPPPPNNNNNNNN
Base64: ??????111111BBBBBBOOOOOO  => ?1BO

То же самое относится к конечной группе R??, но поскольку вместо 2 битов есть 4 неопределенных бита, список перестановок длиннее:

Ug??  <=  0000???? ????????
Uh??  <=  0001???? ????????
Ui??  <=  0010???? ????????
Uj??  <=  0011???? ????????
Uk??  <=  0100???? ????????
Ul??  <=  0101???? ????????
Um??  <=  0110???? ????????
Un??  <=  0111???? ????????
Uo??  <=  1000???? ????????
Up??  <=  1001???? ????????
Uq??  <=  1010???? ????????
Ur??  <=  1011???? ????????
Us??  <=  1100???? ????????
Ut??  <=  1101???? ????????
Uu??  <=  1110???? ????????
Uv??  <=  1111???? ????????

Итак, в регулярном выражении ваш base64-magic для ?PNG\r\n??????IHDR будет выглядеть так:

rx = re.compile(b'^.[FVl1]BORw0K........SUhEU[g-v]')
if rx.match(base64.b64encode(b'xPNG\r\n123456IHDR789foobar')):
    print('Yep, it works!')
4
toriningen 15 Дек 2015 в 16:54

Это изображение PNG

import base64

encoded_string = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='

decoded_string = base64.b64decode(encoded_string)
print 'Decoded :', decoded_string

Выход:

python base_decode.py 
Decoded : �PNG
-2
Brad Werth 16 Янв 2016 в 07:30