//TODO 修改Decoder的静态方法,禁止提供返回String的方法,原因:在AES加密时才发现自己一直的一个误区,new String()和getBytes()两者操作得到的字节数组不一定都是一样的,应该先getBytes(),才能去new String(),在测试AES加密时,用这个Base64工具类对AES产生的数组进行加密时没问题,解密时返回字符串就错了,因为这个数组不是getBytes()生成的,new String()时,数组中的字节和编码表并不能一一对应,那么你new String()后,再次getBytes()数组就会变化,所以如果有中间其他调用的时候,Decoder不能提供返回String的方法,可以看到所有java 实现的Base64解密的Decoder类都没有提供返回String的方法
修复不支持中文问题,原理在转字节数组的时候 & 0xff (1111 1111)实际上是将负数转为显示成不考虑符号位的负数的补码形式,正数不受影响
//编码实例:"foobar1",请按标注的数字从1-5看,1.将字符串转为字节数组,2将字节数组换成二进制字符串,3.处理字节数组转为使每个元素长度为8,4.拼接数组为一个大字符串,
// 然后每6个长度为一组,最后一个如果长度不够,后面补0,每两个0记一个"=",并放入数组,5.最后将二进制字符串转为十进制,当做base64表中的下标找对应的值拼接起来,如果后面补0,则在后面拼接上"=",
// 最后可以直接返回字符串,或返回字符串的字节数组
//解码过程就是倒着来
// 5 index列:[25, 38, 61, 47, 24, 38, 5, 50, 12, 16]
// 4 二进制字符串按6分组:[011001, 100110, 111101, 101111, 011000, 100110, 000101, 110010, 001100, 010000]
// 3 字符串转数组,按8补0后:[01100110, 01101111, 01101111, 01100010, 01100001, 01110010, 00110001]
// 2 字符串转数组:[1100110, 1101111, 1101111, 1100010, 1100001, 1110010, 110001]
// 1 待解码的文字的字节数组:[102, 111, 111, 98, 97, 114, 49]
/**
* Base 64 编码规则:
* 1. 将byte数组的值转为二进制并放入数组,将数组中的每个元素的长度变成8个bit位,(即如果长度是6,则在值前面补两个0),最后按顺序拼接成一个完整的字符串
* 2.将字符串以6位分组,不足的,要在末尾补0,达到6的倍数(记下补0的次数)
* 3.将 每个分组的字符串拿出来,转为十进制以这个为下标,去查表,取得所需的对应编码值
* 4.将值(可以拼接成字符串,也可以byte数组的形式返回)如果第二部在末尾补0了,每补"00",就在值后拼接一个'='
* @param binaryStrArr
* @return
*/
根据根据rfc 4648和rfc 2045
package com.util.math; import java.util.Arrays; import com.util.ArrayUtil; import com.util.StringUtil; /** * TODO 暂时不支持中文的编码转换 * @author Administrator * */ public class Base64 { /** * Base 64值参考表 值 编码 值 编码 值 编码 值 编码 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 */ //根据rfc 4648和rfc 2045 private static final byte[] STANDARD_ENCODE_TABLE = { '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', '+', '/' }; public static void main(String[] args) { String encode = Encoder.encode("中"); //System.out.println(encode); //String decode = Decoder.decode("dGVzdHRlc3R0ZXN0dGVzdA=="); //System.out.println(decode); System.out.println(new String(Base64_2.getEncoder().encode("测试测试".getBytes()))); } //编码实例:"foobar1",请按标注的数字从1-5看,1.将字符串转为字节数组,2将字节数组换成二进制字符串,3.处理字节数组转为使每个元素长度为8,4.拼接数组为一个大字符串, // 然后每6个长度为一组,最后一个如果长度不够,后面补0,每两个0记一个"=",并放入数组,5.最后将二进制字符串转为十进制,当做base64表中的下标找对应的值拼接起来,如果后面补0,则在后面拼接上"=", // 最后可以直接返回字符串,或返回字符串的字节数组 //解码过程就是倒着来 // 5 index列:[25, 38, 61, 47, 24, 38, 5, 50, 12, 16] // 4 二进制字符串按6分组:[011001, 100110, 111101, 101111, 011000, 100110, 000101, 110010, 001100, 010000] // 3 字符串转数组,按8补0后:[01100110, 01101111, 01101111, 01100010, 01100001, 01110010, 00110001] // 2 字符串转数组:[1100110, 1101111, 1101111, 1100010, 1100001, 1110010, 110001] // 1 待解码的文字的字节数组:[102, 111, 111, 98, 97, 114, 49] public static class Encoder{ public static String encode(String str){ // System.out.println("待编码的文字的字节数组:"+Arrays.toString(str.getBytes())); return encode(str.getBytes()); } public static String encode(byte[] b){ String strArr [] = new String[b.length]; for (int i = 0; i < b.length; i++) { strArr[i] = DecimalConversion.decimalToOther(2, String.valueOf((byte)b[i] & 0xff));//& 0xff(1111 1111)实际上是将负数转为显示成不考虑符号位的负数的补码形式,正数不受影响 } String encodeByte = encode0(strArr); return encodeByte; } public static byte[] encodeByte(String str){ return encode(str).getBytes(); } public static byte[] encodeByte(byte[] b){ return encode(b).getBytes(); } /** * Base 64 编码规则: * --:错误1.将byte数组中的值转为二进制,将所有二进制数的长度相加,看长度是否为8的倍数,如果不足,则在每个数组元素的二进制前面补0,直到长度满足8的倍数 * 1. 将byte数组的值转为二进制并放入数组,将数组中的每个元素的长度变成8个bit位,(即如果长度是6,则在值前面补两个0),最后按顺序拼接成一个完整的字符串 * 2.将字符串以6位分组,不足的,要在末尾补0,达到6的倍数(记下补0的次数) * 3.将 每个分组的字符串拿出来,转为十进制以这个为下标,去查表,取得所需的对应编码值 * 4.将值(可以拼接成字符串,也可以byte数组的形式返回)如果第二部在末尾补0了,每补"00",就在值后拼接一个'=' * @param binaryStrArr * @return */ private static String encode0(String [] binaryStrArr){//二进制字符串 //int类型最大是32位 int len = 0;//标记第二部,以6分组加 0 的次数 String binaryStr = ""; int strArrLength = StringUtil.getStringArrLength(binaryStrArr); // System.out.println("字符串转数组:"+ Arrays.toString(binaryStrArr)); //将二进制的编码,以8位为一组,//不足的前面补0 for (int i = 0; i < binaryStrArr.length; i++) { binaryStrArr[i] = StringUtil.addZeroFirst(binaryStrArr[i], 8); } binaryStr = StringUtil.getLongStrToStrArr(binaryStrArr); //分成6 位一组的字符串,不足末尾补0 int mod6 = binaryStr.length()%6; if(mod6 != 0){ len = 6 - mod6; binaryStr = binaryStr.concat(StringUtil.addZero(len)); } //按6个长度分组 String[] splitForNum = StringUtil.splitForLength(binaryStr, 6); StringBuffer sbf = new StringBuffer(); int lastLen = len/2; //byte [] result = new byte[splitForNum.length + lastLen]; for (int i = 0; i < splitForNum.length; i++) {//将截取的二进制字符串 转十进制 int index = Integer.valueOf( DecimalConversion.otherToDecimal(2, splitForNum[i])); sbf.append((char)STANDARD_ENCODE_TABLE[index]); } //每加 00;则输出结果 加 = for (int i = splitForNum.length; i < splitForNum.length + lastLen; i++) { sbf.append('='); } // System.out.println("字符串转数组,按8补0后:"+ Arrays.toString(binaryStrArr)); // System.out.println("二进制字符串按6分组后:"+ Arrays.toString(splitForNum)); return sbf.toString(); } } public static class Decoder{ /** *4. 将前面得到的字符串进行按8分组,并去除前面多余的0(不去也行) *5. 将每个二进制元素转为十进制,并放入byte数组,(可以直接返回数组,也可以拼接成字符串返回) * * @param str */ public static byte[] decodeByte(String str){ String longStr = decode0(str); //按8分组 String[] binaryEightStr = StringUtil.splitForLength(longStr, 8); // System.out.println("字符串转数组,按8补0后:"+Arrays.toString(binaryEightStr)); byte [] b = new byte[binaryEightStr.length]; for (int i = 0; i < binaryEightStr.length; i++) { binaryEightStr[i] = StringUtil.removeStartingNumStr(binaryEightStr[i], 0);//去除前面多余的0 //转为十进制 int decimalValue = Integer.valueOf(DecimalConversion.otherToDecimal(2, binaryEightStr[i])); b[i] = (byte) decimalValue; } // System.out.println("字符串转数组:"+Arrays.toString(binaryEightStr)); // System.out.println("待解码的文字的字节数组:"+Arrays.toString(b)); return b; //return new Strng(b);//TODO //不能随便new String,必须先getBytes(),然后在new String(),不然得到的字节数组前后会不一致,不然会出现异想不到的后果 } public static byte[] decodeByte(byte[] b){ return decodeByte(new String(b)); } /** * 1. 去除字符串中的"=",并记录下"="出现的次数 * 2. 去除后的字符串,分成字符串数组,每个值去查 base64标准表,得到对应的下标索引,放入到一个数组中 * 3. 将数组拼接成一个完整的二进制字符串,并且去除末尾多余的0(通过"="出现的次数*2) * @param bytes * @return */ private static String decode0(String str){ int indexOf = str.indexOf("="); int equalsCount = 0; if(indexOf != -1){ str = str.substring(0, indexOf); equalsCount = str.length() - indexOf; } byte[] bytes = str.getBytes(); String[] strArr = new String[bytes.length]; String[] binaryStrArr = new String[bytes.length]; for (int i = 0; i < bytes.length; i++) { //返回 -1 的原因,二分查找,要求查找的数组是有序的.... //strArr[i]= String.valueOf(Arrays.binarySearch(STANDARD_ENCODE_TABLE, bytes[i]));//查找对应编码的下标 strArr[i] = String.valueOf(ArrayUtil.searchIndex(STANDARD_ENCODE_TABLE, bytes[i]));// String strIndex = DecimalConversion.decimalToOther(2, strArr[i]); binaryStrArr[i] = StringUtil.addZeroFirst(strIndex, 6); } // System.out.println("index列:"+Arrays.toString(strArr)); // System.out.println("二进制字符串按6分组:"+Arrays.toString(binaryStrArr)); String longStr = StringUtil.getLongStrToStrArr(binaryStrArr); longStr.substring(0,longStr.length() - (equalsCount << 1));//去除末尾添加的0//equals * 2 return longStr; } } }
用到的自定义String工具类:
package com.util; import java.lang.String; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class StringUtil{ /** * 返回string 的单个字符的String 数组形式,而不是字符数组形式 * @param str * @return */ public static String[] getStrings(String str){ int j = str.length(); String [] strArr = new String[j]; for (int i = 0; i < j; i++) { strArr[i] = str.substring(i, i + 1); } return strArr; } /** * 反转 * @param result * @return */ public static String reverse(String result) { StringBuffer sbf = new StringBuffer(); for (int i = 0,j = result.length(); i < j; i++) { sbf.append(result.substring(j-i-1,j-i)); } return sbf.toString(); } /** * 获得反转的单个字符的字符串数组 * @param str * @return */ public static String[] getStringsReverse(String str){ return getStrings(reverse(str)); } /** * 返回传入的数量的"0"字符串 * @param size * @return */ public static String addZero(int size){ StringBuffer sbf = new StringBuffer(); for (int i = 0; i < size; i++) { sbf.append("0"); } return sbf.toString(); } /** * 根据位数自动添加缺少的0,将0补在开头 * 补全二进制的0 * @param size * @return 返回补全后的字符串 */ public static String addZeroFirst(String str,int num){ int mod = str.length() % num; int len = 0; if(mod == 0) return str; else if(mod != 0) len = num - mod; StringBuffer sbf = new StringBuffer(); for (int i = 0; i < len; i++) { sbf.append("0"); } return sbf.toString().concat(str); } /** * 根据位数自动添加缺少的0,将0补在结尾 * 补全二进制的0 * @param size * @return 返回补全后的字符串 */ public static String addZeroEnd(String str,int num){ return reverse(addZeroFirst(reverse(str), num)); } /** * 根据传入长度截取字符串,返回字符串数组 * 注意,位数不够的请自行补0 * @param str * @param num * @return */ public static String[] splitForLength(String str,int num){ /*int numValue = str.length() % num; if(numValue != 0){ str = str.concat(addzero(num - numValue)); }*/ String [] strArr = new String[str.length() / num]; for (int i = 0; i < strArr.length; i++) { strArr[i] = str.substring((0 + i)*num,(1 + i)*num);// 0,6 ; 6,12 } return strArr; } /** * 获得string数组的数组各个元素的长度之和 * @param strArr * @return */ public static int getStringArrLength(String[] strArr){ int count = 0; for (String str : strArr) { count += str.length(); } return count; } /** * 获得字符串数组的各个下标拼接的长字符串 * @param strArr * @return */ public static String getLongStrToStrArr(String[] strArr){ return getLongStrToIndex(strArr,0,strArr.length); } public static String getLongStrToIndex(String[] strArr,int endIndex){//index 索引,下标 return getLongStrToIndex(strArr, 0,endIndex); } public static String getLongStrToIndex(String[] strArr,int startIndex,int endIndex){ String longStr = ""; for (int i = startIndex; i < endIndex; i++) { longStr += strArr[i]; } return longStr; } public static boolean checkEmpty(String str){ if(null != str && !str.equals("")){ return false; } return true; } /** * 去除以特定数字开头的子字符串,直到后面跟着的是其他子字符串, * 返回一个去除后的新字符串 * * @param str * @param num 单个数字,范围:0-9 */ public static String removeStartingNumStr (String str,int num){ return removeStartingStr(str, String.valueOf(num)); } /** * 去除以特定数字结尾的子字符串,直到前面跟着的是其他字符串 * 返回一个去除后的新字符串 * @param str * @param num * @return */ public static String removeEndingNumStr(String str,int num){ String removeStartingNumStr = removeStartingStr(reverse(str),String.valueOf(num)); return reverse(removeStartingNumStr); } /** * 去除以特定字符(注意:只能是单个字符)开头的子字符串,直到后面跟着的是其他子字符串, * 返回一个去除后的新字符串 * @param str * @param childrenStr * @return */ public static String removeStartingStr (String str,String childrenStr){ String[] strings = getStrings(str); Integer index = null; for (int i = 0; i < strings.length; i++) { if(strings[i].equals(childrenStr)){ index = i; }else{ break; } } if(null != index){ str = str.substring(index+1); } return str; } /** * 去除以特定字符(注意:只能是单个字符)结尾的子字符串,直到前面跟着的是其他子字符串, * 返回一个去除后的新字符串 * @param str * @param childrenStr * @return */ public static String removeEndingStr(String str,String childrenStr){ String removeStartingNumStr = removeStartingStr(reverse(str),childrenStr); return reverse(removeStartingNumStr); } }
数组工具类:
package com.util; public class ArrayUtil { /** * 无序数组的查找 * 如果是有序的,Arrays.binarySearch()二分查找 * @param b * @param key * @return 返回该元素在数组中的下标,如果有数组中要查询的值有多个,则返回第一次出现的索引 * -1:表示数组中不存在该值 */ public static int searchIndex(byte[] b,byte key){ byte[] b2 = {key}; String str = new String(b); String keyStr = new String(b2); if(str.contains(keyStr)){ return str.indexOf(keyStr); }else{ return -1; } } /** * 无序数组的查找 * 如果是有序的,Arrays.binarySearch()二分查找 * @param b * @param key * @return 返回该元素在数组中的下标,如果有数组中要查询的值有多个,则返回第一次出现的索引 * -1:表示数组中不存在该值 */ public static int searchIndex2(byte[] b,byte key){ for (int i = 0; i < b.length; i++) { if(b[i] == key){ return i; } } return -1; } }
自定义实现的DecimalConversion工具类,在上篇进制转换类中有https://blog.csdn.net/kzcming/article/details/79649639
java Base64主要实现有:
1.java8.0 中 java.util.Base64 效率较高
2.java8 中的sun.misc.BASE64Decoder和Base64Encoder,因为支持不是很好,以后可能会废弃
3.java6中隐藏较深的javax.xml.bind.DatatypeConverter有两个静态方法:parseBase64Binary和 printBase64Binary
其他三方实现
4.apache的commons-codec 的jar包的org.apache.commons.codec.binary.Base64
5.便是Google Guava库里面的com.google.common.io.BaseEncoding.base64() 这个静态方法;
6是net.iharder.Base64,这个jar包就一个类;
7.号称Base64编码速度最快的MigBase64,
& 0xff 具体解释:
&上0xff(1111 1111)实际上是将负数转为显示成不考虑符号位的负数的补码形式,正数不受影响
举例: - 13 & 0xff = 243; 13 & 0xff = 13;
-13的原码为:1000 1101 (13的原码为:0000 1101,正数的补码原码反码相同,负数的原码为正数的原码将最高位符号位改为1)
-13的反码为:1111 0010 (负数的反码为原码取反,最高位不变)
-13的补码为:1111 0011(负数的补码为原码+1),将此二进制转为十进制:243
而 0xff 的二进制: 1111 1111
进行& 操作,1为true,0为false,两个数转为二进制,从高位开始比较,如果两个数都为1,则为1,否则为0