对称加密算法 - Java加密与安全

对称加密算法

什么是对称加密算法呢?

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. 使用对称加密算法我们还需要指定算法的名称,工作模式,和填充模式,也就是加解密的双方需要约定好参数

猜你喜欢

转载自blog.csdn.net/Leon_Jinhai_Sun/article/details/89916019