一文读懂Base64
一文读懂Base64
本文来聊一聊Base64, 我个人感觉这个不应该算是加密,而应该是编码,相比于加密算法和哈希函数而言,今天要聊的还是比较轻松的(个人感觉)。
什么是Base64?
❝Base64(基底64)是一种基于64个可打印字符来表示二进制数据的表示方法。-- 维基百科
❞
简单来说,小明和小红两个人想要互相传递信息,他们产生的数据只能是二进制的流数据(也就是0
和1
),但是小明和小红要想互相传递信息不能直接说,而要写在小纸条上,这个小纸条又无法承载0
和1
这时候今天的主角「Base64」就出现了,他可以把二进制的数据流转换成为64个可见的字符。Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME的电子邮件及XML的一些复杂数据。
Base64的工作原理
首先要有一个索引表,标准 Base64 里的 64 个可打印字符是 A-Za-z0-9+/,分别依次对应索引值 0-63,标准的索引表如下:
Base64索引表
Base64索引表
编码方案
编码过程中,每三个字节(8bit * 3 = 24bit)一组进行编码,因为2^6 = 64
因此每组有4个索引与之对应,如下图所示:
编码方案
编码样例
上面的图可能不是很清晰,下面来看一个具体的例子,如下图所示:
编码样例
可以发现,对于ABC
的Base64编码为QUJD
,这时候细心的读者可能发现了,如果输入的长度不是3的整数倍或者二进制流的数据不是24的整数倍那么将要如何处理呢。这里Base64采用的是直接Padding0
的方案,也就是不足24bit最后一个分组直接Padding0
。注意如果是Padding的数据,那么不能按照索引表去查0位置的索引,而是直接用=
填充。下面还是来看一个例子吧:
Base64Padding
也就是说,如果剩余两个字节那么将会填充两个=
, 如果剩余一个字节那么填充一个=
。
编码实现
const BASE64_PADDING_CHAR: char = '='; const BASE64_TABLE: [char; 64] = [ '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', '+', '/', ]; struct Base64 {} impl Base64 { pub fn encode(data: &[u8]) -> String { if data.is_empty() { return String::new(); } // 计算padding长度 let encode_length = match (data.len() / 3, (data.len() % 3) != 0) { (data_length, true) => 4 * data_length + 4, (data_length, false) => 4 * data_length, }; let mut result = String::with_capacity(encode_length); // 编码 for i in (0..(data.len() / 3 * 3)).step_by(3) { let bits = ((data[i] & 0xff) as u32) << 16 | ((data[i + 1] & 0xff) as u32) << 8 | (data[i + 2] & 0xff) as u32; result.push(BASE64_TABLE[((bits >> 18) & 0x3f) as usize]); result.push(BASE64_TABLE[((bits >> 12) & 0x3f) as usize]); result.push(BASE64_TABLE[((bits >> 6) & 0x3f) as usize]); result.push(BASE64_TABLE[(bits & 0x3f) as usize]); } // padding match data.len() % 3 { 1 => { let bits: u32 = (((data[data.len() - 1] & 0xff) as u32) << 4) as u32; result.push(BASE64_TABLE[((bits >> 6) & 0x3f) as usize]); result.push(BASE64_TABLE[(bits & 0x3f) as usize]); result.push(BASE64_PADDING_CHAR); result.push(BASE64_PADDING_CHAR); } 2 => { let bits = ((data[data.len() - 2] & 0xff) as u32) << 10 | ((data[data.len() - 1] & 0xff) as u32) << 2; result.push(BASE64_TABLE[((bits >> 12) & 0x3f) as usize]); result.push(BASE64_TABLE[((bits >> 6) & 0x3f) as usize]); result.push(BASE64_TABLE[(bits & 0x3f) as usize]); result.push(BASE64_PADDING_CHAR); } _ => {} } return result; } } #[cfg(test)] mod test { use crate::base64::Base64; #[test] fn test() { println!("Base64(ABC) = {}", Base64::encode("ABC".as_bytes())); println!("Base64(A) = {}", Base64::encode("A".as_bytes())); println!("Base64(AB) = {}", Base64::encode("AB".as_bytes())); } }