在开源加密组件中,介绍了AES/SM4/3DES对称秘钥加密介绍及其实现 ,但是并没有对总结的结论做说明。现在从摘抄的单元测试类中,对对称加密做一轮充分的验证说明,以便加深大家对对称加密的理解;
本文所列举的所有代码均可从bq-encryptor组件 开源代码的单元测试类中获取;
加密组件引入方法:
<dependency>
<groupId>com.biuqugroupId>
<artifactId>bq-encryptorartifactId>
<version>1.0.1version>
dependency>
bq-encryptor组件 的github地址:https://github.com/woollay/bq-encryptor
秘钥长度
和分组长度
,那就需要验证下,在各种秘钥长度下,对应的秘钥长度是不是固定的;AesEncryption
和AesSecureEncryption
,二者区别是前者不带盐值,后者带盐值,但是不影响秘钥生成,就不一一列举了),秘钥长度支持128/192/256(当下没有512的AES加密算法,大家自己可以看下维基百科)BaseSingleEncryptionTest
),其秘钥生成判断逻辑如下://test1:使用任意初始值创建秘钥,秘钥始终是固定长度(和加密算法的长度相同)(3DES除外)
SecretKey key = encryption.createKey(RandomUtils.nextBytes(32));
Assert.assertTrue(key.getEncoded().length == keyLen / 8);
SecretKey key2 = encryption.createKey(RandomUtils.nextBytes(64));
Assert.assertTrue(key2.getEncoded().length == keyLen / 8);
SecretKey key3 = encryption.createKey(RandomUtils.nextBytes(1));
Assert.assertTrue(key3.getEncoded().length == keyLen / 8);
@Test
public void createKey()
{
int[] keyLenList = {128, 192, 256};
for (int keyLen : keyLenList)
{
BaseSingleEncryption encryption = new AesEncryption();
encryption.setEncryptLen(keyLen);
super.createKey(encryption, keyLen);
}
}
BaseSingleEncryptionTest
抽象类中的验证代码如下:public void toKey(BaseSingleEncryption encryption, int encryptLen)
{
//test1:可以使用任意长度值获取秘钥对象(仅能生成秘钥),但是仅合法长度可以加密
SecretKey secretKey = encryption.toKey(RandomUtils.nextBytes(encryptLen));
Assert.assertNotNull(secretKey);
byte[] data1 = RandomUtils.nextBytes(encryptLen - 1);
byte[] encBytes1 = encryption.encrypt(data1, secretKey.getEncoded(), null);
System.out.println("data1 len=" + data1.length + ",enc len=" + encBytes1.length);
Assert.assertTrue(encBytes1.length == encryptLen);
Assert.assertNotNull(encryption.toKey(RandomUtils.nextBytes(1)));
Assert.assertNotNull(encryption.toKey(RandomUtils.nextBytes(2 * encryptLen)));
Assert.assertNotNull(encryption.toKey(RandomUtils.nextBytes(3 * encryptLen + 1)));
try
{
//test2:秘钥长度高于合法秘钥长度会报错
byte[] key2 = RandomUtils.nextBytes(encryptLen + 1);
SecretKey secretKey2 = encryption.toKey(key2);
System.out.println("secretKey2 len=" + secretKey2.getEncoded().length);
byte[] data2 = RandomUtils.nextBytes(encryptLen);
byte[] encBytes2 = encryption.encrypt(data2, secretKey2.getEncoded(), null);
System.out.println("data2 len=" + data2.length + ",enc len=" + encBytes2.length);
Assert.fail();
}
catch (Exception e)
{
e.printStackTrace();
Assert.assertTrue(true);
}
try
{
//test3:秘钥长度低于合法秘钥长度会报错
byte[] key3 = RandomUtils.nextBytes(encryptLen - 1);
SecretKey secretKey3 = encryption.toKey(key3);
System.out.println("secretKey3 len=" + secretKey3.getEncoded().length);
byte[] data3 = RandomUtils.nextBytes(encryptLen);
byte[] encBytes3 = encryption.encrypt(data3, secretKey3.getEncoded(), null);
System.out.println("data3 len=" + data3.length + ",enc len=" + encBytes3.length);
Assert.fail();
}
catch (Exception e)
{
e.printStackTrace();
Assert.assertTrue(true);
}
}
@Test
public void toKey()
{
int[] keyLenList = {128, 192, 256};
for (int keyLen : keyLenList)
{
BaseSingleEncryption encryption = new AesEncryption();
encryption.setEncryptLen(keyLen);
super.toKey(encryption, 16);
}
}
- 在此次验证中,可根据异常得知:在秘钥长度不为合法值时,会抛出如下异常:
Key length not 128/192/256 bits.
,这也间接说明秘钥长度最大只有256位;- 在验证过程中,其实还可以发现无论什么长度的秘钥数据反转成秘钥对象,都不会报错,但是在使用对应的加解密方法时就会报错;
@Test
public void createKey()
{
BaseSingleEncryption encryption = new Sm4Encryption();
super.createKey(encryption, 128);
}
@Test
public void toKey()
{
BaseSingleEncryption encryption = new Sm4Encryption();
super.toKey(encryption, 16);
}
BaseSingleEncryptionTest
抽象类后,其单元测试代码如下:@Test
public void createKey() throws NoSuchAlgorithmException
{
BaseSingleEncryption encryption = new Des3Encryption();
try
{
//3DES不允许低于24byte的秘钥,因为无法解析出3个8byte的DES秘钥
super.createKey(encryption, 192);
}
catch (Exception e)
{
e.printStackTrace();
}
}
Caused by: java.security.InvalidKeyException: Wrong key size
@Test
public void testGetKey()
{
BaseSingleEncryption encryption = new Des3Encryption();
byte[] keyBytes = RandomUtils.nextBytes(24);
//test1: 任意24byte的内容均可以作为3DES的秘钥
SecretKey secretKey = encryption.toKey(RandomUtils.nextBytes(24));
System.out.println("init key=" + Hex.toHexString(keyBytes));
System.out.println("3des key=" + Hex.toHexString(secretKey.getEncoded()));
Assert.assertTrue(secretKey.getEncoded().length == 24);
//test2:秘钥对象的二进制和原始秘钥的二进制并不相同
Assert.assertFalse(Hex.toHexString(secretKey.getEncoded()).equals(Hex.toHexString(keyBytes)));
byte[] keyBytes2 = RandomUtils.nextBytes(25);
//test3: 任意大于24byte的内容均可以作为3DES的秘钥,而且只会截取前24byte
SecretKey secretKey2 = encryption.toKey(keyBytes2);
Assert.assertTrue(secretKey2.getEncoded().length == 24);
byte[] subBytes2 = ArrayUtils.subarray(keyBytes2, 0, 24);
Assert.assertTrue(Hex.toHexString(secretKey2.getEncoded()).equals(Hex.toHexString(encryption.toKey(subBytes2).getEncoded())));
}
@Test
public void testEncryptPadding()
{
int[] keyLenList = {128, 192, 256};
String[] modes = {"ECB", "CBC", "CTR", "CFB"};
String[] paddings = {"NoPadding", "PKCS5Padding"};
for (int keyLen : keyLenList)
{
BaseSingleEncryption encryption = new AesEncryption();
super.doCipher(encryption, keyLen, paddings, modes);
}
}
BaseSingleEncryptionTest
抽象类的加解密验证逻辑如下:public void doCipher(BaseSingleEncryption encryption, int keyLen, String[] paddings, String[] modes)
{
this.doCipher(encryption, keyLen, 16, paddings, modes);
}
public void doCipher(BaseSingleEncryption encryption, int keyLen, int encGroupLen, String[] paddings,
String[] modes)
{
//test1:分段(分组)加密明文长度为n的明文数据,存在填充时,密文长度为(n/encGroupLen+1)*encGroupLen的倍数(除法取整),无填充时为(n/encGroupLen)*encGroupLen的倍数(除法取整,且n必须为encGroupLen的倍数)
encryption.setEncryptLen(keyLen);
SecretKey secretKey = encryption.toKey(RandomUtils.nextBytes(keyLen / 8));
Assert.assertEquals(secretKey.getEncoded().length, keyLen / 8);
for (String mode : modes)
{
for (String padding : paddings)
{
StringBuilder alg = new StringBuilder(encryption.getAlgorithm());
alg.append("/").append(mode);
alg.append("/").append(padding);
encryption.setPaddingMode(alg.toString());
int paddingLen = 0;
if (!"NoPadding".equals(padding))
{
paddingLen = encGroupLen;
}
System.out.println("[" + keyLen + "]padding-1=" + alg);
byte[] salt = RandomUtils.nextBytes(16);
if (paddingLen > 0)
{
byte[] data1 = RandomUtils.nextBytes(1);
byte[] encBytes1 = encryption.encrypt(data1, secretKey.getEncoded(), salt);
byte[] decBytes1 = encryption.decrypt(encBytes1, secretKey.getEncoded(), salt);
System.out.println("[" + keyLen + "]padding-1=" + alg + ",enc len=" + encBytes1.length);
System.out.println("[" + keyLen + "]padding-1=" + alg + ",dec len=" + decBytes1.length);
Assert.assertTrue(encBytes1.length == (data1.length / encGroupLen) * encGroupLen + paddingLen);
Assert.assertArrayEquals(data1, decBytes1);
}
byte[] data2 = RandomUtils.nextBytes(encGroupLen);
byte[] encBytes2 = encryption.encrypt(data2, secretKey.getEncoded(), salt);
byte[] decBytes2 = encryption.decrypt(encBytes2, secretKey.getEncoded(), salt);
System.out.println("[" + keyLen + "]padding-2=" + alg + ",enc len=" + encBytes2.length);
System.out.println("[" + keyLen + "]padding-2=" + alg + ",dec len=" + decBytes2.length);
Assert.assertTrue(encBytes2.length == (data2.length / encGroupLen) * encGroupLen + paddingLen);
Assert.assertArrayEquals(data2, decBytes2);
byte[] data3 = RandomUtils.nextBytes(keyLen * 2);
byte[] encBytes3 = encryption.encrypt(data3, secretKey.getEncoded(), salt);
byte[] decBytes3 = encryption.decrypt(encBytes3, secretKey.getEncoded(), salt);
System.out.println("[" + keyLen + "]padding-3=" + alg + ",enc len=" + encBytes3.length);
System.out.println("[" + keyLen + "]padding-3=" + alg + ",dec len=" + decBytes3.length);
Assert.assertTrue(encBytes3.length == (data3.length / encGroupLen) * encGroupLen + paddingLen);
Assert.assertArrayEquals(data3, decBytes3);
}
}
}
- 在验证过程中,会发现
NoPadding
填充模式下会和其它填充模式的密文不同,非NoPadding
下会多一个分组秘钥
长度的密文;- 在验证过程中,还会发现ECB模式不支持盐值,后面直接把这个逻辑放到开源源码中去了;
@Test
public void testEncryptPadding()
{
int[] keyLenList = {128};
String[] modes = {"ECB", "CBC", "CTR", "CFB"};
String[] paddings = {"NoPadding", "PKCS5Padding"};
//test1:sm4可以分段(分组)加密明文长度为n的明文数据,密文长度为(n/16+1)*16的倍数(除法取整)
for (int len : keyLenList)
{
BaseSingleEncryption encryption = new Sm4Encryption();
super.doCipher(encryption, len, paddings, modes);
}
}
@Test
public void testEncrypt()
{
int[] keyLenList = {192};
String[] modes = {"ECB", "CBC", "CTR", "CFB"};
String[] paddings = {"NoPadding", "PKCS5Padding"};
//test1:分段(分组)加密明文长度为n的明文数据,密文长度为(n/encGroupLen+1)*encGroupLen的倍数(除法取整)
for (int len : keyLenList)
{
BaseSingleEncryption encryption = new Des3Encryption();
super.doCipher(encryption, len, 8, paddings, modes);
}
}