概述
加密算法分爲單向加密和雙向加密。
單向加密包括MD5
,SHA
等摘要算法。單向加密算法是不可逆的,也就是無法將加密後的數據恢復成原始數據,除非採取碰撞攻擊和窮舉的方式。像是銀行賬戶密碼的存儲,一般採用的就是單向加密的方式。
雙向加密是可逆的,存在密文的密鑰,持有密文的一方可以根據密鑰解密得到原始明文,一般用於發送方和接收方都能通過密鑰獲取明文的情況。雙向加密包括對稱加密和非對稱加密。對稱加密包括DES
加密,AES
加密等,非對稱加密包括RSA
加密,ECC
加密。AES
算法全稱Advanced Encryption Standard
,是DES
算法的替代者,也是當今最流行的對稱加密算法之一。
AES基本概念
要想學習AES算法,首先要弄清楚三個基本的概念:密鑰、填充、模式。
密鑰
密鑰是AES
算法實現加密和解密的根本。對稱加密算法之所以對稱,是因爲這類算法對明文的加密和解密需要使用同一個密鑰。
AES支持三種長度的密鑰:
128位,192位,256位
平時大家所說的AES128,AES192,AES256,實際上就是指的AES算法對不同長度密鑰的使用。從安全性來看,AES256安全性最高。從性能來看,AES128性能最高。本質原因是它們的加密處理輪數不同。
填充
要想了解填充的概念,我們先要了解AES的分組加密特性。AES算法在對明文加密的時候,並不是把整個明文一股腦加密成一整段密文,而是把明文拆分成一個個獨立的明文塊,每一個明文塊長度128bit。
這些明文塊經過AES加密器的複雜處理,生成一個個獨立的密文塊,這些密文塊拼接在一起,就是最終的AES加密結果。
但是這裏涉及到一個問題:
假如一段明文長度是192bit,如果按每128bit一個明文塊來拆分的話,第二個明文塊只有64bit,不足128bit。這時候怎麼辦呢?就需要對明文塊進行填充(Padding)。AES在不同的語言實現中有許多不同的填充算法,我們只舉出集中典型的填充來介紹一下。
- NoPadding:
不做任何填充,但是要求明文必須是16字節的整數倍。
- PKCS5Padding(默認):
如果明文塊少於16個字節(128bit),在明文塊末尾補足相應數量的字符,且每個字節的值等於缺少的字符數。
比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6個字節,則補全爲{1,2,3,4,5,a,b,c,d,e,6,6,6,6,6,6}
- ISO10126Padding:
如果明文塊少於16個字節(128bit),在明文塊末尾補足相應數量的字節,最後一個字符值等於缺少的字符數,其他字符填充隨機數。
比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6個字節,則可能補全爲{1,2,3,4,5,a,b,c,d,e,5,c,3,G,$,6}
需要注意的是,如果在AES加密的時候使用了某一種填充方式,解密的時候也必須採用同樣的填充方式。
模式
AES的工作模式,體現在把明文塊加密成密文塊的處理過程中。AES加密算法提供了五種不同的工作模式:
ECB、CBC、CTR、CFB、OFB
模式之間的主題思想是近似的,在處理細節上有一些差別。我們這一期只介紹各個模式的基本定義。
- ECB模式(默認):
電碼本模式 Electronic Codebook Book
- CBC模式:
密碼分組鏈接模式 CipherBlock Chaining
- CTR模式:
計算器模式 Counter
- CFB模式:
密碼反饋模式 CipherFeedBack
- OFB模式:
輸出反饋模式 OutputFeedBack
如果在AES加密的時候使用了某一種工作模式,解密的時候也必須採用同樣的工作模式。
AES算法原理
AES加密算法的流程
AES加密主要包括兩個步驟:密鑰擴展和明文加密。
密鑰擴展:將輸入的密鑰(16字節、24字節和32字節)進行擴展,根據密鑰長度的不同,得到擴展後的密鑰進行加密的輪數也不相同。
密鑰擴展算法
密鑰擴展過程說明(密鑰爲16字節):
- 將初始密鑰以列爲主,轉化爲4個32 bits的字,分別記爲w[0…3];
- 按照如下方式,依次求解w[j],其中j是整數並且屬於[4,43];
- 若j%4=0,則w[j]=w[j-4]⊕g(w[j-1]),否則w[j]=w[j-4]⊕w[j-1];
函數g的流程說明:
- 將w循環左移一個字節;
- 分別對每個字節按S盒進行映射;
- 與32 bits的輪常量Rcon[j]進行異或。
輪常量(Rcon)是一個字,最右邊三個字節總爲0。因此字與Rcon相異或,其結果只是與該字最左的那個字節相異或。每輪的輪常量不同,定位爲Rcon[j] = (RC[j], 0, 0, 0)。(RC是一維數組)
RC生成函數:RC[1] = 1, RC[j] = 2 * RC[j – 1]。
因爲16字節密鑰的只進行10輪的擴展,所以最後生成的RC[j]的值按16進製表示爲:
RC值
十輪的密鑰擴展後,就能生成44個字大小的擴展密鑰。擴展後的密鑰將用於AES對明文的加密過程。
明文加密:無論是AES的加密和解密過程,都涉及到四個主要的步驟:字節代替、行移位、列混淆和輪密鑰加。以下對這四個過程進行詳細說明。
- 字節代替:將輸入狀態的每個字節使用S盒上對應的字節進行替換.
輸入狀態:是一個4×4的數組,數組內每個元素由輸入的明文分組組成,按照列進行排序,比如輸入的明文數據爲193de3bea0f4e22b9ac68d2ae9f84808,則輸入狀態爲
輸入狀態矩陣
S盒是16×16個字節組成的矩陣,行列的索引值分別從0開始,到十六進制的F結束,每個字節的範圍爲(00-FF)。
S盒
進行字節代替的時候,把狀態中的每個字節分爲高4位和低4位。高4位作爲行值,低4位作爲列值,以這些行列值作爲索引從S盒的對應位置取出元素作爲輸出,如下圖所示:
S盒替換
S盒的構造方式如下:
(1) 按字節值得升序逐行初始化S盒。在行y列x的字節值是{yx}。
(2) 把S盒中的每個字節映射爲它在有限域GF中的逆;{00}映射爲它自身{00}。
(3) 把S盒中的每個字節的8個構成位記爲(b7, b6, b5, b4, b3, b2, b1)。對S盒的每個字節的每個位做如下的變換:
S-box盒生成公式
ci指的是值爲{63}的字節c的第i位。
解密過程逆字節代替使用的是逆S盒,構造方式爲
逆S-box盒生成公式
字節d={05}。
- 行移位:狀態的第一行保持不變。第二行循環左移一個字節,第三行左移兩個字節,第四行循環左移三個字節。
正向行移位
逆向行移位將狀態中後三行執行相反方向的移位操作,如第二行向右循環移動一個字節,其他行類似。
- 列混淆:將每列中的每個字節映射爲一個新值,新值由該列中的4個字節通過函數變換得到。
正向列混淆
要注意,圖示的矩陣的乘法和加法都是定義在GF(2^8)上的。
逆向列混淆原理如下:
逆向列混淆
- 輪密鑰加(AddRoundKey):將當前分組和擴展密鑰的一部分進行按位異或。
擴展密鑰通過擴展密鑰算法已經求出來了,所以這一步的步驟就是將分組與一部分擴展密鑰按位異或操作。
輪密鑰加
輪密鑰加後的分組再進行一次輪密鑰加就能恢復原值.所以,只要經過密鑰擴展和明文加密,就能將明文加密成密文,進行解密的時候,只需要進行逆向變換即可。
圖[AES加密算法的流程]中還需要注意,明文輸入到輸入狀態後,需要進行一輪的輪密鑰加,對輸入狀態進行初始化。前9輪的加密過程,都需要進行字節替代、行移位、列混淆和輪密鑰加,但是第10輪則不再需要進行列混淆。
進行解密的時候,需要進行逆向字節替代,逆向行移位、逆向列混淆和輪密鑰加。
AES算法的Java代碼
- 加密
/**
* AES加密字符串
*
* @param content
* 需要被加密的字符串
* @param password
* 加密需要的密碼
* @return 密文
*/
public static byte[] encrypt(String content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");// 創建AES的Key生產者
kgen.init(128, new SecureRandom(password.getBytes()));// 利用用戶密碼作爲隨機數初始化出
// 128位的key生產者
//加密沒關係,SecureRandom是生成安全隨機數序列,password.getBytes()是種子,只要種子相同,序列就一樣,所以解密只要有password就行
SecretKey secretKey = kgen.generateKey();// 根據用戶密碼,生成一個密鑰
byte[] enCodeFormat = secretKey.getEncoded();// 返回基本編碼格式的密鑰,如果此密鑰不支持編碼,則返回
// null。
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");// 轉換爲AES專用密鑰
Cipher cipher = Cipher.getInstance("AES");// 創建密碼器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化爲加密模式的密碼器
byte[] result = cipher.doFinal(byteContent);// 加密
return result;
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
- 解密
/**
* 解密AES加密過的字符串
*
* @param content
* AES加密過過的內容
* @param password
* 加密時的密碼
* @return 明文
*/
public static byte[] decrypt(byte[] content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");// 創建AES的Key生產者
kgen.init(128, new SecureRandom(password.getBytes()));
SecretKey secretKey = kgen.generateKey();// 根據用戶密碼,生成一個密鑰
byte[] enCodeFormat = secretKey.getEncoded();// 返回基本編碼格式的密鑰
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");// 轉換爲AES專用密鑰
Cipher cipher = Cipher.getInstance("AES");// 創建密碼器
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化爲解密模式的密碼器
byte[] result = cipher.doFinal(content);
return result; // 明文
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}