What is Base64?
这是一篇普及互联网术语的文章。每天呆在互联网上,发现呆的时间越长,发现自己不知道的东西越多。互联网上有太多的知识,虽然我们不可能一下子了解透彻, 但是遇到一个就有必要去了解其相关的知识或是原理,这样以后再见到他就不会觉得陌生了。
Base64是一种使用64基的位置计数法。它使用2的最大次方来代表仅可打印的ASCII 字符。这使它可用来作为电子邮件的传输编码。在Base64中的变量使用字符A-Z、a-z和0-9 ,这样共有62个字符,用来作为开始的64个数字,最后两个用来作为数字的符号在不同的系统中而不同。例如:”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/“。
base64是一种将二进制的01序列转化成ASCII字符的编码方法。编码后的文本或者二进制消息,就可以运用SMTP等只支持ASCII字符的协议传送了。Base64一般被认为会平均增加33%的报文长度,而且,经过编码的消息对于人类来说是不可读的。
在MIME格式的电子邮件中,base64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本。
算法详解
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。
转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲区中剩下的bit用 0补足。然后,每次取出6(因为26 = 64)个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。
如果最后剩下两个输入数据,在编码结果后加1个“=”;如果最后剩下一个输入数据,编码结果后加2个“=”;如果没有剩下任何数据,就什么都不要加,这样才可以保证资料还原的正确性。
注意:根据RFC822规定,每76个字符,还需要加上一个回车换行。
转换后,我们用一个码表来得到我们想要的字符串(也就是最终的Base64编码),这个表是这样的:(摘自RFC2045)
Table 1: The Base64 Alphabet
Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y
这也是Base64名称的由来,而Base64编码的结果不是根据算法把编码变为高两位是0而低6为代表数据,而是变为了上表的形式, 如”A”就有7位,而”a”就只有6位。表中,编码的编号对应的是得出的新字节的十进制值。
用更接近于编程的思维来说,编码的过程是这样的:
第一个字符通过右移2位获得第一个目标字符的Base64表位置,根据这个数值取到表上 相应的字符,就是第一个目标字符。
然后将第一个字符左移4位加上第二个字符右移4位,即获得第二个目标字符。
再将第二个字符左移2位加上 第三个字符右移6位,获得第三个目标字符。
最后取第三个字符的右6位即获得第四个目标字符。
在以上的每一个步骤之后,再把结果与 0×3F 进行 AND 位操作,就可以得到编码后的字符了。
在JAVA中要实现Base64的编码和解码是非常容易的,因为JDK中已经有提供有现成的类:
编码:
String src =”BASE64编码测试”;
sun.misc.BASE64Encoder en = new sun.misc.BASE64Encoder();
String encodeStr = en.encode(src.getBytes());
注意:当encodeStr的长度超过76时,会包含有回车和换行符
解码:
sun.misc.BASE64Decoder dec = new sun.misc.BASE64Decoder();
byte[] data = dec.decodeBuffer(decodeStr);
最近在研究Xmappr项目时候,发现了其对Base64编码的一种实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | /* * This software is released under the BSD license. Full license available at http://xmappr.googlecode.com * * Copyright (c) 2008, 2009, Peter Knego & Xmappr contributors * All rights reserved. */ package xmappr.org.xmappr.converters; /** * Utility class implementing Base64 decoder/encoder. */ public class Base64 { public static char[] base64code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" .toCharArray(); // 初始化数组 private static byte[] nibbles = new byte[128]; static { /** * The Base64 Alphabet Value Encoding 0 A 17 R 34 i 51 z 1 B 18 S 35 j 52 0 2 C 19 T 36 k 53 1 3 D 20 U 37 l 54 2 4 E 21 V 38 m 55 3 5 F 22 W 39 n 56 4 6 G 23 X 40 o 57 5 7 H 24 Y 41 p 58 6 8 I 25 Z 42 q 59 7 9 J 26 a 43 r 60 8 10 K 27 b 44 s 61 9 11 L 28 c 45 t 62 + 12 M 29 d 46 u 63 / 13 N 30 e 47 v (pad) = 14 O 31 f 48 w 15 P 32 g 49 x 16 Q 33 h 50 y */ for (int i = 0; i < nibbles.length; i++) nibbles[i] = -1; //将0-63存入nibbles['A']-nibbles['='],生成上面的Base64位字母表 for (int i = 0; i < 64; i++){ nibbles[base64code[i]] = (byte) i;} } public static String encode(byte[] input) { return encode(input, 0); } public static String encode(byte[] input, int lineLength) { int ilen = input.length; // 计算加入多少个等于号到编码后面 int padding = (3 - (ilen % 3)) % 3; char[] encoded = new char[4 * (ilen + padding) / 3]; int c = 0; int j, j0, j1, j2; int i = 0; while (i < ilen) { j0 = input[i++]; j1 = i < ilen ? input[i++] : 1; j2 = i < ilen ? input[i++] : 1; j = (j0 << 16) + (j1 << 8) + j2; encoded[c++] = base64code[(j >> 18) & 0x3f]; encoded[c++] = base64code[(j >> 12) & 0x3f]; encoded[c++] = base64code[(j >> 6) & 0x3f]; encoded[c++] = base64code[j & 0x3f]; } // replace encoded padding nulls with "=" if (padding == 2) { encoded[encoded.length - 1] = '='; encoded[encoded.length - 2] = '='; } else if (padding == 1) { encoded[encoded.length - 1] = '='; } return lineLength > 0 ? splitLines(encoded, lineLength) : String .valueOf(encoded); } private static String splitLines(char[] chars, int lineLength) { StringBuilder lines = new StringBuilder(); for (int i = 0; i < chars.length; i += lineLength) { lines.append(chars, i, Math.min(chars.length - i, lineLength)) .append("\r\n"); } return lines.delete(lines.length() - 2, lines.length()).toString(); } public static byte[] decode(String data) { return decode(data.trim().toCharArray()); } public static byte[] decode(char[] chars) { int charCount = chars.length; // remove newlines from character count int a = 0; while (a < chars.length) { if (chars[a] == '\r' || chars[a] == '\n') charCount--; a++; } // check the char count if (charCount % 4 != 0) { throw new IllegalArgumentException( "Length of Base64 encoded input string is not a multiple of 4."); } // count padding characters '=' int padCount = 0; while (chars[chars.length - 1 - padCount] == '=') padCount++; // create the output byte array, take padding into account int olen = ((3 * charCount) / 4) - padCount; byte[] out = new byte[olen]; int i = 0; int o = 0; int c = 0; byte[] nibbleBlock = new byte[4]; char currentChar; byte currentNibble; while (i < chars.length) { // read four character into temporary storage (and skip newlines) c = 0; while (c < 4) { currentChar = chars[i++]; // skip newline and padding characters if (currentChar != '\r' && currentChar != '\n') { // nibble conversion table only holds 128 characters if (currentChar > 127) { throw new IllegalArgumentException( "Illegal character in Base64 encoded data."); } currentNibble = currentChar != '=' ? nibbles[currentChar] : 0; // char does not exist (==-1) in conversion table if (currentNibble == -1) { throw new IllegalArgumentException( "Illegal character in Base64 encoded data."); } nibbleBlock[c++] = currentNibble; } } // convert four nibbles into three bytes (4x 6-bit nibbles = 3 // bytes) out[o++] = (byte) ((nibbleBlock[0] < < 2) | (nibbleBlock[1] >>> 4)); if (o < olen) out[o++] = (byte) (((nibbleBlock[1] & 0xf) << 4) | (nibbleBlock[2] >>> 2)); if (o < olen) out[o++] = (byte) (((nibbleBlock[2] & 3) << 6) | nibbleBlock[3]); } return out; } } |
网上有很多介绍关于base64编码的例子,典型的是用“张3”举例得到的编码是“1iUz”,可是我用上面的代码得到的结果是:“1MUz”,两种结果相差了一位,很是奇怪。这是为什么?!
UTF-7
UTF-7 是一个修改的Base64(Modified Base64)。主要是将UTF-16的数 据,用Base64的方法编码为可打印的 ASCII 字符序列。目的是传输 Unicode 数据。主要的区别在于不用等号”=”补余,因为该字符通常需要大量的转译。
在URL中的应用
Base64编码可用于在HTTP环境下传递较长的标识信息。例如,在Java持久化系统Hibernate中,就采用了Base64来将一 个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中 的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码不仅比较简短,同时也具 有不可读性,即所编码的数据不会被人用肉眼所直接看到。
然而,标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式, 而这些“%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将“%”号用作通配 符。
为解决此问题,可采用一种用于URL的改进Base64编码,它不在末尾填充’='号,并将标准Base64中的“+”和“/”分别 改成了“*”和“-”,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识 符的格式。
另有一种用于正则表达式的改进Base64变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“*”以及前面在IRCu中 用到的“[”和“]”在正则表达式中都可能具有特殊含义。
此外还有一些变种,它们将“+/”改为“_-”或“._”(用作编程语言中的标识符名称)或“.-”(用于XML中的Nmtoken) 甚至“_:”(用于XML中的Name)。
关于 Base64可以参照网上另一篇文章:浅谈Base64编码
