轉載地址:JAVA版AES密碼編譯演算法
密碼學中的進階加密標準(Advanced Encryption Standard,AES),又稱Rijndael加密法,是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的DES,已經被多方分析且廣為全世界所使用。經過五年的甄選流程,進階加密標準由美國國家標準與技術研究院 (NIST)於2001年11月26日發佈於FIPS PUB 197,並在2002年5月26日成為有效標準。2006年,進階加密標準已然成為對稱金鑰密碼編譯中最流行的演算法之一。
該演算法為比利時密碼學家Joan Daemen和Vincent Rijmen所設計,結合兩位作者的名字,以Rijndael之命名之,投稿進階加密標準的甄選流程。(Rijdael的發音近於 “Rhinedoll”)
AES 是一個新的可以用於保護電子資料的密碼編譯演算法。明確地說,AES 是一個迭代的、對稱金鑰分組的密碼,它可以使用128、192 和 256 位密鑰,並且用 128 位(16位元組)區塊編碼器和解密資料。與公用密鑰密碼使用金鑰組不同,對稱金鑰密碼使用相同的祕密金鑰加密和解密資料。通過分組密碼返回的加密資料的位元與輸入資料相同。迭代加密使用一個迴圈結構,在該迴圈中重複置換(permutations )和替換(substitutions)輸入資料。Figure 1 顯示了 AES 用192位金鑰組一個16位位元組資料區塊進行加密和解密的情形。
對稱密碼體制的發展趨勢將以分組密碼為重點。分組密碼演算法通常由密鑰擴充演算法和加密(解密)演算法兩部分組成。密鑰擴充演算法將b位元組使用者主要金鑰擴充成r個子密鑰。密碼編譯演算法由一個密碼學上的弱函數f與r個子密鑰迭代r次組成。混亂和密鑰擴散是分組密碼演算法設計的基本原則。抵禦已知明文的差分和線性攻擊,可變長密鑰和分組是該體制的設計要點。
AES是美國國家標準技術研究所NIST旨在取代DES的21世紀的加密標準。
AES的基本要求是,採用對稱分組密碼體制,密鑰長度的最少支援為128、192、256,分組長度128位,演算法應易於各種硬體和軟體實現。1998年NIST開始AES第一輪分析、測試和徵集,共產生了15個候選演算法。1999年3月完成了第二輪AES2的分析、測試。2000年10月2日美國政府正式宣布選中比利時密碼學家Joan Daemen 和 Vincent Rijmen 提出的一種密碼演算法RIJNDAEL 作為 AES.
AES加密資料塊大小最大是256bit,但是密鑰大小在理論上沒有上限。AES加密有很多輪的重複和變換。大致步驟如下:
1. 密鑰擴充(KeyExpansion)
2. 初始輪(Initial Round)
3. 重複輪(Rounds),每一輪又包括:SubBytes、ShiftRows、MixColumns、AddRoundKey
4. 最終輪(Final Round),最終輪沒有MixColumns。
import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.KeyGenerator;import javax.crypto.NoSuchPaddingException;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;import java.io.UnsupportedEncodingException;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;public abstract class AesHelper { /** * 加密 * * @param content 需要加密的內容 * @param salt 加密密碼 * @return */ public static byte[] encrypt(String content, String salt) { try { SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); random.setSeed(salt.getBytes()); KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128, random); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "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 (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return null; } /** * 解密 * * @param content 待解密內容 * @param salt 解密密鑰 * @return */ public static byte[] decrypt(byte[] content, String salt) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(salt.getBytes()); kgen.init(128, secureRandom); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "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; } public static String bytesToHexString(byte[] src) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < src.length; i++) { String hex = Integer.toHexString(src[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } sb.append(hex.toUpperCase()); } return sb.toString(); } public static String encryptToStr(String content, String password){ return bytesToHexString(encrypt(content,password)); } public static byte[] decrypt(String content, String keyWord) { return decrypt(hexStringToBytes(content), keyWord); } public static byte[] hexStringToBytes(String hexString) { if (hexString.length() < 1) return null; byte[] result = new byte[hexString.length() / 2]; for (int i = 0; i < hexString.length() / 2; i++) { int high = Integer.parseInt(hexString.substring(i * 2, i * 2 + 1), 16); int low = Integer.parseInt(hexString.substring(i * 2 + 1, i * 2 + 2), 16); result[i] = (byte) (high * 16 + low); } return result; }}
以下是TestCase:
public class AesTest { @Test public void testAes(){ String content = "carl.zhao"; String Key = "http://www.csdn.net"; //加密 String encryptResult = AesHelper.encryptToStr(content, Key); //解密 byte[] decryptResult = AesHelper.decrypt(encryptResult,Key); Assert.assertEquals(content, new String(decryptResult)); }}
以上代碼是正確的,我之前再使用其它代碼的時候遇到了下面的問題:在Windows上運行沒有問題,但是在Linux上運行時提示如下錯誤:
javax.crypto.BadPaddingException: Given final block not properly padded
這個是因為 : SecureRandom實現完全隨作業系統本身的內部狀態。該實現在 windows 上每次產生的 key 都相同。但是在 solaris 或部分 linux 系統上則不同。大家可以通過日誌列印來確認一這點。
我之前的代碼如下:
secureRandom = new SecureRandom(seed.getBytes());
修改後的正確代碼如下:
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(seed.getBytes());