对称加密算法
什么是对称加密算法呢?
1. 对称加密算法就是加密和解密使用同一个密钥,例如我们使用WinRAR,对文件进行打包的时候,我们可以设置一个秘密,
在解压的时候需要使用同一个密码,才能够正确的解压,WinRAR使用的加密算法就是一种对称加密算法
对称加密算法在加密的时候,我们需要输入一个key,和原始数据message,然后得到密文s,在解密的时候,
我们需要通过密钥key,和密文s,获得原文message
常用的对称加密算法有DES,AES,IDEA等,他们的密钥长度,各不相同,密钥长度直接决定着加密的长度,另外工作模式
和填充模式可以看成是对称加密的参数和格式选择,JDK提供的算法并没有包括所有的这些模式和所有的填充模式,
但是通常我们只需要选用常用的就可以了,最后我们要注意,DES算法,因为密钥过短,可以在短时间内被暴力破解,
所以现在已经不安全了
package com.learn.securl;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* 如何使用AES的ECB模式进行加密
* @author Leon.Sun
*
*/
public class AES_ECB_Cipher {
/**
* 我们需要指定工作模式为ECB
* 他的填充模式为PKCS5Padding
* 这是JDK支持的一种加密模式
*/
static final String CIPHER_NAME = "AES/ECB/PKCS5Padding";
/**
* 加密:
* 然后在encrypt方法中传入AES的key,
* 它是一个byte数组
* 我们在传入input也是一个byte数组
* @throws Exception
*/
public static byte[] encrypt(byte[] key,byte[] input) throws Exception {
/**
* 传入加密算法的名字
* 我们就得到一个Cipher实例
*/
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
/**
* 紧接着我们通过创建一个SecretKeySpec
* 然后把byte数组转为一个AES的key
*/
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
/**
* 紧接着我们使用cipher.init
* 初始化为加密模式
* 然后传入key
*/
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
/**
* 最后我们通过doFinal
* 就得到了加密以后的加密数组
*/
return cipher.doFinal(input);
}
/**
* 对于解密代码是类似的
* 只要传入key以及加密的输入
* @throws Exception
*/
public static byte[] decrypt(byte[] key,byte[] input) throws Exception {
/**
* 我们仍然通过Cipher.getInstance获得一个Cipher实例
*/
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
/**
* 然后通过把字节数组的key变为一个SecretKeySpec的实例
*/
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
/**
* 在解密的时候我们调用init方法,
* 传入的是ENCRYPT_MODE
*/
cipher.init(Cipher.DECRYPT_MODE, keySpec);
/**
* 最后用doFinal方法就可以 把密文翻译为明文
*/
return cipher.doFinal(input);
}
/**
* 最后我们来编写一个main方法来测试
* @throws Exception
*/
public static void main(String[] args) throws Exception {
/**
* 原文:
*
* 我们的原文是一个String
*/
String message = "Hello, world! encrypted using AES!";
System.out.println("Message: " + message);
/**
* 128位密钥 = 16 bytes Key:
*
* 而我们的密钥是要一个128位的密钥
* 也就是16个字节
* 我们把字符串转换为字节数组
* 然后进行加密
*/
byte[] key = "1234567890abcdef".getBytes("UTF-8");
/**
* 加密:
*/
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encrypt(key, data);
/**
* 我们通过Base64把密文转化为Base64编码
* 6ofAje3dbEseeIBkwKEonQIUi09dPO9fVx4OgZ7ozsE7BWtJJdcJs1+N58l1mWqh
* 以Base64加密后的密文
*/
System.out.println("Encrypted data: " + Base64.getEncoder().encodeToString(encrypted));
/**
* 解密:
*
* 紧接着我们调用decrypt方法进行解密
*/
byte[] decrypted = decrypt(key, encrypted);
/**
* 我们把字节数组打印为原始的字符串
*
* Decrypted data: Hello, world! encrypted using AES!
* 解密后得到的数据和原始的数据是一致的
*/
System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
}
}
package com.learn.securl;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AES_CBC_Cipher {
/**
* 在使用CBC模式的时候
* 我们使用的算法是AES/CBC/PKCS5Padding
*/
static final String CIPHER_NAME = "AES/CBC/PKCS5Padding";
public static byte[] encrypt(byte[] key,byte[] input) throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
/**
* CBC模式需要生成一个16 bytes的Initialization vector:
*
* 我们通过SecureRandom.getInstance可以获得一个SecureRandom的值
*/
SecureRandom sr = SecureRandom.getInstanceStrong();
/**
* 我们注意在使用CBC模式的时候需要
* 这个向量就是一个64字节的随机数
*
* 我们可以通过generateSeed可以获得一个64字节的向量
*/
byte[] iv = sr.generateSeed(16);
/**
* 把字节数组转换为IvParameterSpec对象
*/
IvParameterSpec ivps = new IvParameterSpec(iv);
/**
* 就是在加密的时候传入ENCRYPT_MODE,keySpec,ivps向量
*/
cipher.init(Cipher.ENCRYPT_MODE, keySpec,ivps);
/**
* 然后通过doFinal方法得到密文
*/
byte[] data = cipher.doFinal(input);
/**
* IV不需要保密,把IV和密文一起返回
*
* 在这里需要注意的是iv变量是不需要保密的
* 所以我们把iv和密文data拼在一起返回
*/
return join(iv, data);
}
/**
* 由于input包含的是iv和密文
* 所以我们把它分割成16字节的iv以及密文本身
* @param key
* @param input
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] key,byte[] input) throws Exception {
/**
* 把input分割成IV和密文:
*/
byte[] iv = new byte[16];
byte[] data = new byte[input.length - 16];
System.arraycopy(input, 0, iv, 0, 16);
System.arraycopy(input, 16, data, 0, data.length);
/**
* 解密:
*/
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
/**
* 然后我们可以设置DECRYPT_MODE进行解析
* 代码和Encrtypt_mode是一样的
*/
cipher.init(Cipher.DECRYPT_MODE, keySpec,ivps);
/**
* 这里传入的是data而不是input
*/
return cipher.doFinal(data);
}
public static byte[] join(byte[] bs1, byte[] bs2) {
byte[] r = new byte[bs1.length + bs2.length];
System.arraycopy(bs1, 0, r, 0, bs1.length);
System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
return r;
}
/**
* 最后我们编写一个main方法来测试
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
String message = "Hello, world! encrypted using AES!";
System.out.println("Message: " + message);
byte[] key = "1234567890abcdef".getBytes("UTF-8");
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted data: " + Base64.getEncoder().encodeToString(encrypted));
byte[] decrypted = decrypt(key, encrypted);
/**
* 得到解密以后的明文
* Decrypted data: Hello, world! encrypted using AES!
*/
System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
}
}
package com.learn.securl;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* 在ECB模式下加密
* @author Leon.Sun
*
*/
public class AES256_ECB_Cipher {
/**
* 在这里我们把加密模式指定为AES/ECB/PKCS5Padding
*/
static final String CIPHER_NAME = "AES/ECB/PKCS5Padding";
/**
* 加密
*/
public static byte[] encrypt(byte[] key, byte[] input) throws Exception{
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
/**
* 解密
*/
public static byte[] decrpty(byte[] key, byte[] input) throws Exception{
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
public static void main(String[] args) throws Exception{
// 原文:
String message = "Hello, world! encrypted using AES!";
System.out.println("Message: " + message);
// 256位密钥 = 32 bytes Key:
/**
* 然后我们在生成密钥的时候使用32字节的密钥
* 就是256位的密钥
* InvalidKeyException: Illegal key size or default parameters
* 这个时候我们发现JDK报错他告诉我们一个InvalidKeyException
* 当我们遇到这个错误的时候并不是因为JDK不支持256位AES加密
* 而是默认安装的JDK他不允许你使用256位加密
* 我们需要打开浏览器我们搜索jdk8 jce policy
* 然后找到ORACLE的官方网站
* https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
* 我们打开这个界面
* 我们需要下载
* Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 8 Download
* 文件
* AES256
* 使用256位加密需要修改JDK的policy文件
* https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html下载,
* 切换到目录C:\Program Files\Java\jdk1.8.0_151\jre\lib\security下添加 local_policy.jar
* 和 US_export_policy.jar
* 这样我们的AES的256算法就正常运行了
* 我们之所以要替换这两个policy文件
* 因为受到美国出口法律的限制
* ORCALE提供的JDK中他把加密算法限制在256位以下
* 我们只需要替换local_policy这个文件
* 就可以实现256位以上和更长的加密长度
*/
byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encrypt(key, data);
System.out.println("Enctypted data: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrpty(key, encrypted);
System.out.println("Decrypted data: " + new String(decrypted, "UTF-8"));
}
}
最后我们总结一下:
1. 对称加密算法是指使用同一个密钥进行加密和解密
2. 常用的算法有DES/AES/IDEA等
3. 密钥长度由算法设计的时候决定,AES的密钥长度是128位,192位,或者是256位
4. 使用256位加密的时候,我们需要修改JDK的policy文件
5. 使用对称加密算法我们还需要指定算法的名称,工作模式,和填充模式,也就是加解密的双方需要约定好参数