Base64不是加密算法,不适合用在加密场景下,但是Base64的效率不错适合用在一些特殊场景下,Base64不是信息摘要算法,但是比较适合整理在信息摘要技术类中。
Base64的产生是解决电子邮件传输问题的,因为最早的电子邮件只允许ASCII码字符,这样如果传输非ASCII码内容,很可能发生乱码;
Base64是双向的,即可以解码;
Base64算法是一种基于64个字符的编码,定义在RFC 2045文档中:
Base64内容传送编码是一种以任意8位字节序列组合的描述形式,这种描述不易被人直接识别;
经过Base64编码的内容会比原来数据略长,为原来的4/3倍;
经过Base64编码的字符串的字符数是以4为单位的整数倍;
=作为Base64的补位符,一般结尾会包含=号,即Base64编码;
Base64的结尾最多会有两个“=”号;
Base64还有很多衍生算法,如Base32、Base16,Http的Get传输时对字符进行Base64编码算法是一种衍生算法:Url Base64。
Url Base64:算法主要替换了Base64字符映射表中的“+”和“/”,将这两个字符替换为“-”和“_”;
补位符:Url Base64有两种补位,“.”和“~”,两种都有实现,Commons Codec完全杜绝补位符;
URL Base64是Base64的变种,目前应用也很广泛。URL Base64仅仅是将一些在URL中不允许或容易引起歧义的字符做了替换:
关于“=”的替换符没有严格标准,规范仅仅做了建议,具体要看自己的使用场景做决定,这要是各个Base64工具的差异地方。
Base64算法的大部分适用场景即解决乱码,不时候作为加密手段。
RFC 2045定义了Base64标准,但是有些实现是不严格遵循的,比如规范要求在编码的字符串末尾添加回车换行符;
很多时候我们不需要遵循标准,所以看自己的场景选择工具类!
Base64也普遍用在如下场景:
整个过程有两个关键的地方,1是分组转换过程,2是编码映射表,分组转换过程很标准,编解码时双向可逆,但是字符编码映射表要保持一致,否则会解码出错。
下表示Base64的映射表(规范定义,非假设):
十进制码 | 字符 | 十进制码 | 字符 | 十进制码 | 字符 | 十进制码 | 字符 |
---|---|---|---|---|---|---|---|
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 | m | 54 | 2 |
4 | E | 21 | V | 38 | l | 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 | 58 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | M | 28 | c | 45 | t | 62 | + |
12 | L | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | (pad) | = |
14 | O | 31 | f | 48 | w | url base64 | +换为- |
15 | P | 32 | g | 49 | x | url base64 | /换为_ |
16 | Q | 33 | h | 50 | y | url base64 | = 处理有不同 |
输入:abc
下面的代码简单演示了Base64的过程,代码仅仅是根据Base64的算法描述做的伪代码,不可用于实际生产。
package com.sailen.research.encry;
import java.util.HashMap;
import java.util.Map;
/**
* @author : qiesai
* @createTime : 2017/3/22
*/
public class Base64Test {
private static Map table = new HashMap();
static {
char begin = 'A';
char begin2 = 'a';
int idx = 0;
for (int i = 0; i < 26; i++, idx++) {
char v = (char) (begin + i);
table.put(idx, v + "");
char v2 = (char) (begin2 + i);
table.put(idx + 26, v2 + "");
}
idx = 52;
for (int i = 0; i < 10; i++, idx++) {
table.put(idx, "" + i);
}
idx++;
table.put(idx, "+");
idx++;
table.put(idx, "/");
// 特殊处理,不合规的:
idx++;
table.put(idx, "=");
log(table.toString());
}
public static void main(String[] args) {
// 对于非ASCII的编码是有问题的
// String input = "Java加密与解密的艺术";
String input = "A";
String binaryStr = getBinaryString(input);
String[][] group46 = convert2Group46(binaryStr);
String rs = getReslut(group46);
log("%s -> %s", input, rs);
}
/**
* 将给定的字符串转换为二进制字符串:
* 1.将字符串转换为字符数组;
* 2.将字符数组转换为ASCII码,将ASCII码转换为二进制编码;
* 3.如果二进制码不足8位要补0
*
* @param input
* @return
*/
public static String getBinaryString(String input) {
// 1. 将输入字符转换为字符数组
char[] inputChars = input.toCharArray();
StringBuilder binaryBuilder = new StringBuilder();
for (char c : inputChars) {// 将字符转换为ascii编码,再转换为二进制码.
Integer charASCII = Integer.valueOf(c);
String charBinary = Integer.toBinaryString(charASCII);
// 这里获得二进制码是7位,因为高位为0,所以添加个0,如果已经是8位不用添加0
int x = 8 - (charBinary.length() % 8);
for (int i = 0; i < x; i++) {
binaryBuilder.append('0');
}
binaryBuilder.append(charBinary);
log("%s -> %s -> %s", c, charASCII, charBinary);
}
// 不足6位,低位补0
int m = 6 - (binaryBuilder.length() % 6);
for (int i = 0; i < m; i++) {
binaryBuilder.append('0');
}
String binaryStr = binaryBuilder.toString();
log("binary string : %s ", binaryStr);
return binaryStr;
}
/**
* 将给定的字符串转换为4-6的分组:
* 1.二进制字符串长度一定是24的倍数
* 2.计算可以分多个3-8分组;
* 3.将3-8分组转换为4-6分组,并在高位补2个0;
*
* @param binaryStr
* @return
*/
public static String[][] convert2Group46(String binaryStr) {
// 2.进行分组转换3-8的分组,转换为4-6分组
int len = binaryStr.length();
if (len % 6 != 0) {
throw new RuntimeException("Error , Binary String Length Is Not Multiples 8, len : " + len);
}
// 计算能以3-8的分组,能分多少组,少于24位;
int groupNum = len / (3 * 8);
if (len % 24 != 0) {
groupNum += 1;
}
String[][] group46 = new String[groupNum][4];
int idx = 0;
for (int gn = 0; gn < groupNum; gn++) {// 对每个3-8的分组进行转换
for (int unitIdx = 0; unitIdx < 4; unitIdx++, idx += 6) {// 转换为4-6的分组,并对高位补2个0
int end = idx + 6;
group46[gn][unitIdx] = "00";
if (end <= len) {
group46[gn][unitIdx] += binaryStr.substring(idx, idx + 6);// 每次读取6位
} else {
group46[gn][unitIdx] += "01000001";// 每次读取6位
}
log("4-6Group[%s][%s] -> %s", gn, unitIdx, group46[gn][unitIdx]);
}
}
log("Get 46Group Num : %s", group46.length);
return group46;
}
public static String getReslut(String[][] group46) {
int groupNum = group46.length;
int[][] decimalGroup = new int[groupNum][4];
// 将4-6分组补位后的二进制转换为10进制编码,并在映射表中寻找对应的字符
StringBuilder rs = new StringBuilder();
for (int gn = 0; gn < groupNum; gn++) {
for (int unitIdx = 0; unitIdx < 4; unitIdx++) {// 转换为十进制编码
decimalGroup[gn][unitIdx] = Integer.parseInt(group46[gn][unitIdx], 2);
String val = table.get(decimalGroup[gn][unitIdx]);
rs.append(val);
log("4-6Group[%s][%s] -> %s -> %s", gn, unitIdx, group46[gn][unitIdx], val);
}
}
log(rs.toString());
return rs.toString();
}
private static void log(Object msg) {
System.out.println(msg);
}
private static void log(String tmp, Object... params) {
System.out.println(String.format(tmp, params));
}
}
下表列举了在实际项目中可以使用的Base64工具:
工具位置 | 特点 | 适用性 |
---|---|---|
java.util.Base64 | JDK 1.8 加入到util包中 | 适用JDK 1.8后可以使用 |
Commons Codec | Java加解密领域很出名的包,可以选择是否使用RFC-2045的标准规范进行编解码 | 很多框架在用,支持的算法很多,Base32等都支持 |
Spring等框架自己的 | JDK 1.8前Java不支持Base64,为了不和其他框架绑定,自己推出 | 不建议用这些框架的,vendor lock-in |
自己定义 | 自己书写底层框架,尤其是大项目,即不绑定死JDK,也不绑定死框架 | 要注意多测试,考虑Url Base64的特点 |