13.5. base64 — 使用 ASCII 编码二进制数据
目标:base64 模块含有一些可将二进制数据转译为用 ASCII 子集编码数据的函数,使得这些数据可以用纯文本协议来传递。
base64 , base32 , base16 和 base85 编码将 8 位的单字节转换成 ASCII 范围内可打印的字符值,来兼容那些只支持 ASCII 编码数据的系统,代价是使用更多的位来表示数据,比如 SMTP 。 base 的值表示每个字母在编码中所需要的相应的长度。一些使用略有不同的字母表,但仍然是 URL-安全的原编码系统的变种也是存在的。
Base 64 编码
这是用来编码文本的一个基本例子.
base64_b64encode.py
import base64
import textwrap
# 读取源文件并去掉头部。
with open(__file__, 'r', encoding='utf-8') as input:
raw = input.read()
initial_data = raw.split('#end_pymotw_header')[1]
byte_string = initial_data.encode('utf-8')
encoded_data = base64.b64encode(byte_string)
num_initial = len(byte_string)
# 填充字节不超过两个
padding = 3 - (num_initial % 3)
print('{} bytes before encoding'.format(num_initial))
print('Expect {} padding bytes'.format(padding))
print('{} bytes after encoding\n'.format(len(encoded_data)))
print(encoded_data)
输入必须是字节串,因此 unicode 字符串首先用 UTF-8 编码。输出显示原本 185 个字节(按输出来看应该是 184 个字节)的 UTF-8 源数据被扩展编码为 248 个字节。
注意
由库函数编码后的数据是不包含换行符的,但我们人为地将其断行是为了在页面上更好地展示输出。
$ python3 base64_b64encode.py
184 bytes before encoding
Expect 2 padding bytes
248 bytes after encoding
b'CmltcG9ydCBiYXNlNjQKaW1wb3J0IHRleHR3cmFwCgojIExvYWQgdGhpcyBzb3
VyY2UgZmlsZSBhbmQgc3RyaXAgdGhlIGhlYWRlci4Kd2l0aCBvcGVuKF9fZmlsZV
9fLCAncicsIGVuY29kaW5nPSd1dGYtOCcpIGFzIGlucHV0OgogICAgcmF3ID0gaW
5wdXQucmVhZCgpCiAgICBpbml0aWFsX2RhdGEgPSByYXcuc3BsaXQoJw=='
Base 64 解码
b64decode()
将编译后的字符串还原成原来的形式,即用查表的方式将四个字节换为原本的三个。
base64_b64decode.py
import base64
encoded_data = b'VGhpcyBpcyB0aGUgZGF0YSwgaW4gdGhlIGNsZWFyLg=='
decoded_data = base64.b64decode(encoded_data)
print('Encoded :', encoded_data)
print('Decoded :', decoded_data)
在编码过程中,输入的每 24 位(每三个字节)组成一个序列,该序列在输出中都被替换为四个字节。输出末尾处的等号仅表示填充,因为本例中,原字符串的位长度不被 24 整除。
$ python3 base64_b64decode.py
Encoded : b'VGhpcyBpcyB0aGUgZGF0YSwgaW4gdGhlIGNsZWFyLg=='
Decoded : b'This is the data, in the clear.'
b64decode()
返回的值是一个字节串。如果已知内容是文本,那么字节串可以被转换成一个 unicode 对象。然而,用 base 64 编码的意义在于传递二进制数据,因此假定解码后的值是文本并不总是安全的。
URL-安全变种
由于默认的 base64 字母表包含 +
和 /
这两个字符,而它们在 URLs 中也会被用到,因此有必要采用另一种编码方式来替换这些字符。
base64_urlsafe.py
import base64
encodes_with_pluses = b'\xfb\xef'
encodes_with_slashes = b'\xff\xff'
for original in [encodes_with_pluses, encodes_with_slashes]:
print('Original :', repr(original))
print('Standard encoding:',
base64.standard_b64encode(original))
print('URL-safe encoding:',
base64.urlsafe_b64encode(original))
print()
+
被替换为 -
,而 /
则被替换为下划线 (_
) 。其他字符保持不变。
$ python3 base64_urlsafe.py
Original : b'\xfb\xef'
Standard encoding: b'++8='
URL-safe encoding: b'--8='
Original : b'\xff\xff'
Standard encoding: b'//8='
URL-safe encoding: b'__8='
其他编码
除了 Base64 编码,该模块还提供了其他一些函数来处理 Base85 ,Base32 和 Base16 (hex) 编码的数据.
base64_base32.py
import base64
original_data = b'This is the data, in the clear.'
print('Original:', original_data)
encoded_data = base64.b32encode(original_data)
print('Encoded :', encoded_data)
decoded_data = base64.b32decode(encoded_data)
print('Decoded :', decoded_data)
Base32 字母表包含 ASCII 中所有 26 个大写字母和数字 2 到 7 。
$ python3 base64_base32.py
Original: b'This is the data, in the clear.'
Encoded : b'KRUGS4ZANFZSA5DIMUQGIYLUMEWCA2LOEB2GQZJAMNWGKYLSFY==
===='
Decoded : b'This is the data, in the clear.'
Base16 函数则可以处理十六进位制字母表。
base64_base16.py
import base64
original_data = b'This is the data, in the clear.'
print('Original:', original_data)
encoded_data = base64.b16encode(original_data)
print('Encoded :', encoded_data)
decoded_data = base64.b16decode(encoded_data)
print('Decoded :', decoded_data)
每当减少编码一个字母所用的位数时,编码后的数据就需要更多的空间。
$ python3 base64_base16.py
Original: b'This is the data, in the clear.'
Encoded : b'546869732069732074686520646174612C20696E207468652063
6C6561722E'
Decoded : b'This is the data, in the clear.'
Base85 函数则使用了在空间利用率上面比 Base64 更高效的扩展字母表。
base64_base85.py
import base64
original_data = b'This is the data, in the clear.'
print('Original : {} bytes {!r}'.format(
len(original_data), original_data))
b64_data = base64.b64encode(original_data)
print('b64 Encoded : {} bytes {!r}'.format(
len(b64_data), b64_data))
b85_data = base64.b85encode(original_data)
print('b85 Encoded : {} bytes {!r}'.format(
len(b85_data), b85_data))
a85_data = base64.a85encode(original_data)
print('a85 Encoded : {} bytes {!r}'.format(
len(a85_data), a85_data))
在 Mercurial , git 和 PDF 文件格式中使用的 Base85 编码存在多个不同变种。Python 实现了其中两种, b85encode()
实现的是在 Git 和 Mercurial 中使用的版本,而 a85encode()
实现的则是在 PDF 文件中使用的 ASCII85 变种。
$ python3 base64_base85.py
Original : 31 bytes b'This is the data, in the clear.'
b64 Encoded : 44 bytes b'VGhpcyBpcyB0aGUgZGF0YSwgaW4gdGhlIGNsZWF
yLg=='
b85 Encoded : 39 bytes b'RA^~)AZc?TbZBKDWMOn+EFfuaAarPDAY*K0VR9}
'
a85 Encoded : 39 bytes b'<+oue+DGm>FD,5.A79Rg/0JYE+EV:.+Cf5!@<*t
'
参考
- 标准库 base64 文档
- RFC 3548 -- Base16, Base32 和 Base64 数据编码
- RFC 1924 -- 一份 IPv6 地址的简约表示 (建议使用 base-85 编码 IPv6 网址)
- Ascii85
- 从 Python 2 移植到 3 的关于 base64 的注意...
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。