后端开发 java Java 对称加密算法 iamxun 2020-12-24 2024-11-29 Java 对称加密算法
对称加密算法加密和解密使用的是同一份秘钥,解密是加密的逆运算。对称加密算法加密速度快,密文可逆,一旦秘钥文件泄露,就会导致原始数据暴露。对称加密的结果一般使用Base64算法编码,便于阅读和传输。JDK8支持的对称加密算法主要有DES、DESede、AES、Blowfish,以及RC2和RC4等。不同的算法秘钥长度不同,秘钥长度越长,加密安全性越高。
DES
DES(Data Encryption
Standard,数据加密标准)算法是对称加密算法领域中的典型算法,DES算法秘钥较短,以现在计算机的计算能力,DES算法加密的数据在24小时内可能被破解。所以DES算法已经被淘汰,建议使用AES算法,不过这里还是简单了解下。
JDK8仅支持56位长度的DES秘钥,下面举个JDK8实现DES加密的例子:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "DES"; // 转换模式 String transformation = "DES"; // --- 生成秘钥 --- // 实例化秘钥生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); // 初始化秘钥长度 keyGenerator.init(56); // 生成秘钥 SecretKey secretKey = keyGenerator.generateKey(); // 实例化DES秘钥材料 DESKeySpec desKeySpec = new DESKeySpec(secretKey.getEncoded()); // 实例化秘钥工厂 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); // 生成DES秘钥 SecretKey desSecretKey = secretKeyFactory.generateSecret(desKeySpec); System.out.println("DES秘钥:" + Base64.getEncoder().encodeToString(desSecretKey.getEncoded())); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥 cipher.init(Cipher.ENCRYPT_MODE, desSecretKey); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("DES加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, desSecretKey); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("DES解密结果:" + new String(decrypt)); } }
转换模式transformation这里先设置为和加密算法一样,下面介绍了加密模式和填充模式后再作说明。
上面步骤看着挺多,其实可以总结为如下几步:
生成加密秘钥;
1.1. 通过KeyGenerator生成一个指定位数的秘钥;
1.2. 通过上面生成的秘钥实例化算法对应的秘钥材料KeySpec;
1.3.
使用秘钥材料通过秘钥工厂SecretKeyFactory生成算法秘钥SecretKey。
通过转换模式实例化Cipher;
指定Cipher模式和秘钥,进行加解密操作。
运行结果如下:
1 2 3 4 待加密值:mrbird's blog DES秘钥:LO/CCEC8y8E= DES加密结果:hRZdN6f+6yj/lhPWUPsayA== DES解密结果:mrbird's blog
如果在生成秘钥的时候,不指定为56位,则会抛出java.security.InvalidParameterException:
Wrong keysize: must be equal to 56异常。
DESede
作为DES算法的一种改良,DESede算法(也成为3DES,三重DES)针对其秘钥长度偏短和迭代次数偏少等问题做了相应改进,提高了安全强度,但同时也造成处理速度较慢、秘钥计算时间加长、加密效率不高的问题。所以这里还是简单了解下,实际还是推荐用AES。
JDK8支持112位或168位长度的DESede秘钥,下面举个JDK8实现DESede加密的例子:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "DESede"; // 转换模式 String transformation = "DESede"; // --- 生成秘钥 --- // 实例化秘钥生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); // 初始化秘钥长度 keyGenerator.init(112); // 生成秘钥 SecretKey secretKey = keyGenerator.generateKey(); // 实例化DESede秘钥材料 DESedeKeySpec desKeySpec = new DESedeKeySpec(secretKey.getEncoded()); // 实例化秘钥工厂 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); // 生成DES秘钥 SecretKey desSecretKey = secretKeyFactory.generateSecret(desKeySpec); System.out.println("DESede秘钥:" + Base64.getEncoder().encodeToString(desSecretKey.getEncoded())); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥 cipher.init(Cipher.ENCRYPT_MODE, desSecretKey); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("DESede加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, desSecretKey); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("DESede解密结果:" + new String(decrypt)); } }
过程和DES一致,区别仅在于使用的加密算法为DESede,秘钥长度指定为112,秘钥材料对象为DESedeKeySpec类。
上面程序输出如下:
1 2 3 4 待加密值:mrbird's blog DESede秘钥:1fFb+F1wboVGGTSPNIBJ+9XxW/hdcG6F DESede加密结果:WVg9w6exyRREqY1vNyRI+Q== DESede解密结果:mrbird's blog
如果指定不合法的秘钥长度,程序将抛出java.security.InvalidParameterException:
Wrong keysize: must be equal to 112 or 168异常。
AES
AES(AdvancedEncryption
Standard,高级数据加密标准)算法支持128位、192位和256位的秘钥长度,加密速度比DES和DESede都快,至今还没有被破解的报道。经过验证,目前采用的AES算法能够有效抵御已知的针对DES算法的所有攻击方法,如部分差分攻击、相关秘钥攻击等。AES算法因秘钥建立时间短、灵敏性好、内存需求低等优点,在各个领域得到广泛的研究与应用。
JDK8支持128位、192位和256位长度的AES秘钥,下面举个JDK8实现AES加密的例子:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "AES"; // 转换模式 String transformation = "AES"; // --- 生成秘钥 --- // 实例化秘钥生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); // 初始化秘钥长度 keyGenerator.init(256); // 生成秘钥 SecretKey secretKey = keyGenerator.generateKey(); // 生成秘钥材料 SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm); System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded())); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥 cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("AES解密结果:" + new String(decrypt)); } }
过程和上面例子大体一致,唯一区别是使用AES算法无需指定特定类型的秘钥材料,直接用SecretKeySpec对象即可。
程序输出如下:
1 2 3 4 待加密值:mrbird's blog AES秘钥:vMj1fnhYdIGpTfxpEeKaynG879ARZ5r2AcfBpf+yoRs= AES加密结果:GCmIlqEJIBlBRw/474OanQ== AES解密结果:mrbird's blog
如果指定不合法的秘钥长度,程序将抛出java.security.InvalidParameterException:
Wrong keysize: must be equal to 128, 192 or 256异常。
RC2、RC4
RC2和RC4算法也可以用于替换DES算法,特点是秘钥长度较为灵活,RC2和RC4的秘钥长度范围为40到1024位。
JDK8支持RC2和RC4算法:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "RC2"; // 转换模式 String transformation = "RC2"; // --- 生成秘钥 --- // 实例化秘钥生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); // 初始化秘钥长度 keyGenerator.init(666); // 生成秘钥 SecretKey secretKey = keyGenerator.generateKey(); // 生成秘钥材料 SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm); System.out.println("RC2秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded())); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥 cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("RC2加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("RC2解密结果:" + new String(decrypt)); } }
程序输出如下:
1 2 3 4 待加密值:mrbird's blog RC2秘钥://wHmkfa/TpQqeard5RD8h6Gdd1YJQZbFT4pUsssRfitpz1YkhJfCN/J/hjR0Q1HmVReJaSPCNDqP+GUBlvnWi7zsoKW35O6R5W/R0Bgnjh/qVsu RC2加密结果:+XL70sqXz/myi1JRiLVbCw== RC2解密结果:mrbird's blog
如果指定不合法的秘钥长度,程序将抛出java.security.InvalidParameterException:
Key length for RC2 must be between 40 and 1024 bits异常。
RC4读者可以自己玩一玩。
Blowfish
Blowfish算法也可以用于替换DES,Blowfish算法的秘钥长度范围为32到448位,并且必须为8的倍数。
JDK8支持Blowfish算法:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "Blowfish"; // 转换模式 String transformation = "Blowfish"; // --- 生成秘钥 --- // 实例化秘钥生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); // 初始化秘钥长度 keyGenerator.init(128); // 生成秘钥 SecretKey secretKey = keyGenerator.generateKey(); // 生成秘钥材料 SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm); System.out.println("Blowfish秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded())); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥 cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("Blowfish加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("Blowfish解密结果:" + new String(decrypt)); } }
程序运行结果:
1 2 3 4 待加密值:mrbird's blog Blowfish秘钥:d3ReFylxI2OI7n3qgPFViQ== Blowfish加密结果:nnH2mqVZhhhegBwAuqecLw== Blowfish解密结果:mrbird's blog
如果指定不合法的秘钥长度,程序将抛出java.security.InvalidParameterException:
Keysize must be multiple of 8, and can only range from 32 to 448
(inclusive)异常。
PBE
PBE(Password Based
Encryption,基于口令加密)算法是一种基于口令的加密算法,特点是没有秘钥的概念,信息交互双方事先拟定好口令即可。单纯的口令很容易通过穷举攻击方式破译,所以PBE也加入了“盐”的概念。
PBE算法是对称加密算法的综合性算法,常见算法如PBEWithMD5AndDES,该算法使用了MD5和DES算法构建PBE算法。JDK8支持PBEWithMD5AndDES、PBEWithMD5AndTripleDES、PBEWithSHA1AndDESede、PBEWithSHA1AndRC2_40、PBEWithSHA1AndRC2_128、PBEWithSHA1AndRC4_40、PBEWithSHA1AndRC4_128、PBEWithHmacSHA1AndAES_128、PBEWithHmacSHA224AndAES_128、PBEWithHmacSHA256AndAES_128、PBEWithHmacSHA384AndAES_128、PBEWithHmacSHA512AndAES_128、PBEWithHmacSHA1AndAES_256、PBEWithHmacSHA224AndAES_256、PBEWithHmacSHA256AndAES_256、PBEWithHmacSHA384AndAES_256和PBEWithHmacSHA512AndAES_256,其中算法名称中包含Hmac的需要配和初始化向量使用。
不需要指定初始化向量的PBE算法族(PBEWithMD5AndDES、PBEWithMD5AndTripleDES、PBEWithSHA1AndDESede、PBEWithSHA1AndRC2_40、PBEWithSHA1AndRC2_128、PBEWithSHA1AndRC4_40、PBEWithSHA1AndRC4_128)例子:
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 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 52 53 54 55 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import java.security.SecureRandom; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "PBEWithSHA1AndDESede"; // 转换模式 String transformation = "PBEWithSHA1AndDESede"; // 密码(口令) String password = "mrbird@qq.com"; System.out.println("PBE口令:" + password); // 迭代次数 int count = 99; // 实例化安全随机数 SecureRandom secureRandom = new SecureRandom(); // 生成盐 byte[] salt = secureRandom.generateSeed(8); System.out.println("盐值:" + Base64.getEncoder().encodeToString(salt)); // 通过密码生成秘钥材料 PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); // 实例化秘钥工厂 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); // 生成秘钥 SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // 实例化PBE参数材料 PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, count); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 初始化 cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("PBE加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("PBE解密结果:" + new String(decrypt)); } }
程序输出如下:
1 2 3 4 5 待加密值:mrbird's blog PBE口令:mrbird@qq.com 盐值:e2vd/Or8i3U= PBE加密结果:TZx40AyeTP9PZyF6iP3C8A== PBE解密结果:mrbird's blog
算法名称包含Hmac的PBE算法需要指定初始化向量,比如PBEWithHmacSHA1AndAES_128:
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 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 52 53 54 55 56 57 58 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import java.security.SecureRandom; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "PBEWithHmacSHA1AndAES_128"; // 转换模式 String transformation = "PBEWithHmacSHA1AndAES_128"; // 密码(口令) String password = "mrbird@qq.com"; System.out.println("PBE口令:" + password); // 迭代次数 int count = 99; // 实例化安全随机数 SecureRandom secureRandom = new SecureRandom(); // 生成盐 byte[] salt = secureRandom.generateSeed(8); System.out.println("盐值:" + Base64.getEncoder().encodeToString(salt)); // 通过密码生成秘钥材料 PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); // 实例化秘钥工厂 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); // 生成秘钥 SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // 创建初始化向量 IvParameterSpec iv = new IvParameterSpec("123456789abcdefg".getBytes()); // 实例化PBE参数材料 PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, count, iv); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 初始化 cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("PBE加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("PBE解密结果:" + new String(decrypt)); } }
通过IvParameterSpec类创建初始化向量,创建初始化向量的秘钥必须为16字节,这里为123456789abcdefg,实例化PBE参数材料的时候通过构造参数传入初始化向量。
程序输出如下:
1 2 3 4 5 待加密值:mrbird's blog PBE口令:mrbird@qq.com 盐值:UJwEggkfM88= PBE加密结果:l+HGXLUHEQN+uKmNuIzqzA== PBE解密结果:mrbird's blog
加密模式
对称加密算法分为:序列密码(流密码)加密,分组密码(块密码)加密两种。流密码是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密,块密码是先对信息流分块,再对每一块分别加密。
上面介绍的这些都属于块密码加密。不同的算法侧重点不同,有的强调效率,有的强调安全,有的强调容错性。根据数据加密时每个加密区块间的关联方式来区分,可以分为4种加密模式:电子密码本模式(Electronic
Code Book,ECB)、密文链接模式(Cipher Book
Chaining,CBC)、密文反馈模式(Cipher Feed
Back,CFB)、输出反馈模式(Output Feed
Back,OFB)。AES标准除了推荐上述4种工作模式外,还推荐了一种新的工作模式—计数器模式(Counter,CTR)。这些工作模式可适用于各种分组密码算法。
ECB
ECB模式加解密过程如下图所示:
明文分为若干块,每次加密均产生独立的密文分组,每组的加密结果不会对其他分组产生影响,相同的明文加密后对应产生相同的密文。
优点 :可并行操作,没有误差传递(因为每个密文都是独立加密来的);
缺点 :如果明文重复,则对应的密文也会重复,对明文进行主动攻击的可能性较高;
用途 :适合加密秘钥、随机数等短数据。例如,安全地传递DES秘钥,ECB是最合适的模式。
CBC
CBC模式加解密过程如下图所示:
明文分为若干块,每次加密前,明文块都和前一个明文块加密后的内容进行异或处理,然后再用秘钥加密。因为第一个明文块没有可以用来异或处理的密文块,所以我们需要提供一个初始化向量来替代。
优点 :密文链接模式加密后的密文上下文关联,对明文的主动攻击的可能性较低;
缺点 :不能并行加密,如果在加密过程中发生错误,则错误将被无限放大,导致加密失败。并且需要提供初始化向量;
用途 :可加密任意长度的数据;适用于计算产生检测数据完整性的消息认证码Mac。
CFB
CFB模式加解密过程如下图所示:
明文分为若干块,每次加密前,先将前一个密文块使用秘钥加密,加密结果和当前明文块异或处理得到密文块。同样的,需要为第一个明文块加密提供初始化向量。
优点 :和CBC类似;
缺点 :和CBC类似;
用途 :因错误传播无界,可用于检查发现明文密文的篡改。
OFB
OFB模式加解密过程如下图所示:
过程和CFB类似,区别在于OFB第一次使用秘钥对初始化向量进行加密(结果为A),加密结果和明文块异或处理得到密文块,下一次操作时候,不是使用秘钥加密前一个密文块,而是使用秘钥加密A的结果再和明文块异或处理,得到当前密文块。
优点 :和CFB类似;
缺点 :不利于并行计算;对明文的主动攻击是可能的,安全性较CFB差;
用途 :适用于加密冗余性较大的数据,比如语音和图像数据。
CTR
CTR模式加解密过程如下图所示:
CTR含义是计数器模式,所以它维护了一个递增的计数器。秘钥加密计数器,结果和明文块异或得到密文块,依次类推。
优点 :可以并行操作,安全性和CBC一样好;
缺点 :没有错误传播,因此不易确保数据完整性;
用途 :适用于各种加密应用。
填充模式
当需要按块处理的数据, 数据长度不符合块处理需求时,
按照一定的方法填充满块长的规则。如果不填充,待加密的数据块长度不符合要求时程序会抛出异常。
JDK8中主要支持NoPadding和PKCS5Padding填充模式。
NoPadding:不填充;
PKCS5Padding:数据块的大小为8位, 不够就补足。
加密、填充模式实战
在了解了加密模式和填充模式后,我们回头看前面代码中的transformation参数,实例化Cipher对象的时候需要指定transformation转换模式,转换模式主要有两种格式:
算法;
算法/加密模式/填充模式。
下面就AES算法来实践不同的加密、填充模式。
当转换模式为AES/ECB/PKCS5Padding时:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "AES"; // 转换模式 String transformation = "AES/ECB/PKCS5Padding"; // --- 生成秘钥 --- // 实例化秘钥生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); // 初始化秘钥长度 keyGenerator.init(256); // 生成秘钥 SecretKey secretKey = keyGenerator.generateKey(); // 生成秘钥材料 SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm); System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded())); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥 cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("AES解密结果:" + new String(decrypt)); } }
程序运行解果:
1 2 3 4 待加密值:mrbird's blog AES秘钥:xCwGw2d/cflibDRY4oQtjNEgBN5tIrxOBtt/31jAeN8= AES加密结果:kjFz9IMO2HAOFgExXMsBjg== AES解密结果:mrbird's blog
将转换模式改为AES/CBC/PKCS5Padding时,程序输出抛出java.security.InvalidKeyException:
Parameters missing异常。
因为该模式需要指定初始化向量,将代码修改为:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String value = "mrbird's blog"; System.out.println("待加密值:" + value); // 加密算法 String algorithm = "AES"; // 转换模式 String transformation = "AES/CBC/PKCS5Padding"; // --- 生成秘钥 --- // 实例化秘钥生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); // 初始化秘钥长度 keyGenerator.init(256); // 生成秘钥 SecretKey secretKey = keyGenerator.generateKey(); // 生成秘钥材料 SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm); System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded())); // 初始化向量,123456789abcdefg初始化向量秘钥,16字节 IvParameterSpec iv = new IvParameterSpec("123456789abcdefg".getBytes()); // 实例化密码对象 Cipher cipher = Cipher.getInstance(transformation); // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥 cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv); // 加密 byte[] encrypt = cipher.doFinal(value.getBytes()); System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt)); // 解密 // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv); byte[] decrypt = cipher.doFinal(encrypt); System.out.println("AES解密结果:" + new String(decrypt)); } }
程序输出如下:
1 2 3 4 待加密值:mrbird's blog AES秘钥:xbqvwRKSrAtMsKOxzkbJqG0aMgMQwTC7TF5jkch1M5I= AES加密结果:jksbHzoAA/K0sNVW1+RUOw== AES解密结果:mrbird's blog
将转换模式改为AES/CBC/NoPadding时,程序抛出javax.crypto.IllegalBlockSizeException:
Input length not multiple of 16 bytes异常。因为mrbird’s
blog不是16字节的倍数。
更多对称加密算法和加密模式填充模式的组合可以自己尝试下,或者参考下面的算法总结。
手动指定秘钥
在使用对称加密算法加解密的时候,秘钥一般是双方事先约定好的,假如现在有AES秘钥/a3nqjFFtkWNmeX5HzmMtzCziOThbAnEHoiTA4JquAI=,AES密文y9YMlnX7TE4qF0dDIQ==,转换模式为AES/CTR/PKCS5Padding,初始化向量秘钥为123456789abcdefg,如何通过秘钥和密文进行解密呢,可以参考下面的代码(即演示如何手动指定秘钥并解密):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class Demo { @Test public void test() throws Exception { String algorithm = "AES"; String transformation = "AES/CTR/PKCS5Padding"; String key = "/a3nqjFFtkWNmeX5HzmMtzCziOThbAnEHoiTA4JquAI="; String encrypt = "y9YMlnX7TE4qF0dDIQ=="; String ivKey = "123456789abcdefg"; Cipher cipher = Cipher.getInstance(transformation); SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), algorithm); IvParameterSpec iv = new IvParameterSpec(ivKey.getBytes()); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv); byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(encrypt)); System.out.println("AES解密结果:" + new String(decrypt));
输出如下:
算法总结
对上面这些算法进行总结:
算法
秘钥长度(位)
工作模式
填充模式
初始化向量秘钥长度(字节)
DES
56
ECB、CBC、CFB、OFB、CTR等
NoPadding、 PKCS5Padding、
ISO10126Padding
8
DESede
112、168
ECB、CBC、CFB、OFB、CTR等
NoPadding、 PKCS5Padding、
ISO10126Padding
8
AES
128、192、256
ECB、CBC、CFB、OFB、CTR等
NoPadding、 PKCS5Padding、
ISO10126Padding
16
RC2
40~1024
ECB、CBC、CFB、OFB、CTR等
NoPadding、 PKCS5Padding、
ISO10126Padding
8
RC4
40~1024
ECB
NoPadding
无
Blowfish
32~448,8的倍数
ECB、CBC、CFB、OFB、CTR等
NoPadding、 PKCS5Padding、
ISO10126Padding
8
PBE
无
CBC
PKCS5Padding
16(带Hmac)
《Java加密与解密的艺术》读书笔记