Python,有人可以通过base64编码来猜测文件的类型吗?

时间:2015-12-15 11:17:28

标签: python base64

我们说我有以下内容:

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

这只是一个点图像(来自https://en.wikipedia.org/wiki/Data_URI_scheme)。但我不知道它是图像还是文本等。是否有可能理解它只有这个编码的字符串?我在Python中尝试它,但它也是一般性问题。因此,对这两者的任何见解都非常受欢迎。

3 个答案:

答案 0 :(得分:7)

你不能,至少没有解码,因为有助于识别文件类型的字节分布在base64字符上,而这些字符并不直接与整个字节对齐。每个字符编码6个,这意味着每4个字符编码3个字节。

识别文件类型需要访问不同块大小的字节。例如,JPEG图像可以从字节FF D8或FF D9中识别,但是两个字节;后面的第三个字节也必须编码为4字符块的一部分。

可以做的是解码刚好的base64字符串来进行文件类型指纹识别。因此,您可以解码前4个字符以获得3个字节,然后使用前两个字符来查看对象是否为JPEG图像。可以从第一个或最后一个字节序列中识别出大量文件格式(PNG图像可以由前8个字节识别,GIF由前6个字符识别,等等)。从base64字符串解码那些字节是微不足道的。

您的样本是PNG图像;您可以使用imghdr module

测试图像类型
>>> 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

我只使用了base64数据的前33个字节来回显imghdr.what()函数从你传递的文件中读取的内容(它读取32个字节,但该数字不会除以3)。

有一个等效的soundhdr module,还有python-magic project允许您传递多个字节来确定文件类型。

答案 1 :(得分:3)

当然,你可以。对于我能想到的问题,几乎没有简单的方法:

部分解码

每个base64字符对6位输入进行编码,因此您可以按如下方式对它们进行关联:

Base64: AAAAAABBBBBBCCCCCCDDDDDDEEEEEEFFFFFFGGGGGGHHHHHH
Data:   xxxxxxxxyyyyyyyyzzzzzzzzqqqqqqqqwwwwwwwweeeeeeee

如果你想提取4个字节的数据,从偏移量1开始,如下所示:

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

然后,要仅解码您想要的部分,您需要知道位距离。它们很容易计算,只需将字节距离乘以8.现在,在您知道需要32位后,从第8位开始,您可以找到包含起始位的base64字符。为此,请将您的offsetoffset+length加密为6:

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

好吧,这映射到上面的方案 - 你的span在1个完整的base64 char和2位之后开始,并在6个完整的base64 chars和4位之后结束。

现在,在您知道所需的确切base64字符后,您需要解码它们。为此,利用现有的base64解码器是有意义的,因此我们不需要自己处理base64编码。要做到这一点,你应该知道base64代码的每4个字符对应3个字节的数据。所以,这里有诀窍 - 你可以在你提取的base64代码前加上乱码,直到base64和byte边界对齐 - 并且知道base64解码器会产生多少无效输入,抛弃过多。

因此,前置多少取决于位余数的值。如果起始位余数为0,则表示Ax已对齐,因此无需进行任何更改:

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

如果位余数为2,则需要预先添加一个base64 char,并在解码后抛出一个前导字节:

                 ##|==================
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字符,从char 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??组,但因为有4个未定义位而不是2个,所以排列列表更长:

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???? ????????

所以,在regexp中,?PNG\r\n??????IHDR的base64-magic看起来像这样:

rx = re.compile(b'^.[FVl1]BORw0K........SUhEU[g-v]')
if rx.match(base64.b64encode(b'xPNG\r\n123456IHDR789foobar')):
    print('Yep, it works!')

答案 2 :(得分:-2)

它的PNG图像

import base64

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

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

输出:

python base_decode.py 
Decoded : �PNG