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