AES演演算法

2023-06-26 06:01:20

1.前言

對接資方的時候用到了AES加密演演算法,之前經常用但是沒好好梳理一下,這次有空就簡單梳理一下,方面日後整理學習。
AES是加密演演算法其中的一種,它是屬於對稱加密,對稱加密的意思就是,加密以及解密用的都是同一個Key。相比於非對稱加密RSA,SM2等,它的優點就是快。
為了瞭解AES,我們大概從以下幾個方面入手

  • 金鑰支援的長度
  • 常用的工作模式(ECB模式/CBC模式)
  • Padding 的填充模式

2. 金鑰

金鑰是AES演演算法實現加解密的根本。AES 支援三種長度的金鑰: 128bit (16B), 192bit(24B) , 256bit(32B)。
AES256 安全性最高,AES128效能最優。本質是它們的加密處理輪數不同。

AES128 10輪
AES192 12輪
AES256 14輪

而我們預設的就是AES128,也就是會迴圈10輪。

public static String generateAESKey() {
	KeyGenerator keyGenerator = null;
	try {
		keyGenerator = KeyGenerator.getInstance("AES");
	} catch (NoSuchAlgorithmException e) {
		return null;
	}
        // 這裡程式碼祕鑰的長度,128,192,256
	keyGenerator.init(128);
	SecretKey key = keyGenerator.generateKey();
	byte[] keyExternal = key.getEncoded();
	return Base64.encodeBase64String(keyExternal);
}

我們可以從如下圖看出,生成的key的祕鑰陣列大小為 16,其中有些字元不是正常顯示的ASCII。
也就是說, 我們可以隨機生成一段長度為16、24、32大小的字串也可以充當祕鑰。

System.out.println(RandomUtil.randomString(16));

當然這種祕鑰我理解是不安全的,不建議使用

3. 工作模式

3.1. ECB模式

ECB 模式是最簡單塊密碼加密模式,加密前根據加密塊大小(AES 128位元)分成若干塊,之後將每塊使用相同的金鑰單獨加密,在該模式下,每個明文塊的加密都是獨立的,互不影響的。解密同理。
優勢

  • 簡單
  • 有利於平行計算

缺點

  • 相同的明文塊經過加密會變成相同的密文塊,因此安全性較差。

3.2. CBC模式

CBC模式引入一個新的概念:初始向量IV。IV的作用和MD5的"加鹽"有些類似,目的是防止同樣的明文塊始終加密成相同的密文塊。
CBC模式原理:在每個明文塊加密前會讓那個明文塊和IV向量先做互斥或操作。IV作為初始化變數,參與第一個明文塊的互斥或,後續的每個明文塊和它前一個明文塊所加密出的密文塊相互斥或,這樣相同的明文塊加密出來的密文塊顯然不一樣。
優勢

  • 安全性更高

缺點

  • 無法平行計算,效能上不如ECB
  • 引入初始向量IV,增加複雜度

4. 常用的填充方式

AES 演演算法在對明文加密的時候,並不是把整個明文加密成一整段密文,而是把明文拆分成幾組獨立的明文塊,每一個明文塊的長度128bit(16B),最後不足128bit(16B),會根據不同的Padding 填充模式進行填充,然後進行加密。
總結:加密過程是先處理pading,後加密。解密過程是先進行分塊解密,最後在處理Padding。
例如:一段明文的長度198bit,按照128bit 拆分,第二個之後70bit,不足128bit,就需要對明文塊進⾏填充(Padding)

4.1 NoPadding

不做任何填充,要求明文必須是16位元組的整數倍。

4.2 PKC5Padding(推薦)

明文塊少於128bit(16B),在明文塊的末尾補足相應數量的字元,且每個位元組的值等於缺少的字元數。
如 明文:{1,2,3,4,5,6,7,8, a,b,c},缺少5個位元組,則補全為{1,2,3,4,5,6,7,8, a,b,c,5,5,5,5,5,5}

4.3 ISO10126Padding

明文塊少於128bit(16B),在明文塊的末尾補足相應數量的字元,最後一個字元值等於缺少的字元數,其他字元填充亂數
如 明文{1,2,3,4,5,6,7,8, a,b,c},缺少5個位元組,則補全為{1,2,3,4,5,6,7,8, a,b,c,e,i,o,p,k,5}
具體介面使用,請根據自己的業務需求去選擇。

5. AES程式碼範例

private static final String KEY_ALGORITHM = "AES";
private static final String ENCODING = StandardCharsets.UTF_8.name();
private static final String AES_ECB_MODE = "AES/ECB/PKCS5Padding";
private static final String AES_CBC_MODE = "AES/CBC/PKCS5Padding";

// 因為AES塊 為16位元組,所以IV必須為16位元組
private static final String IV = RandomUtil.randomString(16);
private static final IvParameterSpec ips = new IvParameterSpec(IV.getBytes());

5.1 對稱key的生成

// key只要在16、24、32大小即可

public static String generateAESKey() {
	KeyGenerator keyGenerator = null;
	try {
		keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
	} catch (NoSuchAlgorithmException e) {
		return null;
	}
	keyGenerator.init(128);
	SecretKey key = keyGenerator.generateKey();
	byte[] keyExternal = key.getEncoded();
	return Base64.encodeBase64String(keyExternal);
}

5.2 加密

public static String encrypt(String content, String key) {
	try {
		byte[] bytesKey = Base64.decodeBase64(key);
		SecretKeySpec secretKey = new SecretKeySpec(bytesKey, KEY_ALGORITHM);
		Cipher cipher = Cipher.getInstance(AES_ECB_MODE);// 建立密碼器
		byte[] byteContent = content.getBytes(ENCODING);
		cipher.init(Cipher.ENCRYPT_MODE, secretKey);// 初始化
		byte[] result = cipher.doFinal(byteContent);// 加密
		return Base64.encodeBase64String(result);
	} catch (Exception e) {
	}
	return null;
}

5.3 解密

public static String decrypt(String content, String key) {
	try {
		byte[] bytesKey = Base64.decodeBase64(key);
		SecretKeySpec secretKey = new SecretKeySpec(bytesKey, KEY_ALGORITHM);
		Cipher cipher = Cipher.getInstance(AES_ECB_MODE);// 建立密碼器
		cipher.init(Cipher.DECRYPT_MODE, secretKey);// 初始化
		byte[] result = cipher.doFinal(Base64.decodeBase64(content));// 解密
		return new String(result);
	} catch (Exception e) {
	}
	return null;
}

備註:如果是CBC的模式,只要在cipher.init加入向量的ips即可