一,概述
前面講解了大體通用的對稱,非對稱,MD系列等加密算法。
見前面 我的博客文: https://blog.csdn.net/Mynah886/article/details/82787683
但感覺還是不能直觀的讓開發者來直接爽起來。最近又看到了其他人分享的,自己項目正好又要涉及使用了。故此重新細化下。供使用時候直接拿來!
對稱加密算法就是傳統的用一個密碼進行加密和解密。就像我們常用的WinZIP && WinRAR 對壓縮包的加密和解密,就是使用對稱加密算法。
代表算法/實現方式:
1) DES(Data Encryption standard)數據加密標準 3DES, 密鑰長度一般 56/64. DES算法由於密鑰過短,可以在短時間內被暴力破解,所以現在已經不安全了
2) AES高級DES替代者(工作模式:ECB, CBC, PCBC, CTR, CFB8~128... 填充方式:NoPadding, PKCS5Padding, ISO10126Padding...), 密鑰長度一般 128/192/256.
3) RC4/5/6算法: 是參數可變的分組密碼算法,三個可變的參數是:分組大小、密鑰大小和加密輪數; 加密時使用了2r+2個密鑰相關的的32位字: 這裏r表示加密的輪數。
4) Blowfish算法: 每次加密一個64位分組,使用32位~448位的可變長度密鑰,應用於內部加密,輸入64位明文,輸出64位密文。加密過程分爲兩個階段:密鑰預處理和信息加密。 自從32位處理器誕生後,blowfish加密算法在加密速度上就超越了DES加密算法,引起了人們的關注。blowfish加密算法沒有註冊專利,不需要授權,可以免費使用
5) PBE(Password Based Encryption)基於口令加密 = 口令+鹽
6)IDEA,早於AES替代出來取代DES. 密鑰長度128 . 工作模式:ECB, 填充方式:PKCS5Padding/PKCS7Padding/... 。實現方式有Bouncy Castle
注意: 密鑰長度直接決定加密強度,而工作模式和填充模式可以看成是對稱加密算法的參數和格式選擇。Java標準庫提供的算法實現並不包括所有的工作模式和所有填充模式,但是通常我們只需要挑選常用的使用就可以了。
從程序的角度看,所謂加密,就是這樣一個函數,它接收密碼和明文,然後輸出密文:
secret = encrypt(key, message);
而解密則相反,它接收密碼和密文,然後輸出明文:
plain = decrypt(key, secret);
二, 開發DEMO
2.1 我們先用AES加密算法 && ECB模式加密並解密。
Java標準庫提供的對稱加密接口非常簡單,使用時按以下步驟編寫代碼:
-
根據算法名稱/工作模式/填充模式獲取Cipher實例;
-
根據算法名稱初始化一個SecretKey實例,密鑰必須是指定長度;
-
使用SerectKey初始化Cipher實例,並設置加密或解密模式;
-
傳入明文或密文,獲得密文或明文。
/**
* desc- 對稱加密,ECB模式加密並解密
* 加密算法 AES, 填充方式 ECB
* auth xupengfei
*
* @param key 加密key
* @param input 需要加密的字節數組
* @return 加密後的字節數組
*/
public static byte[] encrypt( byte[] key, byte[] input) {
byte[] result = null;
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
result = cipher.doFinal(input);
}catch (Exception ex){
log.error("EX-------- {}", ex);
}
return result;
}
/**
* desc 對稱 解密,ECB模式加密並解密
* 加密算法 AES, 填充方式 ECB
* auth xupengfei
*
* @param key 加密key
* @param input 需要解密的字節數組
* @return 解密後的字節數組
* @throws GeneralSecurityException
*/
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
/**
* 測試類
* @param args
* @throws Exception
*/
@Test
public void testAesECB() throws Exception {
// 原文:
String message = "Hello world,I am DCONE member, please call me mynah!";
log.debug("Message: " + message);
// 128位密鑰 = 16 bytes Key:
byte[] key = "xupengfei1234567".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(key, data);
log.debug("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
log.debug("Decrypted: " + new String(decrypted, "UTF-8"));
}
2.1 測試結果如下:
2.2 ECB模式是最簡單的AES加密模式,它只需要一個固定長度的密鑰,固定的明文會生成固定的密文,這種一對一的加密方式會導致安全性降低,更好的方式是通過CBC模式,它需要一個隨機數作爲IV參數,這樣對於同一份明文,每次生成的密文都不同。
在CBC模式下,需要一個隨機生成的16字節IV參數,必須使用 SecureRandom
生成。因爲多了一個IvParameterSpec
實例,因此,初始化方法需要調用Cipher
的一個重載方法並傳入IvParameterSpec
。 可以發現每次生成的IV不同,密文也不同。
/**
* desc 對稱加密, CBC 模式加密並解密
* 加密算法 AES, 填充方式 CBC
* auth xupengfei
*
* @param key
* @param input
* @return
* @throws GeneralSecurityException
*/
public static byte[] encrypt2(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// CBC模式 要生成一個 16bytes 的initialization vector
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] iv = sr.generateSeed(16);
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
byte[] data = cipher.doFinal(input);
// IV不需要保密,把 IV && 密文一起返回
return join(iv, data);
}
/**
* desc 對稱 解密,CBC模式加密並解密
* 加密算法 AES, 填充方式 CBC
* auth xupengfei
*
* @param key
* @param input
* @return
* @throws GeneralSecurityException
*/
public static byte[] decrypt2(byte[] key, byte[] input) throws GeneralSecurityException {
// 把 入參input 分割成: IV && 密文
byte[] iv = new byte[16];
byte[] data = new byte[input.length - 16];
System.arraycopy(input, 0, iv, 0, 16);
System.arraycopy(input, 16, data, 0, data.length);
// 解密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
return cipher.doFinal(data);
}
/**
*
*
* @param bs1
* @param bs2
* @return
*/
public static byte[] join(byte[] bs1, byte[] bs2) {
byte[] r = new byte[bs1.length + bs2.length];
System.arraycopy(bs1, 0, r, 0, bs1.length);
System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
return r;
}
/**
* 測試類 CBC 模式
*
* @param args
* @throws Exception
*/
@Test
public void testAesCBC() throws Exception {
// 明文
String message = "Hello world,I am DCONE member, please call me mynah, ouye!";
log.debug("Message: " + message);
// 256位密鑰 = 32 bytes Key:
byte[] key = "0123456789abcdef0123456789abcdef".getBytes("UTF-8");
// 加密
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(key, data);
log.debug("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密
byte[] decrypted = decrypt(key, encrypted);
log.debug("Decrypted: " + new String(decrypted, "UTF-8"));
}
2.2 測試結果如下: