Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个位元为一个单元,对应某个可打印字符。三个字节有24个位元,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。它可用来作为电子邮件的传输编码。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9 ,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。一些如uuencode的其他编码方法,和之后binhex的版本使用不同的64字符集来代表6个二进制数字,但是它们不叫Base64。【摘自维基百科】
为了保证所输出的编码位可读字符,Base64制定了一个编码表,以便进行统一转换。编码表的大小为2^6=64,这也是Base64名称的由来。
Base64编码表
Java代码中Base64的使用示例:
package cn.com.base64;
import java.io.IOException;
public class Test {
/**
* 编码
* @param bstr
* @return String
*/
public static String encode(byte[] bstr){
return new sun.misc.BASE64Encoder().encode(bstr);
}
/**
* 解码
* @param str
* @return string
*/
public static byte[] decode(String str){
byte[] bt = null;
try {
sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
bt = decoder.decodeBuffer( str );
} catch (IOException e) {
e.printStackTrace();
}
return bt;
}
/**
* @param args
*/
public static void main(String[] args) {
/* Test te = new Test();
String aa = "hello world";
System.out.println("加密前:"+aa);
aa = te.encode(aa.getBytes());
System.out.println("经过BASE64加密后:"+aa);
String str = aa;
String str2 = new String(te.decode(str));
System.out.println("经过BASE64解密后:"+str2); */
}
说明:第一次在Eclipse中引入BASE64Encoder /BASE64Decoder类,无法引入,解决办法请参 考:http://blog.csdn.net/a0501bqzhxy/article/details/6441526
运行后的结果:
加密前:hello world
经过BASE64加密后:aGVsbG8gd29ybGQ=
经过BASE64解密后:hello world
一步步解读Base64的执行过程:
Base64的编码都是按字符串长度,以每3个8bit的字符为一组,然后针对每组,首先获取每个字符的ASCII编码,然后将ASCII编码转换成8bit的二进制,得到一组3*8=24bit的字节,然后再将这24bit划分为4个6bit的字节,并在每个6bit的字节前面都填两个高位0,得到4个8bit的字节。然后将这4个8bit的字节转换成10进制,对照Base64编码表(下表),得到对应编码后的字符。(注:1. 要求被编码字符是8bit的,所以须在ASCII编码范围内,\u0000-\u00ff,中文就不行。 2. 如果被编码字符长度不是3的倍数的时候,则都用0代替,对应的输出字符为=
例1)字符长度为能被3整除时:
ASCII: 84 111 109
8bit字节: 01010100 01101111 01101101
6bit字节: 010101 000110 111101 101101
十进制: 21 6 61 45
对应编码: V G 9 t
例2)字符串长度不能被3整除时,比如“Lucy”:
L u c y
ASCII: 76 117 99 121
8bit字节: 01001100 01110101 01100011 01111001 00000000 00000000
6bit字节: 010011 000111 010101 100011 011110 010000 000000 000000
十进制: 19 7 21 35 30 16 (异常) (异常)
对应编码: T H V j e Q = =
说明:由于Lucy只有4个字母,所以按3个一组的话,第二组还有两个空位,所以需要用0来补齐。这里就需要注意,因为是需要补齐而出现的0,所以转化成十进制的时候就不能按常规用base64编码表来对应,所以不是a,可以理解成为一种特殊的“异常”,编码应该对应“=”。
接下来解读上面的类Test.java运行的过程:
hello world
对应的ASCII码序列为:
[104,101,108,108,111,32,119,111,114,108,100]
对应的8bit字节序列为:
[01101000,01100101,01101100,01101100,01101111,00100000,01110111,01101111,01110010,01101100,
01100100,00000000]
对应的6bit字节序列为:
[011010,000110,010101,101100,011011,000110,111100,100000,011101,110110,111101,110010,011011
,000110,010000,000000]
十进制序列为:
[26,6,21,44,27,6,60,32,29,54,61,50,27,6,16, (异常)]
对应base64编码序列(对照base64编码表)为:
[aGVsbG8gd29ybGQ=]
和类Test.java运行的结果是一样的。其中值得注意的是Java中的getBytes()方法,可以获得字符的ASCII码值。
根据上面的推理,我实现了Base64加密与解密的算法:
public class Base64 {
private static final String base64code = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/";
private static final String PADDING_STR = "=";
private static final String PADDING_STR_BINARRY ="00000000";
private static final String PADDING_STR_BINARRY2 ="000000";
/**加密
* @param string 加密的报文
* @return 返回 加密后的报文
*/
public static String encode(String string) {
Integer[] asciiArray = stringToASCII(string);
String binarryStr = asciiToBinarry(asciiArray);
//补"0"
int zeroPaddingNum = 0;
int length = asciiArray.length;
while(length%3!=0){
binarryStr += PADDING_STR_BINARRY;
length++;
zeroPaddingNum++;
}
//查Base64编码表,进行"偷天换日"
StringBuffer retvalue = new StringBuffer();
while(binarryStr.length()>0){
String str = binarryStr.substring(0,6);
int temp = Integer.valueOf(str,2); //二进制转十进制
if(binarryStr.length()>zeroPaddingNum*6){
retvalue.append(base64code.charAt(temp));
}else{
retvalue.append(PADDING_STR);
}
binarryStr = binarryStr.substring(6);
}
return retvalue.toString();
}
/**解密
* @param string 解密的报文
* @return 返回 解密后的报文
*/
public static String decode(String string) {
char[] charArray = string.toCharArray();
//转二进制字符串
int zeroPaddingNum = 0;
StringBuffer sb = new StringBuffer();
for(int i=0;i<charArray.length;i++){
if(base64code.indexOf(String.valueOf(charArray[i]))>-1){
int tempI = base64code.indexOf(String.valueOf(charArray[i]));
String temp = Integer.toBinaryString(tempI);
while(temp.length()<6){
temp = "0"+temp;
}
sb.append(temp);
}else{
sb.append(PADDING_STR_BINARRY2);
zeroPaddingNum++;
}
}
String binarryStr = sb.toString();
StringBuffer retvalue = new StringBuffer();
while(binarryStr.length()>0){
String str = binarryStr.substring(0,8);
int temp = Integer.valueOf(str,2); //二进制转十进制
if(binarryStr.length()>zeroPaddingNum*8){
retvalue.append((char)temp);
}
binarryStr = binarryStr.substring(8);
}
return retvalue.toString();
}
/**把String转为ASCII码数组
* 1.先把String转为char[]数组
* 2.char数组转为ascii数组
* @param value
* @return
*/
private static Integer[] stringToASCII(String value){
Integer[] retInt = new Integer[value.length()];
char[] charArray = value.toCharArray();
for(int i=0;i<charArray.length;i++){
retInt[i] = (int)charArray[i];
}
return retInt;
}
/**把 ASCII码数组转为二进制字符串
* @param value
* @return
*/
private static String asciiToBinarry(Integer[] asciiArray){
StringBuffer sb = new StringBuffer();
for(Integer i : asciiArray){
String temp = Integer.toBinaryString(i);
while(temp.length()<8){
temp = "0"+temp;
}
sb.append(temp);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(encode("Tom"));
System.out.println(decode(encode("Tom")));
System.out.println(encode("Lucy"));
System.out.println(decode(encode("Lucy")));
System.out.println(encode("hello world"));
}
}
下面是摘自高人的Base64源码:
package cn.com.base;
public class Base64 {
private static final String base64code = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/";
private static final int splitLinesAt = 76;
public static byte[] zeroPad(int length, byte[] bytes) {
byte[] padded = new byte[length]; // initialized to zero by JVM
System.arraycopy(bytes, 0, padded, 0, bytes.length);
return padded;
}
public static String encode(String string) {
String encoded = "";
byte[] stringArray;
try {
stringArray = string.getBytes("UTF-8"); // use appropriate encoding string!
} catch (Exception ignored) {
stringArray = string.getBytes(); // use locale default rather than croak
}
// determine how many padding bytes to add to the output
int paddingCount = (3 - (stringArray.length % 3)) % 3;
// add any necessary padding to the input
stringArray = zeroPad(stringArray.length + paddingCount, stringArray);
// process 3 bytes at a time, churning out 4 output bytes
// worry about CRLF insertions later
for (int i = 0; i < stringArray.length; i += 3) {
int j = ((stringArray[i] & 0xff) << 16) +
((stringArray[i + 1] & 0xff) << 8) +
(stringArray[i + 2] & 0xff);
encoded = encoded + base64code.charAt((j >> 18) & 0x3f) +
base64code.charAt((j >> 12) & 0x3f) +
base64code.charAt((j >> 6) & 0x3f) +
base64code.charAt(j & 0x3f);
}
// replace encoded padding nulls with "="
return splitLines(encoded.substring(0, encoded.length() -
paddingCount) + "==".substring(0, paddingCount));
}
public static String splitLines(String string) {
String lines = "";
for (int i = 0; i < string.length(); i += splitLinesAt) {
lines += string.substring(i, Math.min(string.length(), i + splitLinesAt));
lines += "\r\n";
}
return lines;
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.err.println("encoding \"" + args[i] + "\"");
System.out.println(encode(args[i]));
}
}
}
其中核心算法一系列的左移与右移运算,实现是理解不了!这个必须得有高人指点。
参考资料:
http://snowolf.iteye.com/blog/379860
http://my.eoe.cn/indexer/archive/1055.html
http://www.iteye.com/topic/605714
http://java.chinaitlab.com/Special/javajm/Index.html
http://www.wikihow.com/Encode-a-String-to-Base64-With-Java