base64
定义
百度百科对base64的定义:Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。
传输数据为什么需要编码?数据在网络中传输一定是以字节码形式传输的,A端发送数据data给B端,由于A发送的是字符数据,在进行网络传输前数据必须被编码成字节数据,这就涉及到编码问题。
为什么要将字节码编码成64个可打印字符进行传输?编码也是有讲究的,在有些文本传输协议中,如果直接将字符完整编码为对应的字节序列就可能出现问题,因为编码出来的字节序列中可能存在文本传输协议的控制信息,这样就会破坏应用层协议传输数据。出现这种问题的根源在于编码后的字节中出现了不可见字符(128-255的asc码),如果能够将字符全部编码为可见字符(字母、符号),这种问题就可以得到解决,Base64就这样的一种编码方式。
编码逻辑
Base64顾名思义就只有64个字符,产生64个字符就需要6bit,而一个字节是8bit,故需要对原字节拆分。如下图,3个字节正好可以拆分成4组6bit,再将每组6bit的高2位补0,就产生了新的8bit字节,新字节一定是可见字符(0-63的asc码)。如果字节数不能被正好拆分,比如1个字节,就需要使用=来占位(=只可能是0个、1个或2个,=也是可见字符,所以是安全的。之所以要补充=,猜测是因为base64在解码时是4个字节一组进行解码的,所以才必须补全)。
base64的64个字符是['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
Example
“abcd”的字节序列
01100001 01100010 01100011 01100100
Base64编码后
00011000 00010110 00001001 00100011 00011001 00000000
对照Base64字符集
YWJjZA
补=
YWJjZA==
HTTP中的编码
之前提到base64适合在文本传输协议中传输二进制数据,http属于典型的文本传输协议,http中有必要使用base64吗?
我们可以通过http上传文件,来看http是如何处理文件上传的。
在上传图片、文件等二进制数据时,http请求大致是下面的格式:
POST /t2/upload.do HTTP/1.1 User-Agent: xyz Accept-Language: zh-cn,zh;q=0.5 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Content-Length: 60408 Content-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC Host: www.xyz.com --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC Content-Disposition: form-data;name="desc" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name=zhang3 --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC Content-Disposition: form-data;name="pic"; filename="photo.jpg" Content-Type: application/octet-stream Content-Transfer-Encoding: binary [图片二进制数据] --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC--
可以发现,http传输图片、文件时并不需要base64编码,因为其对这类二进制数据有专门的协议约束(application/octet-stream、boundary),此时即便存在不可见字符,也不会威胁到协议本身(当然可以手动进行base64编码)。
是不是说http就不需要编码了呢?文本传输都需要编码字符序列,http在传输参数时会使用url编码,其实就是%+utf-8编码的十六进制,由于%的存在(可见字符),其仍然是安全的。
附:自己实现的base64编码,使用3字节一组进行编码应该会好些。
1 public class Base64Encoder { 2 private byte[] bytes; 3 private static Map<Byte, Character> base64; 4 static { 5 base64 = new HashMap<>(); 6 int i = 0; 7 for(; i<26; i++) { 8 base64.put((byte) i, Character.valueOf((char) ('A' + i))); 9 } 10 for(; i<52; i++) { 11 base64.put((byte) i, Character.valueOf((char) ('a' + i-26))); 12 } 13 base64.put((byte) i++, '+'); 14 base64.put((byte) i, '/'); 15 } 16 17 public Base64Encoder(String s, Charset charset) { 18 bytes = s.getBytes(charset); 19 } 20 public char[] toBase64() { 21 int length = bytes.length; 22 char[] base64Chars = new char[(length % 3 == 0 ? length / 3 * 4 : (length / 3 * 4 + 4))]; 23 for(int i=0; i<length; i++) { 24 base64Chars[i] = nextBase64Char(i); 25 } 26 return base64Chars; 27 } 28 29 /** 30 * 产生bytes中索引为i的base64字符, 31 * @param i 32 * @return 33 */ 34 private char nextBase64Char(int i) { 35 i = i*6; 36 int index = i/8; 37 int offset = i%8; 38 Character c; 39 if (index < bytes.length) { 40 byte b = nextBase64Byte(index, offset); 41 System.out.print(Integer.toBinaryString(b) + " "); 42 c = base64.get(b); 43 }else { 44 c = '='; 45 } 46 return c; 47 } 48 49 /** 50 * 从bytes中得到索引为index,偏移量为offset的base64字节 51 * @param index 52 * @param offset 53 * @return 54 */ 55 private byte nextBase64Byte(int index, int offset) { 56 byte b = bytes[index]; 57 byte result = 0; 58 b = (byte) (b<<offset); 59 b = (byte) ((0xFF&b)>>>2); 60 if (offset <= 2) { 61 return b; 62 } 63 result |= b; 64 if (++index < bytes.length) { 65 b = bytes[index]; 66 result |= (0xFF&b) >>> (10 - offset); 67 } 68 return result; 69 } 70 }