轉載請标明出處:http://blog.csdn.net/zhaoyanjun6/article/details/120285594
本文出自【趙彥軍的部落格】
文章目錄
- AES 簡介
- 對稱加密
- 加密模式
- 填充模式
-
- 常見填充模式
- PKCS5Padding到底是什麼?
- 偏移量
- 字元集
- 實際工作中的加密流程
- AES 加密/解密 注意的問題
- 實戰
-
- AES加解密
- AES預設實作類
- AES随機加密
AES 簡介
- DES 全稱為Data Encryption Standard,即資料加密标準,是一種使用密鑰加密的塊算法,1977年被美國聯邦政府的國家标準局确定為聯邦資料處理标準(FIPS)
- AES 密碼學中的進階加密标準(Advanced Encryption Standard,AES),又稱Rijndael加密法,是美國聯邦政府采用的一種區塊加密标準。這個标準用來替代原先的DES(Data Encryption Standard),已經被多方分析且廣為全世界所使用。
為什麼 DES 被廢棄?
我們知道資料加密标準(Data Encryption Standard: DES)的密鑰長度是56比特,是以算法的理論安全強度是2的56次方。但二十世紀中後期正是計算機飛速發展的階段,元器件制造技術的進步使得計算機的處理能力越來越強,DES将不能提供足夠的安全性。
簡單來說,DES标準的秘鑰長度要求太短,安全性不夠。
為什麼AES算法被稱為 Rijndael 算法?
1997年1月2号,美國國家标準技術研究所(National Institute of Standards and Technology: NIST)宣布希望征集進階加密标準(Advanced Encryption Standard: AES)[3],用以取代DES。AES得到了全世界很多密碼工作者的響應,先後有很多人送出了自己設計的算法。最終有5個候選算法進入最後一輪:Rijndael,Serpent,Twofish,RC6和MARS,下圖分别為其中的5位作者。最終經過安全性分析、軟硬體性能評估等嚴格的步驟,Rijndael算法獲勝。
為什麼AES算法安全性高?
AES的區塊長度固定為128位,密鑰長度則可以是128 bit,192 bit 或256位 bit 。換算成位元組長度,就是密碼必須是 16個位元組,24個位元組,32個位元組。AES密碼的長度更長了,破解難度就增大了,是以就更安全。
對稱加密
- 對稱加密 : 也就是加密秘鑰和解密秘鑰是一樣的。
- 非對稱加密 : 也就是加密秘鑰和解密秘鑰是不一樣的。
AES 是對稱加密算法,優點:加密速度快;缺點:如果秘鑰丢失,就容易解密密文,安全性相對比較差
RSA 是非對稱加密算法 , 優點:安全 ;缺點:加密速度慢
AES加密需要:明文 + 密鑰+ 偏移量(IV)+密碼模式(算法/模式/填充)
AES解密需要:密文 + 密鑰+ 偏移量(IV)+密碼模式(算法/模式/填充)
AES的算法模式一般為
AES/CBC/PKCS5Padding
加密模式
AES
的加密模式有以下幾種
- 電碼本模式(ECB)
- 密碼分組連結模式(CBC)
- 電腦模式(CTR)
- 密碼回報模式(CFB)
- 輸出回報模式(OFB)
密碼分組連結模式(CBC)
:将整段明文切成若幹小段,然後每一小段與初始塊或者上一段的密文段進行異或運算後,再與密鑰進行加密。
電碼本模式 ECB (Electronic codebook,ECB):需要加密的消息按照塊密碼的塊大小被分為數個塊,并對每個塊進行獨立加密。
根據圖示,在 CBC 模式下,使用 AES 加解密方式進行分組加解密時,需要用到的兩個參數
- 1、初始化向量,也就是偏移量
- 2、加解密秘鑰
填充模式
如電子密碼本(ECB)和密文塊連結(CBC)。 為對稱密鑰加密設計的塊密碼工作模式要求輸入明文長度必須是塊長度的整數倍,是以資訊必須填充至滿足要求。
常見填充模式
算法/模式/填充 | 16位元組加密後資料長度 | 不滿16位元組加密後長度 |
---|---|---|
AES/CBC/NoPadding | 16 | 不支援 |
AES/CBC/PKCS5Padding | 32 | 16 |
AES/CBC/ISO10126Padding | 32 | 16 |
AES/CFB/NoPadding | 16 | 原始資料長度 |
AES/CFB/PKCS5Padding | 32 | 16 |
AES/CFB/ISO10126Padding | 32 | 16 |
AES/ECB/NoPadding | 16 | 不支援 |
AES/ECB/PKCS5Padding | 32 | 16 |
AES/ECB/ISO10126Padding | 32 | 16 |
AES/OFB/NoPadding | 16 | 不支援 |
AES/OFB/PKCS5Padding | 32 | 16 |
AES/OFB/ISO10126Padding | 32 | 16 |
AES/PCBC/NoPadding | 16 | 不支援 |
AES/PCBC/PKCS5Padding | 32 | 16 |
AES/PCBC/ISO10126Padding | 32 | 16 |
PKCS5Padding到底是什麼?
為什麼 JAVA 裡指定算法時,寫的是
AES/CBC/PKCS5Padding
,每個都是什麼含義,又有什麼作用。
- AES,加解密算法
- CBC,資料分組模式
- PKCS5Padding,資料按照一定的大小進行分組,最後分剩下那一組,不夠長度,就需要進行補齊, 也可以叫
補齊模式
簡單的說:拿到一個原始資料以後,首先需要對資料進行分組,分組以後如果長度不滿足分組條件,需要進行補齊,最後形成多個分組,在使用加解密算法,對這多個分組進行加解密。是以這個過程中,AES,CBC,PKCS5Padding 缺一不可。
在對資料進行加解密時,通常将資料按照固定的大小(block size)分成多個組,那麼随之就産生了一個問題,如果分到最後一組,不夠一個 block size 了,要怎麼辦?此時就需要進行補齊操作。
補齊規則:
The value of each added byte is the number of bytes that are added, i.e. N bytes, each of value N are added.
舉例:
36 位的 UUID,如果按照 block size=16 位元組(即 128 比特),那麼就需要補齊到 48 位,差 12 個位元組。那麼最後填充的 12 個位元組的内容,都是位元組表示的 0x0c(即 12)。
偏移量
偏移量
的添加一般是為了增加
AES
加密的複雜度,增加資料的安全性。一般在
AES_256
中會使用到
偏移量
,而在
AES_128
加密中不會使用到。
字元集
在
AES
加密中,特别也要注意到字元集的問題。一般用到的字元集是
utf-8
和
gbk
。
實際工作中的加密流程
在實際的工作中,用戶端跟伺服器互動一般都是字元串格式,是以一個比較好的加密流程是:
- 加密流程 :明文通過
(有時也需要密鑰
),利用偏移量
加密算法,然後通過AES
轉碼,最後生成加密後的字元串。Base64
- 解密流程 :加密後的字元串通過
(有時也需要密鑰
),利用偏移量
解密算法,然後通過AES
轉碼,最後生成解密後的字元串。Base64
AES 加密/解密 注意的問題
AES
加密/解密的時候,通常是用在服務端和用戶端通訊的過程中,一端加密傳輸,另一端解密使用。雖然
AES
加密看似簡單,但在使用過程過程中,仍然會出現在一端加密ok,但是另一端解密失敗的情況。一旦出現
AES
解密失敗,我們可以通過以下幾個方面進行排查:
1. AES 加密/解密 使用相同的密鑰
2. 若涉及到偏移量,則AES 加密/解密 使用的偏移量要一樣
3. AES 加密/解密 要使用相同加密數位,如都使用`AES_256`
4. AES 加密/解密 使用相同的字元集
5. AES 加密/解密 使用相同的加密,填充模式,如都使用`AES/CBC/PKCS5Padding`模式
6. 由于不同開發語言(如C 和 Java)及不同開發環境(如 Java 和 Android)的影響,可能相同的加解密算法在實作上出現差異,若你們注意到這個差異,就可能導緻加解密出現問題
最後,當我們需要驗證自己的
AES
解密算法是否與别人的加密方法為一套的時候。可以讓加密方發你一份加密後的密文和加密前的明文,然後你用密文解密,看解密結果和加密方發你的是否一緻。需要注意的是,加密方給你的明文要盡量簡潔,如就
中國
二字,這樣既能看出加密方和解密方的字元集是否一緻,而且能避免複制粘貼等環節出現空格,回車等轉義字元對驗證結果的幹擾。
實戰
AES加解密
首先定義加密、解密工具類
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
/**
* AES加解密工具類
*/
public class AES {
/**
* AES加密
*
* @param key
* @param iv
* @throws GeneralSecurityException
* @throws IOException
*/
public static byte[] encryptAes(byte[] data, byte[] key, byte[] iv)
throws GeneralSecurityException, IOException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
return cipher.doFinal(data);
}
/**
* AES解密
*
* @param key
* @param iv
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static byte[] decryptAesToByteString(byte[] data, byte[] key, byte[] iv)
throws GeneralSecurityException, IOException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
return cipher.doFinal(data);
}
}
加密和解密的代碼很像,唯一的不同點是,加密
Cipher.ENCRYPT_MOD
, 解密用的是
Cipher.DECRYPT_MODE
下面我們寫一個測試代碼:
try {
//加密密碼
String key = "zhaoyanjunzhaoy1";
//偏移量
String iv = "1234567890123456";
String message = "今天是周二,我好開心";
//加密
byte[] encryResult = AES.encryptAes(message.getBytes(), key.getBytes(), iv.getBytes());
//解密
byte[] decryResult = AES.decryptAesToByteString(encryResult, key.getBytes(), iv.getBytes());
System.out.println("解密資料 = " + new String(decryResult));
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
輸出結果:
解密資料 = 今天是周二,我好開心
可以看到資料已經正常解密了。
AES預設實作類
不帶模式和填充來擷取AES算法的時候,其預設使用
AES/ECB/PKCS5Padding
(輸入可以不是16位元組,也不需要填充向量), 是以不需要偏移量參數
我下面封裝一個工具類
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
/**
* AES加解密工具類
*/
public class AES {
/**
* AES加密
*
* @param key
* @throws GeneralSecurityException
*/
public static byte[] encryptAes(byte[] data, byte[] key) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
return cipher.doFinal(data);
}
/**
* AES解密
*
* @param key
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static byte[] decryptAesToByteString(byte[] data, byte[] key)
throws GeneralSecurityException, IOException {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"));
return cipher.doFinal(data);
}
}
測試代碼
public class T2 {
public static void main(String[] args) {
try {
//加密密碼
String key = "zhaoyanjunzhaoy1";
//加密正文
String message = "今天是周二,我好開心";
//加密
byte[] encryResult = AES.encryptAes(message.getBytes(), key.getBytes());
//解密
byte[] decryResult = AES.decryptAesToByteString(encryResult, key.getBytes());
System.out.println("解密資料 = " + new String(decryResult));
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
}
測試結果
解密資料 = 今天是周二,我好開心
AES随機加密
在上面的例子中,我們在 AES 加密中,需要指定規定長度的密碼,偏移量。在 Java 中還給我們提供了 KeyGenerator 類來随機生成一個密碼和偏移量,解決了我們動腦想密碼的問題。
我們來看看随機加密怎麼用。
/**
* AES加密/解密
*
* @throws GeneralSecurityException
*/
public void encryptAes() throws GeneralSecurityException {
//原始資料
String message = "今天是周四,好開心哦";
byte[] data = message.getBytes();
//指定加密類型
KeyGenerator keygen = KeyGenerator.getInstance("AES");
//指定秘鑰長度
keygen.init(256);
SecretKey secretKey = keygen.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
//擷取秘鑰
byte[] key = secretKey.getEncoded();
//擷取偏移量
byte[] iv = cipher.getIV();
//解密資料
byte[] ciphertext = cipher.doFinal(data);
//解密資料
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
byte[] decryMessage = cipher.doFinal(ciphertext);
System.out.println("解密資料 = " + new String(decryMessage));
}
這種加密 key , iv 都是随機産生的,每次加密後的密文都不一樣,适合一定的特殊場景。
随機是怎麼發生的,我們就看
在 init 方法的時候,
JceSecurity.RANDOM
産生随機數,源碼如下:
public final void init(int opmode, Key key) throws InvalidKeyException {
init(opmode, key, JceSecurity.RANDOM);
}
具體細節就不看了,知道原理就行。