首先,我们需要知道几个基本概念:
凯撒密码作为一种最为古老的加密技术,在古罗马的时候都已经很流行,他的基本思想是:通过把字母移动一定的位数来实现加密和解密。明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。
例如,当偏移量是 3 的时候,所有的字母 A 将被替换成 D,B 变成 E,由此可见,位数就是凯撒密码加密和解密的密钥。 字符串”ABC”的每个字符都右移 3 位则变成”DEF”,解密的时候”DEF”的每个字符左移 3 位即能还原,如下图所示:
文件目录结构:
CasarDemo.java
package com.itheima.encrypt;
public class CaesarDemo {
public static void main(String[] args) {
//1.明文原始信息
String clearTest="heima";
//2.加密规则:将字母按字母表的顺序向右移动3位
int key=3;
//通过加密算法将明文混要之后的信息
String cipher=encrypt(clearTest,key);
System.out.println(cipher);
//解密规则:将字母按照字母表的顺序向左移动3位
String clear=decrypt(cipher,key);
System.out.println(clear);
}
private static String encrypt(String clearTest,int key) {
char[] charArray=clearTest.toCharArray();
//System.out.println(charArray);
for(int i=0;icharArray[i]+=key;
}
return new String(charArray);
}
private static String decrypt(String cipher,int key) {
char[] charArray=cipher.toCharArray();
//System.out.println(charArray);
for(int i=0;icharArray[i]-=key;
}
return new String(charArray);
}
}
加密和解密都使用同一把密钥,这种加密方法称为对称加密,也称为单密钥加密。
简单理解为:加密解密都是同一把钥匙。
上节中学到的凯撒密码就属于对称加密,他的字符偏移量即为密钥。
基于“对称密钥”的加密算法主要有DES算法,3DES算法,AES算法,Blowfish算法,RC5算法和IDEA算法等等,我们这里主要介绍DES算法和AES算法。
对称密码当中有几种常用到的数学运算。
◆移位和循环移位 移位就是将一段数码按照规定的位数整体性地左移或右移。循环右移就是当右移时,把数码的最后的位移到 数码的最前头,循环左移正相反。例如,对十进制数码12345678循环右移1位(十进制位)的结果为81234567, 而循环左移1位的结果则为23456781。
◆置换 就是将数码中的某一位的值根据置换表的规定,用另一位代替。它不像移位操作那样整齐有序,看上去杂乱无章。这正 是加密所需,被经常应用。
◆扩展 就是将一段数码扩展成比原来位数更长的数码。扩展方法有多种,例如,可以用置换的方法,以扩展置换表来规定扩展后的 数码每一位的替代值。
◆压缩 就是将一段数码压缩成比原来位数更短的数码。压缩方法有多种,例如,也可以用置换的方法,以表来规定压缩后的数码 每一位的替代值。
◆异或 这是一种二进制布尔代数运算。异或的数学符号为⊕ ,它的运算法则如下: 1⊕1 = 0 0⊕0 = 0 1⊕0 = 1 0⊕1 = 1 也可以简单地理解为,参与异或运算的两数位如相等,则结果为0,不等则为1。
◆迭代 迭代就是多次重复相同的运算,这在密码算法中经常使用,以使得形成的密文更加难以破解。
总结:这些运算主要是对比特位进行操作,其共同目的就是把被加密的明文数码尽可能深地打乱,从而加大破译的难度。
①.简介
DES是Data Encryption Standard(数据加密标准)的缩写。它是由IBM公司研制的一种对称密码算法,美国国家标准局于1977年公布把它作为非机要部门使用的数据加密标准,三十年来,它一直活跃在国际保密通信的舞台上,扮演了十分重要的角色。
DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。它的密钥长度是56位(因为每个第8 位都用作奇偶校验),密钥可以是任意的56位的数,其保密性依赖于密钥。
DES加密的算法框架大致如下:
首先要生成一套加密密钥,从用户处取得一个64位长的密码口令,然后通过等分、移位、选取和迭代形成一套16个加密密钥,分别供每一轮运算中使用。
DES对64位(bit)的明文分组M进行操作,M经过一个初始置换IP,置换成m0。将m0明文分成左半部分和右半部分,各32位长。
然后进行16轮完全相同的运算(迭代),在每一轮运算过程中数据与相应的密钥结合。
经过16轮迭代后,左、右半部分合在一起经过一个末置换(数据整理),这样就完成了加密过程。 加密流程如图所示:
简单的流程图:
package com.itheima.encrypt;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;public class DESDemo2 {
public static void main(String[] args) throws Exception {
/**
* 1.明文
* 2.提供原始密码:长度64,8个字节
*/
String clearText ="Thanks heima";
String originKey="123456";
String cipherText=desEncript(clearText,originKey);
System.out.println(cipherText);
}
/**
* 用DES算法进行加密
* Cipher
* @param clearText
* @param originKey
* @return NoSuchAlgorithmException:没有此算法异常
* @return NoSuchPaddingException:填充异常
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/private static String desEncript(String clearText, String originKey)throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
//1.获取加密算法工具类对象
Cipher cipher =Cipher.getInstance("DES");
//2.对工具类对象进行初始化
//mode:加密/解密模式
//key:对原始秘钥处理之后的秘钥
SecretKeySpec key=getKey(originKey);
cipher.init(Cipher.ENCRYPT_MODE, key);
//用加密工具类对象对明文进行加密--》密文
byte[] doFinal = cipher.doFinal(clearText.getBytes());
return new String(doFinal);
}
/**
* 不论originkey多长,我们都要形成一个8个字节长度的原始秘钥
* @param originKey
* @return
*/
private static SecretKeySpec getKey(String originKey) {
//byte数组每个元素默认初始值为0
byte[] buffer = new byte[8];
//获取用户提供的原始秘钥字节数组
byte[] originBytes=originKey.getBytes();
//如果originBytes.length > 8,只要8个字节,如果没有超过8个字节,就用默认初始值来填充
for(int i=0;i<8 && ibuffer[i]=originBytes[i];
}
SecretKeySpec key=new SecretKeySpec(buffer,"DES");
return key;
}
}
加密后的结果是字节数组,这些被加密后的字节在码表(例如GBK、 UTF-8 码表)上找不到对应字符,会出现乱码,当乱码字符串再次转换为字节数组时,长度会变化,导致解密失败,所以转换后的数据是不安全的。
使用 Base64 对字节数组进行编码,任何字节都能映射成对应的 Base64 字符,之后能恢复到字节数组,利于加密后数据的保存和传输,所以转换是安全的。
Base64码表如下:
a、当字符串字符个数为3的整数倍时:
比如字符串“ABC”,其在计算机内存中的 十六进制表示为 41、42、43,十进制表示为“65” “66” “67”;二进制表示为
01000001 01000010 01000011 将这三个二进制数依次取6bit
010000/01 0100/0010 01/000011 就转换成了:
010000 010100 001001 000011,将这四个二进制数转换成十六制数为:
10,14,9,3,十进制数位为16,20,9,3。对照上面的码表,分别查找出对应的字符为Q,U,J,D。
也是就说字符串“ABC”经过BASE64编码后得 出“QUJD”。这是最简单的情况,即ASCII码字符数刚好可以被3整除。接着继续讨论余数为2、为1的情况。
b、当字符串字符个数除以3余数为2时:
比如字符串“ce”,其在内存中十六进制表示为63,65;十 进制表示表示99,101;二进制表示为
01100011 01100101 依次取6bit
011000/11 0110/0101 这时,第3个字符不足6位,在后面补零,也就 是0101变成010100。转换结果为
011000 110110 010100 这3个二进 制数转换成十六制数为18,36,14;
十进制数位为24,54,20。对照码表 得出结果“Y2U”。编码后的字符个数不足4位,用“=”填充,最后编码得出“Y2U=”。
c、当余数为1时:
比如字符串“{”,其在内存中的十六进制表示为$7B,十进制为 123,二进制位表示为
01111011 依次取6bit
011110/11 补0后为
011110/110000 转换结果为011110和110000。这两个二进制数转换成
十六进制数为1E,30,十进制数为30,48。对照码表得出结果为“ew”,补上
“=”,最后编码得出“ew= =”。解码也很简单,是编码的逆过程,即将每 个字符对照码表换算成6bit的二进制数,然后重组起来,按8位进行截取,得出原码。
package com.itheima.encrypt;
import java.io.*;
public class Base64 {
private static char[] base64EncodeChars = new char[] {
'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', '+', '/' };
private static byte[] base64DecodeChars = new byte[] {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 };
public static String encode(byte[] data) {
StringBuffer sb = new StringBuffer();
int len = data.length;
int i = 0;
int b1, b2, b3;
while (i < len) {
b1 = data[i++] & 0xff;
if (i == len)
{
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
sb.append("==");
break;
}
b2 = data[i++] & 0xff;
if (i == len)
{
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
sb.append("=");
break;
}
b3 = data[i++] & 0xff;
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
sb.append(base64EncodeChars[b3 & 0x3f]);
}
return sb.toString();
}
public static byte[] decode(String str) throws UnsupportedEncodingException {
StringBuffer sb = new StringBuffer();
byte[] data = str.getBytes("US-ASCII");
int len = data.length;
int i = 0;
int b1, b2, b3, b4;
while (i < len) {
/* b1 */
do {
b1 = base64DecodeChars[data[i++]];
} while (i < len && b1 == -1);
if (b1 == -1) break;
/* b2 */
do {
b2 = base64DecodeChars[data[i++]];
} while (i < len && b2 == -1);
if (b2 == -1) break;
sb.append((char)((b1 << 2) | ((b2 & 0x30) >>> 4)));
/* b3 */
do {
b3 = data[i++];
if (b3 == 61) return sb.toString().getBytes("ISO-8859-1");
b3 = base64DecodeChars[b3];
} while (i < len && b3 == -1);
if (b3 == -1) break;
sb.append((char)(((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));
/* b4 */
do {
b4 = data[i++];
if (b4 == 61) return sb.toString().getBytes("ISO-8859-1");
b4 = base64DecodeChars[b4];
} while (i < len && b4 == -1);
if (b4 == -1) break;
sb.append((char)(((b3 & 0x03) << 6) | b4));
}
return sb.toString().getBytes("ISO-8859-1");
}
}
package com.itheima.encrypt;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;public class DESDemo {
public static void main(String[] args) throws Exception {
/**
* 1.明文
* 2.提供原始密码:长度64,8个字节
*/
String clearText ="heimanihao";
String originKey="12345678333";
String cipherText=desEncript(clearText,originKey);
System.out.println(cipherText);
String clearText2=desDecript(cipherText,originKey);
System.out.println(clearText2);
}
private static String desDecript(String cipherText, String originKey) throws Exception {
// TODO Auto-generated method stub
Cipher cipher = Cipher.getInstance("DES");
Key key=getKey(originKey);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decode=Base64.decode(cipherText);
byte[] doFinal = cipher.doFinal(decode);
return new String(doFinal);
}
/**
* 用DES算法进行加密
* Cipher
* @param clearText
* @param originKey
* @return NoSuchAlgorithmException:没有此算法异常
* @return NoSuchPaddingException:填充异常
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/private static String desEncript(String clearText, String originKey)throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
//1.获取加密算法工具类对象
Cipher cipher =Cipher.getInstance("DES");
//2.对工具类对象进行初始化
//mode:加密/解密模式
//key:对原始秘钥处理之后的秘钥
SecretKeySpec key=getKey(originKey);
cipher.init(Cipher.ENCRYPT_MODE, key);
//用加密工具类对象对明文进行加密--》密文
byte[] doFinal = cipher.doFinal(clearText.getBytes());
//return new String(doFinal);
String encode=Base64.encode(doFinal);
return encode;
}
/**
* 不论originkey多长,我们都要形成一个8个字节长度的原始秘钥
* @param originKey
* @return
*/
private static SecretKeySpec getKey(String originKey) {
//byte数组每个元素默认初始值为0
byte[] buffer = new byte[8];
//获取用户提供的原始秘钥字节数组
byte[] originBytes=originKey.getBytes();
//如果originBytes.length > 8,只要8个字节,如果没有超过8个字节,就用默认初始值来填充
for(int i=0;i<8 && ibuffer[i]=originBytes[i];
}
SecretKeySpec key=new SecretKeySpec(buffer,"DES");
return key;
}
}
DES 的安全性首先取决于密钥的长度。密钥越长,破译者利用穷举法搜索密钥的难度就越大。DES采用64bit分组长度和56bit密钥长度:
随着软硬件技术的发展,多核CPU、分布式计算、量子计算等理论的实现,DES在穷举方式的暴力攻击下还是相当脆弱的,因此很多人想办法用某种算法替代它,面对这种需求广泛被采用的有两种方案: 1. 设计一套全新的算法,例如AES,这个在后面会说到。 2. 为了保护已有软硬件的投资,仍使用DES,但使用多个密钥进行多次加密,这就是多重DES加密。
例如后来演变出的 3-DES 算法使用了 3 个独立密钥(密钥长度为168bit)进行三重 DES 加密,这就比 DES 大大提高了安全性。如果 56 位 DES 用穷举搜索来破译需要 2∧56 次运算,而 3-DES 则需要 2∧112 次。
a.简介
3DES缺点是算法运行相对较慢。因为原来DES是为70年代的硬件设计的,算法代码并不是很高效,而3DES是DES算法的3轮迭代,因此更慢。而且DES和3DES的分组大小都是64bit,3DES密钥长度却是168bit,处于加密效率和安全的考虑,需要更大的分组长度。于是AES应运而生。
Advanced Encryption Standard 高级加密标准,该标准是美国国家标准技术研究所于2001年颁布的。AES旨在取代DES成为广泛使用的标准,2006年AES已成为最流行的对称加密算法。
AES使用的分组大小为128bit,密钥长度可以为128、192、256 bit。最简单最常用的也就是 128 bit(16字节) 的密钥。
b.算法原理
AES 加密过程涉及到 4 种操作:字节替代(SubBytes)、行移位(ShiftRows)、列混淆(MixColumns)和轮密钥加(AddRoundKey)。
解密过程分别为对应的逆操作。由于每一步操作都是可逆的,按照相反的顺序进行解密即可恢复明文。加解密中每轮的密钥分别由初始密钥扩展得到。算法中 16(byte)字节的明文、密文和轮密钥都以一个 4x4 的矩阵表示。
c.代码实现
AES的java实现比较简单,只需要把密码设置为16byte,然后将DES算法实现的java代码中,将DES修改为AES即可。
package com.itheima.encrypt;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;public class AESDemo {
public static void main(String[] args) throws Exception {
/**
* 1.明文
* 2.提供原始密码:长度128,16个字节
*/
String clearText ="zhangjunjie";
String originKey="1234567887654321";
String cipherText=aesEncript(clearText,originKey);
System.out.println(cipherText);
String clearText2=aesDecript(cipherText,originKey);
System.out.println(clearText2);
}
private static String aesDecript(String cipherText, String originKey) throws Exception {
// TODO Auto-generated method stub
Cipher cipher = Cipher.getInstance("aes");
Key key=getKey(originKey);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decode=Base64.decode(cipherText);
byte[] doFinal = cipher.doFinal(decode);
return new String(doFinal);
}
/**
* 用aes算法进行加密
* Cipher
* @param clearText
* @param originKey
* @return NoSuchAlgorithmException:没有此算法异常
* @return NoSuchPaddingException:填充异常
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/private static String aesEncript(String clearText, String originKey)throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
//1.获取加密算法工具类对象
Cipher cipher =Cipher.getInstance("aes");
//2.对工具类对象进行初始化
//mode:加密/解密模式
//key:对原始秘钥处理之后的秘钥
SecretKeySpec key=getKey(originKey);
cipher.init(Cipher.ENCRYPT_MODE, key);
//用加密工具类对象对明文进行加密--》密文
byte[] doFinal = cipher.doFinal(clearText.getBytes());
//return new String(doFinal);
String encode=Base64.encode(doFinal);
return encode;
}
/**
* 不论originkey多长,我们都要形成一个16个字节长度的原始秘钥
* @param originKey
* @return
*/
private static SecretKeySpec getKey(String originKey) {
//byte数组每个元素默认初始值为0
byte[] buffer = new byte[16];
//获取用户提供的原始秘钥字节数组
byte[] originBytes=originKey.getBytes();
//如果originBytes.length > 8,只要8个字节,如果没有超过8个字节,就用默认初始值来填充
for(int i=0;i<16 && ibuffer[i]=originBytes[i];
}
SecretKeySpec key=new SecretKeySpec(buffer,"aes");
return key;
}
}
到这里,我们的课程也即将告一段落了。最后大致的总结一下三种对称加密算法:
DES作为安全性最弱的算法已经在实际运用面临淘汰,而3DES依然在已有的软硬件中返回余热。不过3DES效率太低,在更多的领域被安全和效率兼具的AES所替代。
在实际开发中,对称加密算法这部分的运用大致如下图所示:
当然,这只是一个非常简易的模型。我们学习到这,加密也就入门,更多的知识有待于大家进一步发掘!