最近在深入的学习关于密码学的相关知识,因为密码学中涉及到了加密和解密,学习内容中穿插了关于Base64编码算法的问题。为此,我特地去了解了下关于Base64编码的算法。
关于Base64编码的概念,其实不必我做过多地解释了,这里就贴一下百科的解释:Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。说白一点儿就是在数据加密和解密的过程中,就是把传输数据的每个字节映射成ASCII码表中的某些字符,这样在传输的过程中,就不会出现乱码的问题了。
一、Base64和加密解密算法是如何结合使用的的呢?
这里我们拿DES对称性加密算法结合代码实现给大家看看,DES算法的入口参数有三个:Key、Data、Mode。其中Key为7个字节共56位(实际输入需要8个字节,因为java6对DES算法仅支持56位密钥长度,8个字节的话就是64位,多出的1个字节8位就拿来作为奇偶校验位),是DES算法的工作密钥;Data表示是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。
①DES加密:
通过选择加密模式ENCRYPT_MODE以及定义加密的规则,在doFinal中传入需要经过Base64加密
编码的的参数base64.encode(xxx)。
/**
* DES加密
* @param content 明文
* @param desKey 公共密钥
* @param algorithmType 加密的类型
* @return 通过将明文加密后产生的密文
*/
private static String encryptDES(String content, String desKey, String algorithmType) {
Base64 base64=new Base64();
try {
Cipher cipher = Cipher.getInstance(algorithmType);
//定义加密规则
SecretKeySpec Key = new SecretKeySpec(desKey.getBytes(), algorithmType);
//加密:ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, Key);
//要将传输的明文编码(base64.encode(content.getBytes()))
byte[] bytes = cipher.doFinal(base64.encode(content.getBytes()));
return base64.encodeToString(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
我们可以执行案例,如下:
public static void main(String[] args) {
//明文内容
String content = "Kotlin 是最好使用的语言!!!";
//DES密钥(加密和解密都使用同一个,密钥严格规定是8个字节(64位),否则报 Wrong key size的错误)
String desKey = "25245621";
String algorithmType = "DES";
//DES加密
String ciphertext = encryptDES(content, desKey, algorithmType);
System.out.println("DES加密结果: " + ciphertext);
}
你会发现原来的明文文本内容通过DES加密后,会发现:
> Task :DES.main()
> DES加密结果: IVklbaloTXNLCVRQrQJbjEF/kEpVh9FK/0VE5edkMFuHsHgxylGZS5SaVzFwYg/OsNNC0rd4Ydu6X7XsEDYKCw==
②DES解密:
与加密相反,解密是通过选择解密模式ENCRYPT_MODE以及定义解密的规则,在doFinal中传入需要经过Base64解密
编码的的参数base64.decode(xxx)。
private static String decryptDES(String ciphertext, String desKey, String algorithmType) {
Base64 base64=new Base64();
try {
Cipher cipher = Cipher.getInstance(algorithmType);
SecretKeySpec Key = new SecretKeySpec(desKey.getBytes(), algorithmType);
//DECRYPT_MODE:解密
cipher.init(Cipher.DECRYPT_MODE, Key);
//要将解码的密文编码
byte[] bytes = cipher.doFinal(base64.decode(ciphertext));
return new String(base64.decode(bytes), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
由上面的明文,我们获取到了加密后的得到的密文ciphertext,然后通过此密文我们做进一步的解密的操作
public static void main(String[] args) {
//此处省略其他代码
//DES加密
String ciphertext = encryptDES(content, desKey, algorithmType);
//DES解密
String decryptText = decryptDES(ciphertext, desKey, algorithmType);
System.out.println("DES加密结果: " + ciphertext + "\n DES解密结果: " + decryptText);
}
得到结果如下:
> Task :DES.main()
> DES加密结果: IVklbaloTXNLCVRQrQJbjEF/kEpVh9FK/0VE5edkMFuHsHgxylGZS5SaVzFwYg/OsNNC0rd4Ydu6X7XsEDYKCw==
> DES解密结果: Kotlin 是最好使用的语言!!!
假设我们在加密环节中的doFinal中都去掉Base64的编码的代码,直接传入文本内容转换成的字节数组,其结果如下:
> Task :DES.main()
> DES加密结果: ��:��{��1�4X��(� ��G���c��ra�_��c�dk��_���6�
> DES解密结果: null
通过上面的案例输出的结果,我们发现加密打印出来的结果是一堆乱码,无法让人读懂里面的内容,所以,Base64的本质并不是加密或解密算法,它只是使得加密或解密过程中通过将乱码映射成ASCII码表中的某些字符,令我们对结果更具易读性。
二、Base64的由来是什么,它的算法原理是怎样的?
首先,Base64编码中的“64”的很明显的由来是只支持64种不同可打印字符,这些字符在源码中可以查到,这一般情况下它的64个字符如下:
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', '+', '/'
};
当时用的是URL-SAFE模式时,编码表中的最后两个字符会变成下面的样子:
private static final byte[] URL_SAFE_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', '-', '_'
};
那它为什么是64个可打印字符呢,它的实现算法思路是怎样的呢?以下是我自己的理解,这也仅仅是我自己的个人理解:首先,我们知道正常的一个英文字符是一个字节(1个字节是8位),我们学习的中文的一个字符是两个字节组成的(2个字节是16位),在UTF-8情况下,中文字符等于3个字节,UTF-6时,中文字符等于4个字节,如果拿UTF-8情况下去做字符分组 , 即是分成 3 * 8=24(3个字节 * 单字节8位 = 24位),也就是可以通过二进制表示:00000000 00000000 00000000。因为前面我已经知道一个字符最大可能占用4个字节,在此的前提下,又进一步分组,因为24是8和6的最大公约数,所以分组结果是 4 * 6 = 24(4个字节 * 6 =24位),使用二进制的方式表示:000000 000000 000000 000000,我知道单字节是8位呀,怎么就变成6位了呢,缺少的两位怎么半呀!很简单,缺少的两位就从高位补两个0,这样就满足一个字节8位的条件了。所以就有了表示二进制的方式:00000000 00000000 00000000 00000000,我们从这里面拿一组字节来说一下,最小值是(00000000 min=0),最大值是(00111111 max=63),所以Base64种每组是64种字符。