一,概述
前面讲解了大体通用的对称,非对称,MD系列等加密算法。
见前面 我的博客文: https://blog.csdn.net/Mynah886/article/details/82787683
但感觉还是不能直观的让开发者来直接爽起来。最近又看到了其他人分享的,自己项目正好又要涉及使用了。故此重新细化下。供使用时候直接拿来!
对称加密算法就是传统的用一个密码进行加密和解密。就像我们常用的WinZIP && WinRAR 对压缩包的加密和解密,就是使用对称加密算法。
代表算法/实现方式:
1) DES(Data Encryption standard)数据加密标准 3DES, 密钥长度一般 56/64. DES算法由于密钥过短,可以在短时间内被暴力破解,所以现在已经不安全了
2) AES高级DES替代者(工作模式:ECB, CBC, PCBC, CTR, CFB8~128... 填充方式:NoPadding, PKCS5Padding, ISO10126Padding...), 密钥长度一般 128/192/256.
3) RC4/5/6算法: 是参数可变的分组密码算法,三个可变的参数是:分组大小、密钥大小和加密轮数; 加密时使用了2r+2个密钥相关的的32位字: 这里r表示加密的轮数。
4) Blowfish算法: 每次加密一个64位分组,使用32位~448位的可变长度密钥,应用于内部加密,输入64位明文,输出64位密文。加密过程分为两个阶段:密钥预处理和信息加密。 自从32位处理器诞生后,blowfish加密算法在加密速度上就超越了DES加密算法,引起了人们的关注。blowfish加密算法没有注册专利,不需要授权,可以免费使用
5) PBE(Password Based Encryption)基于口令加密 = 口令+盐
6)IDEA,早于AES替代出来取代DES. 密钥长度128 . 工作模式:ECB, 填充方式:PKCS5Padding/PKCS7Padding/... 。实现方式有Bouncy Castle
注意: 密钥长度直接决定加密强度,而工作模式和填充模式可以看成是对称加密算法的参数和格式选择。Java标准库提供的算法实现并不包括所有的工作模式和所有填充模式,但是通常我们只需要挑选常用的使用就可以了。
从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文:
secret = encrypt(key, message);
而解密则相反,它接收密码和密文,然后输出明文:
plain = decrypt(key, secret);
二, 开发DEMO
2.1 我们先用AES加密算法 && ECB模式加密并解密。
Java标准库提供的对称加密接口非常简单,使用时按以下步骤编写代码:
-
根据算法名称/工作模式/填充模式获取Cipher实例;
-
根据算法名称初始化一个SecretKey实例,密钥必须是指定长度;
-
使用SerectKey初始化Cipher实例,并设置加密或解密模式;
-
传入明文或密文,获得密文或明文。
/**
* desc- 对称加密,ECB模式加密并解密
* 加密算法 AES, 填充方式 ECB
* auth xupengfei
*
* @param key 加密key
* @param input 需要加密的字节数组
* @return 加密后的字节数组
*/
public static byte[] encrypt( byte[] key, byte[] input) {
byte[] result = null;
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
result = cipher.doFinal(input);
}catch (Exception ex){
log.error("EX-------- {}", ex);
}
return result;
}
/**
* desc 对称 解密,ECB模式加密并解密
* 加密算法 AES, 填充方式 ECB
* auth xupengfei
*
* @param key 加密key
* @param input 需要解密的字节数组
* @return 解密后的字节数组
* @throws GeneralSecurityException
*/
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
/**
* 测试类
* @param args
* @throws Exception
*/
@Test
public void testAesECB() throws Exception {
// 原文:
String message = "Hello world,I am DCONE member, please call me mynah!";
log.debug("Message: " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "xupengfei1234567".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(key, data);
log.debug("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
log.debug("Decrypted: " + new String(decrypted, "UTF-8"));
}
2.1 测试结果如下:
2.2 ECB模式是最简单的AES加密模式,它只需要一个固定长度的密钥,固定的明文会生成固定的密文,这种一对一的加密方式会导致安全性降低,更好的方式是通过CBC模式,它需要一个随机数作为IV参数,这样对于同一份明文,每次生成的密文都不同。
在CBC模式下,需要一个随机生成的16字节IV参数,必须使用 SecureRandom
生成。因为多了一个IvParameterSpec
实例,因此,初始化方法需要调用Cipher
的一个重载方法并传入IvParameterSpec
。 可以发现每次生成的IV不同,密文也不同。
/**
* desc 对称加密, CBC 模式加密并解密
* 加密算法 AES, 填充方式 CBC
* auth xupengfei
*
* @param key
* @param input
* @return
* @throws GeneralSecurityException
*/
public static byte[] encrypt2(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// CBC模式 要生成一个 16bytes 的initialization vector
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] iv = sr.generateSeed(16);
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
byte[] data = cipher.doFinal(input);
// IV不需要保密,把 IV && 密文一起返回
return join(iv, data);
}
/**
* desc 对称 解密,CBC模式加密并解密
* 加密算法 AES, 填充方式 CBC
* auth xupengfei
*
* @param key
* @param input
* @return
* @throws GeneralSecurityException
*/
public static byte[] decrypt2(byte[] key, byte[] input) throws GeneralSecurityException {
// 把 入参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("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
return cipher.doFinal(data);
}
/**
*
*
* @param bs1
* @param bs2
* @return
*/
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;
}
/**
* 测试类 CBC 模式
*
* @param args
* @throws Exception
*/
@Test
public void testAesCBC() throws Exception {
// 明文
String message = "Hello world,I am DCONE member, please call me mynah, ouye!";
log.debug("Message: " + message);
// 256位密钥 = 32 bytes Key:
byte[] key = "0123456789abcdef0123456789abcdef".getBytes("UTF-8");
// 加密
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(key, data);
log.debug("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密
byte[] decrypted = decrypt(key, encrypted);
log.debug("Decrypted: " + new String(decrypted, "UTF-8"));
}
2.2 测试结果如下: