前面的话
Python 内置的 base64 模块,在 这里 http://docs.python.org/library/base64.html?highlight=base64#base64 ,包括 b64encode , b64decode , urlsafe_b64decode 等,可以满足包括 URL 在内的文本编码需要。但是在用 base64.encode 编码二进制文件的时候,发现编码不完整,只有部分文件被编码了, base64.decode 解码出来文件错误。 可能是base64 模块用来出来文本的?仔细分析发现,是忘记用二进制模式打开文件了。但是,自己实现 base64 模块基本功能也不是什么难事,不是要重复发明轮子,仅作为学习 python 和 base64 的练习。
用内置 base64模块转换二进制文件与 base64 编码文本文件方法如下:
import base64 fin = open(r"D:\2.zip" , "rb" ) fout = open(r"D:\2.x.txt" , "w" ) base64.encode(fin, fout) fin.close() fout.close() fin = open(r"D:\2.x.txt" , "r" ) fout = open(r"D:\2.x.zip" , "wb" ) base64.decode(fin, fout) fin.close() fout.close()
Base64 介绍
Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。 Base64 常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据。包括 MIME 的 email , email via MIME, 在 XML 中存储复杂数据。
转换的时候,将三个 byte 的数据,先后放入一个 24bit 的缓冲区中,先来的 byte 占高位。数据不足 3byte 的话,于缓冲区中剩下的 bit 用 0 补足。然后,每次取出 6 (因为 )个 bit ,按照其值选择 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。
如果最后剩下两个输入数据,在编码结果后加 1 个 “=” ;如果最后剩下一个输入数据,编码结果后加 2 个 “=” ;如果没有剩下任何数据,就什么都不要加,这样才可以保证资料还原的正确性。
Base64索引表:
Value
Char
Value
Char
Value
Char
Value
Char
0
A
16
Q
32
g
48
w
1
B
17
R
33
h
49
x
2
C
18
S
34
i
50
y
3
D
19
T
35
j
51
z
4
E
20
U
36
k
52
0
5
F
21
V
37
l
53
1
6
G
22
W
38
m
54
2
7
H
23
X
39
n
55
3
8
I
24
Y
40
o
56
4
9
J
25
Z
41
p
57
5
10
K
26
a
42
q
58
6
11
L
27
b
43
r
59
7
12
M
28
c
44
s
60
8
13
N
29
d
45
t
61
9
14
O
30
e
46
u
62
+
15
P
31
f
47
v
63
/
二进制转成 Base64 编码
按照算法描述:
1 、讲输入数据按 3 个字节组成一组 3*8=24 位的整数;
2 、然后将其按6 位一组分为24/6= 4 组;
3 、每组 6 位算出取值,在 base64 索引表中查看对应的字符,即可;
4 、如果还有模 3 剩余的 1 个字节数据,则补 2 个字节的 0 ,将转换成的 4 字符的最后 2 个替换成 ”==” ;
5 、如果还有模 3 剩余的 2 个字节数据,则补 1 个字节的 0 ,将转换成的 4 字符的最后 1 个替换成 ”=” 。
其中 1-3 步容易理解和编写,第 4 、 5 步的实现方法可能有很多种,但是考虑到可读性,而且每次转换之多执行其中一步,可以使用如下中的硬编码:
_CODE_CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" def binbase64(data): base64str = "" for i in range(len(data)/ 3 ): datavalue = ((data[3 *i] << 16 ) | (data[ 3 *i+ 1 ] << 8 ) | data[ 3 *i+ 2 ]) for j in range( 4 ): base64str += _CODE_CHAR[(datavalue >> 6 *( 3 -j)) & 0x3F ] dataremain = len(data) % 3 if dataremain == 1 : datavalue = data[-1 ] << 16 ; base64str += _CODE_CHAR[(datavalue >> 18 ) & 0x3F ] base64str += _CODE_CHAR[(datavalue >> 12 ) & 0x3F ] base64str += "==" elif dataremain == 2 : datavalue = (data[-2 ] << 16 ) | (data[- 1 ] << 8 ); base64str += _CODE_CHAR[(datavalue >> 18 ) & 0x3F ] base64str += _CODE_CHAR[(datavalue >> 12 ) & 0x3F ] base64str += _CODE_CHAR[(datavalue >> 6 ) & 0x3F ] base64str += "=" return base64str
Base64 解码转成二进制
解码之前要对输入数据有效性进行必要的判断,如长度是否为 4 的整数倍,有没有非法字符等。解码步骤为:
1 、取 4 个字符组成一组,判断每个字符在 Base64 索引表中的索引值;
2 、将索引值转成 6 位二进制,并组 4*6=24 位的整数;
3 、再将这个 24 位的整数分成 24/8=3 个字节;
4、如果末尾有2个"==",则只有1个有效字节
5、如果末尾有1个"=",在有2个有效字节
def base64bin(encodedstr): if len(encodedstr) % 4 : raise Base64Error( "The length of input 'base64str' MUST be multiple of 4." ) rawbase64str = encodedstr.rstrip("=" ) if (len(rawbase64str) % 4 ) == 1 : raise Base64Error( "Too many '=' characters, MUST NOT be more than 2." ) for x in rawbase64str: if x not in _CODE_CHAR: raise Base64Error( "Unexpected character %s." , x) data=[] for i in range(len(rawbase64str)/ 4 ): datavalue = (_CODE_CHAR.find(rawbase64str[4 *i]) << 18 ) \ | (_CODE_CHAR.find(rawbase64str[4 *i+ 1 ]) << 12 ) \ | (_CODE_CHAR.find(rawbase64str[4 *i+ 2 ]) << 6 ) \ | (_CODE_CHAR.find(rawbase64str[4 *i+ 3 ])) data.append((datavalue >> 16 ) & 0xFF ) data.append((datavalue >> 8 ) & 0xFF ) data.append((datavalue) & 0xFF ) strremain = len(rawbase64str) % 4 if strremain == 2 : datavalue = (_CODE_CHAR.find(rawbase64str[-2 ]) << 18 ) \ | (_CODE_CHAR.find(rawbase64str[-1 ]) << 12 ) data.append((datavalue >> 16 ) & 0xFF ) elif strremain == 3 : datavalue = (_CODE_CHAR.find(rawbase64str[-3 ]) << 18 ) \ | (_CODE_CHAR.find(rawbase64str[-2 ]) << 12 ) \ | (_CODE_CHAR.find(rawbase64str[-1 ]) << 6 ) data.append((datavalue >> 16 ) & 0xFF ) data.append((datavalue >> 8 ) & 0xFF ) return data
class Base64Error(Exception): pass
字符串转换
def strbase64(astr): return binbase64(map(ord, astr))
def base64str(encodedstr): return "".join(map(chr,base64bin(encodedstr)))
文件转换
文件转换要用到这里介绍的 filehleper 。另外, RFC 822 规定, Base64 文本没行 76 个字符,可存 76/4*3=57 个字节。所以我们每次读入 57 个字节处理。
def binfiletobase64(inp, out): blocksize = 76 / 4 * 3 def _binfiletobase64(fin, fout): while True : chunk = fin.read(blocksize) if chunk: fout.write(strbase64(chunk)) fout.write("\n" ) else : break fileinoutpattern(inp, out, _binfiletobase64, inmode="rb" , outmode= "w" ) def base64filetobin(inp, out): def _base64filetobin(fin, fout): for line in fin: fout.write(base64str(line.rstrip())) fileinoutpattern(inp, out, _base64filetobin, inmode="r" , outmode= "wb" )
测试代码
def test(): rawstr = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure." encodedstr = binbase64(map(ord, rawstr)) for x in range( 0 , len(encodedstr), 76 ): print encodedstr[x:x+ 76 ] encodedstr = strbase64(rawstr) for x in range( 0 , len(encodedstr), 76 ): print encodedstr[x:x+ 76 ] data = base64bin(encodedstr) decodedstr = "".join(map(chr,data)) print decodedstr assert decodedstr == rawstr decodedstr = base64str(encodedstr) print decodedstr assert decodedstr == rawstr binfiletobase64(r"D:\2.zip" , r "D:\2.txt" ) base64filetobin(r"D:\2.txt" , r "D:\2.1.zip" ) print "OK" if __name__ == "__main__" : test()
下载地址1:源代码下载binbase64.zip
下载地址2:源代码下载 binbase64.zip