在对称加密算法中,数据发信方将明文(原始数据)和加密密钥(mi yao)一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
信息安全基本概念:
- DES(Data Encryption Standard,数据加密标准)
- 3DES(Triple DES,三重数据加密算法(TDEA,Triple Data Encryption Algorithm))
- AES(Advanced Encryption Standard,高级加密标准)
- Blowfish
- RC2
- RC4
1.1、加密的工作模式
|
加密模式(英文名称及简写)
|
优点
|
缺点
|
|
|
Electronic Code Book(ECB)
电子密码本模式
|
简单 快速 支持并行计算(加密、解密) |
明文中的重复排列会反映在密文中 通过删除、替换密文分组可以对明文进行操作 对包含某些比特错误的密文进行解密时,对应的分组会出错 |
必应使用 一般情况下很少用 |
|
Cipher Block Chaining(CBC)
密码分组链接模式
|
明文的重复排列不会反映在密文中
支持并行计算(仅解密) 能够解密任意密文分组 |
对包含某些错误比特的密文进行解密时, 第一个分组的全部比特以及后一个分组的相应比特会出 加密不支持并行计算 |
推荐使用 |
|
Cipher Feedback Mode(CFB)
加密反馈模式
|
不需要填充(padding)
支持并行计算(仅解密) 能够解密任意密文分组 |
加密不支持并行计算 对包含某些错误比特的密文进行解密时, 第一个分组的全部比特以及后一个分组的相应比特会出错 不能抵御重放攻击 |
现在已不使用 |
|
Output Feedback Mode(OFB)
输出反馈模式
|
不需要填充(padding) 可事先进行加密、解密的准备 加密、解密使用相同结构 对包含某些错误比特的密文进行解密时,只有铭文中相应的比特会出错 |
不支持并行运算 主动攻击这反转密文分组中的某些比特时, 明文分组中相对应的比特也会被反转 |
推荐用CTR模式代替 |
|
CRT模式
CounTeR计数器模式
|
不需要填充(padding) 可事先进行加密、解密的准备 加密、解密使用相同的结构 对包含某些错误比特的密文进行解密时,只有明文中相对应的比特会出错 支持并行计算(加密、解密) |
主动攻击者反转密文分组中的某些比特时, 明文分组中对应的比特也会被反转 |
推荐使用 |
|
GCM( Galois/Counter Mode )
|
GCM中的G就是指GMAC,C就是指CTR。 在实际应用场景中,有些信息是我们不需要保密,但信息的接收者需要确认它的真实性的, 例如源IP,源端口,目的IP,IV,等等。因此,我们可以将这一部分作为附加消息加入 到MAC值的计算当中。下图的Ek表示用对称秘钥k对输入做AES运算。最后,密文接收者会收到密文、 IV(计数器CTR的初始值)、MAC值。 GMAC就是利用伽罗华域(Galois Field,GF,有限域)乘法运算来计算消息的MAC值。 假设秘钥长度为128bits, 当密文大于128bits时,需要将密文按128bits进行分组。 |
|
1.1.1、ECB
1.1.2、CBC
明文被加密前要与前面的密文进行异或运算后再加密,因此只要选择不同的初始向量,相同的密文加密后会形成不同的密文,这是目前应用最广泛的模式。
CBC加密后的密文是上下文相关的,但明文的错误不会传递到后续分组,但如果一个分组丢失,后面的分组将全部作废(同步错误)。
在CBC模式下,在加密之前,每个明文块与先前的密文块进行异或。 这样,每个密文块依赖于直到那一点处理的所有明文块。 为了使每个消息唯一,必须在第一个块中使用初始化向量。
1.1.3、CFB
类似于自同步序列密码,分组加密后,按8位分组将密文和明文进行移位异或后得到输出同时反馈回移位寄存器,优点最小可以按字节进行加解密,也可以是n位的,CFB也是上下文相关的,CFB模式下,明文的一个错误会影响后面的密文(错误扩散)。
1.1.4、OFB
将分组密码作为同步序列密码运行,和CFB相似,不过OFB用的是前一个n位密文输出分组反馈回移位寄存器,OFB没有错误扩散问题。
1.2、长度、块的大小根据算法需求来决定。
DES秘钥长度:8个字符,DES 的数据块是64bits,也就是8 B,所以,块的大小是8字节。
AES秘钥长度:16个字符,AES 的数据块是128bits,也就是16B,所以,块的大小是16字节。
DES加密后密文长度是8的整数倍
AES加密后密文长度是16的整数倍
填充模式一般针对的是块加密模式(分组加密模式),MD5、SHA等散列方式没有填充模式。
1.3、填充模式
某些加密算法要求明文需要按一定长度对齐,叫做块大小(BlockSize),比如16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。
某些模式(即ECB和CBC)要求在加密之前填补最终的块。存在几种填充方案。最简单的是将空字节添加到明文中以使其长度达到块大小的倍数,但必须注意可以恢复明文的原始长度,如果明文是一个C风格的字符串,除了最后不包含空字节。稍微复杂一点就是添加一个置一位,后面加上足够的零位来填写块。如果消息在块边界上结束,则将添加整个填充块。
CFB,OFB和CTR模式不需要任何特殊措施来处理长度不是块大小倍数的消息,因为这些模式通过将明文与块密码的输出进行异或工作。最后的部分明文块与最后一个密钥流块的前几个字节进行异或,产生与最终部分明文块大小相同的最终密文块。流密码的这种特性使得它们适用于需要加密密文数据与原始明文数据相同大小的应用程序,以及用于以不方便添加填充字节的流式传输数据的应用程序。
注意:常用的填充形式
1、填充数据为填充字节的长度
这种填充方式中,填充字符串由一个字节序列组成,每个字节填充该字节序列的长度。假定块长度为8,原文数据长度9,则填充字节数等于0x07;如果明文数据长度为8的整数倍,则填充字节数为0x08。
填充字符串如下:
原文数据1: FF FF FF FF FF FF FF FF FF
填充后数据1:FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07
因块长度为8 故填充后为8的倍数,原文长度为9,需要在填充 7个,填充的内容为 0x07
原文数据2: FF FF FF FF FF FF FF FF
填充后数据2:FF FF FF FF FF FF FF FF 08 08 08 08 08 08 08 08
因块长度为8 故填充后为8的倍数,原文长度为8,需要在填充 8个,填充的内容为 0x08
2、填充数据为0x80后加0x00
这种填充方式中,填充字符串的第一个字节数是0x80,后面的每个字节是0x00。假定块长度为8,原文数据长度为9或者为8的整数倍,则填充字符串如下:
原文数据1: FF FF FF FF FF FF FF FF FF
填充后数据1:FF FF FF FF FF FF FF FF FF 80 00 00 00 00 00 00
因块长度为8 故填充后为8的倍数,原文长度为9,需要在填充 7个,填充的内容为 0x80,其他6个为00
原文数据2: FF FF FF FF FF FF FF FF
填充后数据2:FF FF FF FF FF FF FF FF 80 00 00 00 00 00 00 00
因块长度为8 故填充后为8的倍数,原文长度为8,需要在填充 8个,填充的内容为 0x80,其他7个为00
3、填充数据的最后一个字节为填充字节序列的长度
这种填充方式中,填充字符串的最后一个字节为该字节序列的长度,而前面的字节可以是0x00,也可以是随机的字节序列。假定块长度为8,原文数据长度为9或者为8的整数倍,则填充字符串如下:
原文数据1:FF FF FF FF FF FF FF FF FF
填充后数据1:FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07
或: FF FF FF FF FF FF FF FF FF 58 B3 98 9B AD F4 07
原文数据2:FF FF FF FF FF FF FF FF
填充后数据2:FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 08
或: FF FF FF FF FF FF FF FF 32 58 B3 98 9B AD F4 08
4、填充数据为空格
这种填充方式中,填充字符串的每个字节为空格对应的字节数0x20。假定块长度为8,原文数据长度为9或者为8的整数倍,则填充字符串如下:
原文数据1:FF FF FF FF FF FF FF FF FF
填充后数据1:FF FF FF FF FF FF FF FF FF 20 20 20 20 20 20 20
原文数据2:FF FF FF FF FF FF FF FF
填充后数据2:FF FF FF FF FF FF FF FF 20 20 20 20 20 20 20 20
5、填充数据为0x00
这种填充方式中,填充字符串的每个字节为0x00。假定块长度为8,原文数据长度为9或者8的整数倍,则填充字符串如下:
原文数据1: FF FF FF FF FF FF FF FF FF
填充后数据1:FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00
原文数据2: FF FF FF FF FF FF FF FF
填充后数据2:FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00
在填充方式 4 和方式 5 中,由于缺少填充数据长度的标识信息,如果原文数据的后几个字节本身包括空格或0,将不能够准确移去填充的数据。因此使用这样的填充方式时,对原文数据有一定的要求。
JCE中支持的补长方案包括:NoPadding、PKCS5Padding、ISO10126Padding、OAEPWithAndPadding和SSL3Padding,其中最常用的就是PKCS5Padding和ISO10126Padding。
1.3.1、ZeroPadding:数据长度不对齐时使用0填充,否则不填充。
1.3.2、PKCS7Padding:PKCS5Padding或PKCS7Padding是RSA公司的公钥密码学标准PKCS #5文档中定义的填充方式,使用上述方式1填充
1.3.3、PKCS5Padding:PKCS7Padding的子集,块大小固定为8字节。
1.3.4、ISO10126Padding:使用上述方式3填充
1.3.5、NoPadding:不补长
1.4、实例
以DES算法加密明文for为例
因for不足8bytes,所以需补长5bytes(for ?? ?? ?? ?? ??),则这5bytes可能选择(16进制):
i. 所有Padding以长度为值:66 6F 72 05 05 05 05 05
ii. Padding以0×80开始后面全部为0×00:66 6F 72 80 00 00 00 00
iii. 最后一个字节为Padding长度, 其它为0×00:66 6f 72 00 00 00 00 05
iv. 全部Padding为0×00:66 6f 72 00 00 00 00 00
简单的说PKCS5Padding就2个规则:
i. 补长的内容为待补长字节数
ii. 补长的字节数为:8 – 明文长度 % 8,即补长长度在1至8bytes之间
如前述的明文for将补长为:66 6F 72 05 05 05 05 05ISO10126Padding,具体规范请参考ISO10126。简单的说ISO10126Padding就是补长的长度作为补长内容的最后一个byte,之前的补长内容为随机数。
如前述的明文for可能补长为:66 6F 72 2A 75 EF F8 05
二、详细介绍
2.1、DES
DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。需要注意的是,在某些文献中,作为算法的DES称为数据加密算法(Data Encryption Algorithm,DEA),已与作为标准的DES区分开来。
DES算法的入口参数有三个:Key、Data、Mode。其中Key为7个字节共56位,是DES算法的工作密钥;Data为8个字节64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。
DES设计中使用了分组密码设计的两个原则:混淆(confusion)和扩散(diffusion),其目的是抗击敌手对密码系统的统计分析。混淆是使密文的统计特性与密钥的取值之间的关系尽可能复杂化,以使密钥和明文以及密文之间的依赖性对密码分析者来说是无法利用的。扩散的作用就是将每一位明文的影响尽可能迅速地作用到较多的输出密文位中,以便在大量的密文中消除明文的统计结构,并且使每一位密钥的影响尽可能迅速地扩展到较多的密文位中,以防对密钥进行逐段破译。
DES还是一种分组加密算法,该算法每次处理固定长度的数据段,称之为分组。DES分组的大小是64位,如果加密的数据长度不是64位的倍数,可以按照某种具体的规则来填充位。
2.1.1、jdk实现
路径:/algorithm-sign/algorithm-sign-impl/src/main/java/com/github/bjlhx15/security/symmetric001des
通过调试发现支持工作模式以及填充模式
// DES SupportedPaddings=NOPADDING|PKCS5PADDING|ISO10126PADDING, // DES SupportedModes=ECB|CBC|PCBC|CTR|CTS|CFB|OFB| // CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64| // OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64
编写测试程序
public class JdkSymmetricTest { String msg = "测试数据2222"; Map.Entry<String, String> padding = null; byte[] key = null; byte[] iv = null; byte[] encrypt = null; byte[] decrypt = null; // DES SupportedPaddings=NOPADDING|PKCS5PADDING|ISO10126PADDING, // DES SupportedModes=ECB|CBC|PCBC|CTR|CTS|CFB|OFB| // CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64| // OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64 @Test public void encryptAll() throws Exception { System.out.println("原文:" + msg); List<Map.Entry<String, String>> paddingListMap = new ArrayList<>(); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES"));//相当于:DES/ECB/PKCS5PADDING paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/ECB/NOPADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/ECB/PKCS5PADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/ECB/ISO10126PADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/CBC/NOPADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/CBC/PKCS5PADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/CBC/ISO10126PADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/PCBC/NOPADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/PCBC/PKCS5PADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/PCBC/ISO10126PADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/CTR/NOPADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/CTR/PKCS5PADDING")); //paddingListMap.add(new AbstractMap.SimpleEntry<>("DES","DES/CTR/ISO10126PADDING"));//不支持 paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/CTS/NOPADDING")); paddingListMap.add(new AbstractMap.SimpleEntry<>("DES", "DES/CTS/PKCS5PADDING")); // paddingListMap.add(new AbstractMap.SimpleEntry<>("DES","DES/CTS/ISO10126PADDING"));//不支持 for (Map.Entry<String, String> entry : paddingListMap) { try { boolean b = encryptCheck(entry); if (b) { System.out.println(entry.getValue() + ":支持"); } } catch (Exception e) { e.printStackTrace(); System.out.println(entry.getValue() + ":不支持"); } } } boolean encryptCheck(Map.Entry<String, String> entry) throws Exception { padding = entry; key = JdkSymmetric.initKey(padding);//Base64.getDecoder().decode("koY9NPFJGf4=");// iv = JdkSymmetric.initIv(); System.out.println("key:" + Base64.getEncoder().encodeToString(key)); System.out.println("iv:" + Base64.getEncoder().encodeToString(iv)); encrypt = JdkSymmetric.encrypt(padding, key, iv, msg.getBytes("utf-8")); System.out.println("encrypt:" + Base64.getEncoder().encodeToString(encrypt)); decrypt = JdkSymmetric.decrypt(padding, key, iv, this.encrypt); //System.out.println("decrypt:" + Base64.getEncoder().encodeToString(encrypt)); //System.out.println("解密原文:" + new String(decrypt, "utf-8")); return true; } }