本文先详细介绍下Base64编码,再使用crate base64 进行代码示例。
介绍之前先引用下base64文档里的一段话
I want canonical base64 encoding/decoding.
First, don't do this. You should no more expect Base64 to be canonical than you should expect compression algorithms to produce canonical output across all usage in the wild (hint: they don't). However, people are drawn to their own destruction like moths to a flame, so here we are.
Base64 详解
通常意义的Base64编码,是将输入的二进制数据每 6个bit 位分为一组,使用 64 个字符集(包括 A-Z, a-z, 0-9, "+", "/"
)来表示这 6 位数据。这样,原始数据的每 3 字节(3 * 8bit = 24 bit = 4 * 6 bit)数据会被编码为 4 字符(每个字符代表 6 位)。
举个例子
01100010 01100001 01110011 01100101 00110110 00110100
011000 100110 000101 110011 011001 010011 011000 110100
24 38 5 51 25 19 24 52
Y m F z Z T Y 0
上述每一行数据含义
- 字符串 ‘base64’ 的二进制数据
- 6个bit一组,得到8个数字
- 每组bit对应的十进制数字
- 从字符集取第N个字符
以上就是base64编码的过程,解码逆向运算即可。
字符集
一般来说,字符集分为两套
- 默认的,非Url safe的
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
- Url safe
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
两者的区别就是使用 -
, _
代替+
,/
“标准字母表使用+和/作为它的两个非字母数字符号,在URL中使用它们时如果不将它们编码为%2B和%2F,则不安全。为了避免这个问题,一些应用程序使用了一个‘URL安全’的字母表,它使用
-
,_
代替(+
,/
)。” 当然必要时也可自定义字符集。
Padding
Base64 编码将每 3 个字节的数据转换为 4 个字符,而 Base64 编码的输出一般是 4 的倍数。如果输入数据的字节数不是 3 的倍数,编码后的结果就会不满 4 个字符。为了满足这一要求,Base64 会用一个或两个 =
字符来进行填充。当然也可以选择不进行Padding,此时Base64 编码的输出可能不再是4的倍数。
1. 填充规则:
-
0 个填充(没有填充) :如果输入数据的字节数是 3 的倍数,那么不需要任何填充。例如前面举例的
base64
字符串 -
1 个填充:如果输入数据的字节数是 3 的倍数少 1(即剩 2 个字节),则需要在输出中添加 1 个
=
字符来填充。 例如:-
输入:
ba
(2 字节) -
Base64 编码后:
YmE=
(4 字符,1 个填充)01100010 01100001 01100010 01100001 00000000 011000 100110 000100 000000 24 38 4 0 Y m E =(padding)
-
-
2 个填充:如果输入数据的字节数是 3 的倍数少 2(即剩 1 个字节),则需要在输出中添加 2 个
=
字符来填充。 例如:-
输入:
b
(1 字节) -
Base64 编码后:
Yg==
(4 字符,2 个填充)01100010 01100010 00000000 00000000 011000 100000 000000 000000 24 32 0 0 Y g =(填充) =(填充)
-
Line wrapping
在 RFC2045 (1996)中,规定了每行最多不能超过 76 字符,当消息过长时需要加入换行符。不过,RFC2045 的主要目的不是规范 Base64,而是针对 MIME 的。后来,在针对 Base64 的 RFC3548 和 RFC4648 中都明确规定了不要再插入换行符。
综上,当仅仅描述Base64时,字符集可以变化,甚至自定义,padding可以变,line可以变化。再回头看开头引用的那段话,所以说不应该期望Base64会有规范性。因此当使用Base64时,所有细节都应该描述清楚。
Rust代码实现
以下代码实现了BASE64_STANDARD、STANDARD_NO_PAD、URL_SAFE、URL_SAFE_NO_PAD,以及自定义字符集,自定义编解码方式。
其他高级用法参考 base64文档。
另外 base64
没有提供Line wrapping模式,
I need to line-wrap base64, e.g. for MIME/PEM.
line-wrap does just that.
use base64::{alphabet, Engine, engine};
use base64::engine::general_purpose::{STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD};
use base64::prelude::BASE64_STANDARD;
fn main() {
println!("standard ba {}", BASE64_STANDARD.encode("ba"));
println!("standard no pad ba {}", STANDARD_NO_PAD.encode("ba"));
println!("url safe ba {}", URL_SAFE.encode("ba"));
println!("url safe no pad ba {}", URL_SAFE_NO_PAD.encode("ba"));
// bizarro-world base64: +/ as the first symbols instead of the last
let alphabet =
alphabet::Alphabet::new("+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
.unwrap();
// a very weird config that encodes with padding but requires no padding when decoding...?
let crazy_config = engine::GeneralPurposeConfig::new()
.with_decode_allow_trailing_bits(true)
.with_encode_padding(true)
.with_decode_padding_mode(engine::DecodePaddingMode::RequireNone);
let crazy_engine = engine::GeneralPurpose::new(&alphabet, crazy_config);
println!("custom {}", crazy_engine.encode(b"ba"));
}