前言
前一篇文章介绍了RSA加解密算法、明文密文长度、数字签名详解和java工具类、及应用探讨,这一篇文章来一起分享一下对称加密算法详解、对比、应用和Java工具类。
对称加密算法的加密过程
特点:
- 密钥只有一个,加密和解密使用同一个密钥
- 长度与明文大致相同
- 相比非对称加密速度很快
- 安全性较非对称加密弱
- 如果存在很多用户,那么相比非对称加密需要维护大量的密码,不利于管理
- 要求提供一条安全的渠道使通讯双方在首次通讯时协商一个共同的密钥。直接的面对面协商可能是不现实而且难于实施的,所以双方可能需要借助于邮件和电话等其它相对不够安全的手段来进行协商;
- 对称加密算法一般不能提供信息完整性的鉴别。它无法验证发送者和接受者的身份;
常见的算法对比
1、DES
已破解,不再安全,基本没有企业在用了
是对称加密算法的基石,具有学习价值
密钥长度56(JDK)、56/64(BC)
2、3DES(三重DES)
早于AES出现来替代DES
计算密钥时间太长、加密效率不高,所以也基本上不用
密钥长度112/168(JDK)、128/192(BC)
3、AES
最常用的对称加密算法
密钥建立时间短、灵敏性好、内存需求低(不管怎样,反正就是好)
实际使用中,使用工作模式为CTR(最好用BC去实现),此工作模式需要引入IV参数(16位的字节数组)
密钥长度128/192/256,其中192与256需要配置无政策限制权限文件(JDK6)
填充模式最常用的两种PKCS5Padding和PKCS7Padding,其中后者只有BC独有。
4、IDEA
常用的电子邮件加密算法
工作模式只有ECB
密钥长度128位
5、PBE
综合了消息摘要算法和对称加密算法,最常见的是PBEWithMD5AndDES
工作模式只有CBC(已丧失安全性,不推荐使用),所以PBE也不推荐使用了
对称加密的常见工作模式
加密一般分为对称加密(Symmetric Key Encryption)和非对称加密(Asymmetric Key Encryption)。对称加密又分为分组加密和序列密码。
(1)分组加密,也叫块加密(block cyphers),一次加密明文中的一个块。是将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。具有代表性的块加密算法有DES,AES,3DES等。
(2)序列加密,也叫流加密(stream cyphers),一次加密明文中的一个位。是指利用少量的密钥(制乱元素)通过某种复杂的运算(密码算法)产生大量的伪随机位流,用于对明文位流的加密。
解密是指用同样的密钥和密码算法及与加密相同的伪随机位流,用以还原明文位流。
1、ECB(Electronic Code Book):
又称电子密码本模式:Electronic codebook,是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
DES对称加密算法的 ECB模式 其实非常简单,就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节的密文或者明文,最后一段不足8个字节,按照需求补足8个字节进行计算,之后按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。
特点:
(1)简单,有利于并行计算,误差不会被传送;
(2)同样的明文块会被加密成相同的密文块,不能很好的隐藏明文数据模式;
(3)加密消息块相互独立成为被攻击的弱点,不能提供严格的数据保密性,可以对明文进行主动攻击;
2、CBC模式:
密码分组链接(CBC,Cipher-block chaining)模式,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量IV。它的实现机制使加密的各段数据之间有了联系。
特点:
①不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准;
②每个密文块依赖于所有的信息块,明文消息中一个改变会影响所有密文块;
③发送方和接收方都需要知道初始化向量;
④主要缺点是加密过程是串行的,无法被并行化,因为在加密时,明文中的微小改变会导致其后的全部密文块发生改变,而且消息必须被填充到块大小的整数倍。而在解密时,从两个邻接的密文块中即可得到一个明文块。因此,解密过程可以被并行化,而解密时,密文中一位的改变只会导致其对应的明文块完全改变和下一个明文块中对应位发生改变,不会影响到其它明文的内容。
3、PCBC模式:
填充密码块链接(PCBC,Propagating cipher-block chaining)或称为明文密码块链接(Plaintext cipher-block chaining),是一种可以使密文中的微小更改在解密时导致明文大部分错误的模式,并在加密的时候也具有同样的特性。
对于使用PCBC加密的消息,互换两个邻接的密文块不会对后续块的解密造成影响。
4、CFB模式:
密文反馈(CFB,Cipher feedback)模式与ECB和CBC模式只能够加密块数据不同,可以将块密码变为自同步的流密码;CFB的解密过程几乎就是颠倒的CBC的加密过程。
①CFB需要使用一个与块的大小相同的移位寄存器,并用IV将寄存器初始化。然后,将寄存器内容使用块密码加密,然后将结果的最高x位与明文的x进行异或,以产生密文的x位。下一步将生成的x位密文移入寄存器中,并对下面的x位明文重复这一过程。
②解密过程与加密过程相似,以IV开始,对寄存器加密,将结果的高x与密文异或,产生x位明文,再将密文的下面x位移入寄存器。与CBC相似,明文的改变会影响接下来所有的密文,因此加密过程不能并行化;而同样的,与CBC类似,解密过程是可以并行化的。
由于加密流程和解密流程中被块加密器加密的数据是前一段密文,因此即使明文数据的长度不是加密块大小的整数倍也是不需要填充的,这保证了数据长度在加密前后是相同的。
非对称加解密算法Java工具类,工具类默认使用AES算法
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),是一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
那么为什么原来的DES会被取代呢,,原因就在于其使用56位密钥,比较容易被破解。而AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据,相对来说安全很多。完善的加密算法在理论上是无法破解的,除非使用穷尽法。使用穷尽法破解密钥长度在128位以上的加密数据是不现实的,仅存在理论上的可能性。统计显示,即使使用目前世界上运算速度最快的计算机,穷尽128位密钥也要花上几十亿年的时间,更不用说去破解采用256位密钥长度的AES算法了。
目前世界上还有组织在研究如何攻破AES这堵坚厚的墙,但是因为破解时间太长,AES得到保障,但是所用的时间不断缩小。随着计算机计算速度的增快,新算法的出现,AES遭到的攻击只会越来越猛烈,不会停止的。
AES现在广泛用于金融财务、在线交易、无线通信、数字存储等领域,经受了最严格的考验,但说不定哪天就会步DES的后尘。
package cn.hengyumo.humor.utils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* SeaUtil
* 对称加密算法 symmetric encryption algorithm
* 加解密工具类,默认使用AES算法
*
* @author hengyumo
* @version 1.0
* @since 2019/11/11
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class SeaUtil {
/**
* 默认算法
*/
private static final String KEY_ALGORITHM = "AES";
/**
* 字符集
*/
private static final String CHARSET = "utf-8";
/**
* 默认密钥长度
*/
private static final int KEY_LENGTH = 128;
/**
* 生成密钥
*
* @param str 字符串
* @param keyAlgorithm 密钥算法
* @param keyLength 密钥长度
*
* @return 密钥
*/
public static SecretKey createKey(String str, String keyAlgorithm, int keyLength) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(keyAlgorithm);
keyGenerator.init(keyLength, new SecureRandom(str.getBytes()));
return new SecretKeySpec(keyGenerator.generateKey().getEncoded(), keyAlgorithm);
}
/**
* 生成AES密钥-128
*
* @param str 字符串
*
* @return 密钥
*/
public static SecretKey createKey(String str) throws NoSuchAlgorithmException {
return createKey(str, KEY_ALGORITHM, KEY_LENGTH);
}
/**
* 加密
*
* @param secretKey 密钥
* @param keyAlgorithm 加密算法
* @param content 明文
*
* @return 密文
*/
public static String encrypt(SecretKey secretKey, String keyAlgorithm, String content) throws Exception {
Cipher cipher = Cipher.getInstance(keyAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] result = cipher.doFinal(content.getBytes(CHARSET));
return new String(Base64.getEncoder().encode(result));
}
/**
* 用AES进行加密
*
* @param secretKey 密钥字符串
* @param content 明文
*
* @return 密文
*/
public static String encrypt(String secretKey, String content) throws Exception {
return encrypt(createKey(secretKey), KEY_ALGORITHM, content);
}
/**
* 解密
*
* @param secretKey 密钥
* @param keyAlgorithm 解密算法
* @param content 密文
*
* @return 明文
* @throws Exception e
*/
public static String decrypt(SecretKey secretKey, String keyAlgorithm, String content) throws Exception {
Cipher cipher = Cipher.getInstance(keyAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] result = cipher.doFinal(Base64.getDecoder().decode(content));
return new String(result, CHARSET);
}
/**
* 用AES进行解密
*
* @param secretKey 密钥字符串
* @param content 密文
*
* @return 明文
* @throws Exception e
*/
public static String decrypt(String secretKey, String content) throws Exception {
return decrypt(createKey(secretKey), KEY_ALGORITHM, content);
}
/**
* 测试
*
* @param args args
*/
public static void main(String[] args) {
try {
// 默认用AES加密
// 生成密钥
SecretKey secretKey = SeaUtil.createKey("hhh!@123");
String p = "A-B-C-D-E-F-G-1234567";
// 加密
// 或者直接String s = SeaUtil.encrypt("hhh!@123", p);
String s = SeaUtil.encrypt(secretKey, "AES", p);
System.out.println(s);
System.out.println('\n');
// 解密
String d = SeaUtil.decrypt("hhh!@123", s);
System.out.println(d);
// 切换Blowfish加密
SecretKey bfs = SeaUtil.createKey("hhh!@123", "Blowfish", 128);
s = SeaUtil.encrypt(bfs,"Blowfish", p);
System.out.println(s);
System.out.println('\n');
System.out.println(SeaUtil.decrypt(bfs, "Blowfish", s));
}
catch (Exception e) {
e.printStackTrace();
}
}
}
应用探讨
1、加密文件、加密消息、加密…你想加密什么加密什么…
2、配合非对称算法进行混合加密
混合加密实现方式:
- 信息(明文)采用DES密钥加密得到密文。
- 使用RSA加密前面的DES密钥信息得到key,最终将密文和key进行传递。
接收到信息后解密:
- 用RSA解密key得到DES密钥。
- 再用DES秘钥解密文信息,最终就可以得到我们要的信息(明文)。
3、配合哈希加密进行混合加密
无法破解的哈希加密:密钥哈希和密码哈希设备
只要攻击者可以检测对一个密码的猜测是否正确,那么他们就可以进行字典攻击或暴力攻击。因此下一步就是向哈希计算中增加一个密钥,只有知道这个密钥的人才能校验密码。有两种办法可以实现:将哈希值加密,比如使用AES算法;将密钥包含到哈希字符串中,比如使用密钥哈希算法HMAC。
听起来很简单,做起来就不一样了。这个密钥需要在任何情况下都不被攻击者获取,即使系统因为漏洞被攻破了。如果攻击者获取了进入系统的最高权限,那 么不论密钥被储存在哪,他们都可以窃取到。因此密钥需要储存在外部系统中,比如另一个用于密码校验的物理服务器,或者一个关联到服务器的特制硬件,如YubiHSM。
我强烈推荐大型服务(10万用户以上)使用这类办法,因为我认为面对如此多的用户是有必要的。
如果你难以负担多个服务器或专用的硬件,仍然有办法在一个普通Web服务器上利用密钥哈希技术。大部分针对数据库的入侵都是由于SQL注入攻击, 因此不要给攻击者进入本地文件系统的权限(禁止数据库服务访问本地文件系统,如果它有这个功能的话)。这样一来,当你随机生成一个密钥存到通过Web程序 无法访问的文件中,然后混入加盐哈希,得到的哈希值就不再那么脆弱了,即便这时数据库遭受了注入攻击。不要把将密钥硬编码到代码里,应该在安装时随机生 成。这当然不如独立的硬件系统安全,因为如果Web程序存在SQL注入点,那么可能还存在其他一些问题,比如本地文件包含漏洞(Local File Inclusion),攻击者可以利用它读取本地密钥文件。无论如何,这个措施比没有好。
请注意密钥哈希不代表无需进行加盐。高明的攻击者迟早会找到办法窃取密钥,因此依然对密码哈希进行加盐和密钥扩展很重要。
参考&引用
五种对称加密算法总结
https://blog.csdn.net/qq_30054961/article/details/82456069
各种加密算法比较
https://www.cnblogs.com/Saoz/p/9996645.html
对称加密算法之分组加密的六种工作模式(ECB、CBC、PCBC、CFB、OFB、CTR)
https://blog.csdn.net/a745233700/article/details/102311776
Java实现AES加密
https://www.cnblogs.com/loong-hon/p/10145060.html
对称加密和非对称加密简介和应用
https://www.jianshu.com/p/a3624d4e2248
加盐密码哈希:如何正确使用 (密码加密的经典文章)
https://crackstation.net/hashing-security.htm
http://blog.jobbole.com/61872/