一、前言声明:本文首发在CSDN的个人博客(点此直达首发),慕课网的手记功能目前还存在一些小瑕疵,所以文章格式怪怪的。虽然CSDN的格式也略怪,但好歹没有漏掉一些符号,建议看原文吧。另,晚些时候再尝试慕课网的手记功能好了。
目前还在找工作,工作日时投投简历面面试,这周末难免就闲来无事了,那就只好看看慕课逛逛CSDN了,正巧看到一个关于Base64的课程《Java实现Base64加密》,点进去看看,完了发觉完全不是我想的那回事儿,人给的实现方式还不唯一,给了3个API,但是没有实现原理。我这个愣头青没别的优点,就是喜欢死钻牛角尖,于是抱着试试的心态,查了官网RFC2045(下载完整PDF),了解了下相关情况,这才有了本文。
二、Base64是什么这种高度概括的事情我相信Base64的百度百科比我靠谱多了,如下:
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。Base64编码可用于在HTTP环境下传递较长的标识信息。例如,在JavaPersistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTPGETURL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。
标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSISQL中已将“%”号用作通配符。
为解决此问题,可采用一种用于URL的改进Base64编码,它在末尾填充'='号,并将标准Base64中的“+”和“/”分别改成了“-”和“”,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。
另有一种用于正则表达式的改进Base64变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“*”以及前面在IRCu中用到的“[”和“]”在正则表达式中都可能具有特殊含义。
此外还有一些变种,它们将“+/”改为“-”或“.”(用作编程语言中的标识符名称)或“.-”(用于XML中的Nmtoken)甚至“:”(用于XML中的Name)。
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(38=46=24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。
原理(这回觉得百度百科不靠谱,引用的是Base64的维基百科)
在MIME格式的电子邮件中,base64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本。使用时,在传输编码方式中指定base64。使用的字符包括大小写字母各26个,加上10个数字,和加号“+”,斜杠“/”,一共64个字符,等号“=”用来作为后缀用途。
完整的base64定义可见RFC1421和RFC2045。编码后的数据比原始数据略长,为原来的4/3。在电子邮件中,根据RFC822规定,每76个字符,还需要加上一个回车换行。可以估算编码后数据长度大约为原长的135.1%(算式1(4/3)((76+1)/76)约等于1.351)。
转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲器中剩下的bit用0补足。然后,每次取出6(因为26=64)个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。
当原数据长度不是3的整数倍时,如果最后剩下一个输入数据,在编码结果后加2个“=”;如果最后剩下两个输入数据,编码结果后加1个“=”;如果没有剩下任何数据,就什么都不要加,这样才可以保证数据还原的正确性。
上面大段的文字概括来讲,可以总结为以下3点:
①把3个8位字节(38=24)转化为4个6位的字节(46=24),之后在6位的前面补两个0,形成8位一个字节的形式。
如果剩下的字符不足3个字节,则用0填充,输出字符使用'=',因此编码后输出的文本末尾可能会出现1或2个'=';
②每76个字节数据后加一个换行符;
③若数据长度除以3余1,则在编码结束时加2个“=”,若数据长度除以3余2,则在编码结束时加1个“=”。
规则
一个是ASCII编码表,如下:
另一个是Base64编码表,如下:
我们还是来看下面这3个例子比较直观(例子下载)。
例子1、假设我们的明文为“Base64”(数据长度为6,正好是3的倍数),则其编码计算方式如下:
例子2、假设我们的明文为“test”(数据长度为4,4%3=1),则其编码计算方式如下:
例子3、假设我们的明文为“JiaMi”(数据长度为5,5%3=2),则其编码计算方式如下:
思路什么的,我觉得就不必要写了,代码里注释得很充分了。
六、代码package com.dikio.base64;
import java.util.List;
import java.util.ArrayList;
public class Base64 {
private static String base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
public static void main(String[] args) {
System.out.println(encode("Base64"));
System.out.println(decode("QmFzZTY0"));
}
public static String encode(String srcData) {
if(srcData == null srcData.length() == 0) {
return srcData;
}
char[] chArr = srcData.toCharArray();
String asciiBin = null;
StringBuilder asciiBin_all = new StringBuilder();
for(int i= 0; i< chArr.length; i++) {
//将字符转换成ASCII编码再转换成对应二进位
asciiBin = Integer.toBinaryString((int)chArr[i]);
//给不足8位的在高位补0直到补足8位
while(asciiBin.length()< 8) {
asciiBin= "0"+ asciiBin;
}
//最后把所有二进位拼接成一个字串
asciiBin_all.append(asciiBin);
}
//若长度不能被6整除,则在低位补0到能被6整除为止
while(asciiBin_all.length()% 6!= 0) {
asciiBin_all.append("0");
}
String asciiBinStr = asciiBin_all.toString();
//按6个一组拆分成字串数组
List<String> bin6List = new ArrayList<String>();
String temp = null;
while(asciiBinStr.length()/ 6> 0) {
temp = asciiBinStr.substring(0, 6);
asciiBinStr = asciiBinStr.substring(6);
bin6List.add(temp);
}
String[] bin6Str = bin6List.toArray(new String[bin6List.size()]);
int[] index = new int[bin6Str.length];
//确定最终补位长度
int overLen = 0;
if(srcData.length()% 3 != 0) {
overLen = 3- srcData.length()% 3;
}
//设定存放最终编码的容器
char[] code = new char[index.length+ overLen];
for(int i= 0; i< index.length; i++) {
//将二进位转换成十进制数字
index[i] = Integer.parseInt(bin6Str[i], 2);
//Base64 : Value -> Encoding
code[i] = base64Code.charAt(index[i]);
}
switch(overLen) {
case 2:code[code.length- 2] = '=';//不需要break
case 1:code[code.length- 1] = '=';
default:
}
return String.valueOf(code);
}
public static String decode(String srcData) {
//检测元数据中“=”的个数,并将之去除
int counter = 0;
if(srcData.contains("=")) {
counter = 1;
if(srcData.substring(srcData.length()- 2, srcData.length()- 1).equals("=")) {
counter = 2;
}
}
srcData = srcData.replaceAll("=", "");
//将密文根据Base64编码表转换成对应Value,再转换成二进位 ,然后将所有二进位补足6位,最后将所有二进位存进一个字串
char[] srcCh = srcData.toCharArray();
StringBuffer bin6SB = new StringBuffer();
int index;
String bin6Str;
for(int i= 0; i< srcCh.length; i++) {
//获得Base64编码表的Value
index = base64Code.indexOf(srcCh[i]);
//将Value转为二进位
bin6Str = Integer.toBinaryString(index);
//在长度不足6位的二进位的高位上补0直到补足6位,再保存进字串
while(bin6Str.length()< 6) {
bin6Str = "0"+ bin6Str;
}
bin6SB.append(bin6Str);
}
String bin6Str_all = bin6SB.toString();
//如果二进位字串后有多补的0,将之去除
if(counter == 1) {
bin6Str_all = bin6Str_all.substring(0, bin6Str_all.length()- 2);
} else if(counter == 2) {
bin6Str_all = bin6Str_all.substring(0, bin6Str_all.length()- 4);
}
//按8个一组拆分成字串数组
List<String> bin8List = new ArrayList<String>();
String temp;
while(bin6Str_all.length()/ 6> 0) {
temp = bin6Str_all.substring(0, 8);
bin6Str_all = bin6Str_all.substring(8);
bin8List.add(temp);
}
String[] bin8Str = bin8List.toArray(new String[bin8List.size()]);
//将该字串数组的每个元素(即一组二进位)转成十进制数,再强制转换成char类型
char[] ascii = new char[bin8Str.length];
for(int i= 0; i< ascii.length; i++) {
ascii[i] = (char)Integer.parseInt(bin8Str[i], 2);
}
return String.valueOf(ascii);
}
}
热门评论
总算找到一个不自相矛盾的代码