本文介紹AES加密,並以JAVA實作AES256加解密程式。
AES
AES(Advanced Encryption Standard)高階加密標準,最常見的為對稱加密演算法,對稱加密演算法即為加密解密方均使用同一組密鑰。
加密介紹
代號 | 意義 |
---|---|
P | 明文 |
K | 密鑰 |
E | AES加密函式 |
D | AES解密函式 |
C | 密文 |
C = E(P,K)
明文(P)使用密鑰(K)透過加密函式(E)產生密文(C)。
P = D(C,K)
密文(C)使用密鑰(K)透過解密函式(D)產生明文(P)。
AES金鑰長度分類
AES | 密鑰長度(bytes) | 分組長度(bytes) | 加密回合(rounds) |
---|---|---|---|
AES-128 | 16 | 16 | 10 |
AES-192 | 24 | 16 | 12 |
AES-256 | 32 | 16 | 14 |
AES加密中,明文每個分組只能是128位(16bytes),而金鑰可以是128、192、256位,依照密鑰長度不同,加密回合數也會不同。
限制
AES加密演算法中,原始資料明文(P)長度須為16bytes的倍數,所以當資料不足16bytes時,加密前必須先將資料補足為16bytes,解密後再將之移除還原為原始資料明文。
加密步驟
依照密鑰長度不同,加密回合數(round)也不同。
回合數
AES-128 -> 10 rounds
AES-192 -> 12 rounds
AES-256 -> 14 rounds
加密函數
參考資料:維基百科
- Add round key: 對資料(128bits or 16 bytes) ⊕ 回合金鑰。回合金鑰是由主金鑰在每回合(透過Rijndael方法產生)產生之密鑰。
- SubBytes:矩陣中的各位元組透過一個8位元的S-box進行轉換。
- ShiftRows:每一列都向左循環位移某個偏移量。
- MixColumns:每一行的四個位元組透過線性變換互相結合。
每round行為
假設共有N rounds。
加密ROUND behaviers
ROUND 0
- Add round key
ROUND 1 ~ ROUND (N-1)
- SubBytes()
- ShiftRows()
- MixColumns()
- AddRoundKey()
ROUND N
- SubBytes()
- ShiftRows()
- AddRoundKey()
解密ROUND behaviers
以回合數相反方向開始。
ROUND N
- AddRoundKey()
- ShiftRows()
- SubBytes()
ROUND (N-1) ~ ROUND 1
- AddRoundKey()
- MixColumns()
- ShiftRows()
- SubBytes()
ROUND 0
- AddRoundKey()
實作 (JAVA)
以下內容使用JAVA實作AES-256加解密。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
import java.io.UnsupportedEncodingException; import java.security.Key; import java.security.Security; import java.util.ArrayList; import java.util.List; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class AES256 { public static final String KEY_ALGORITHM="AES"; //public static final String CIPHER_ALGORITHM="AES/ECB/PKCS5Padding"; public static final String CIPHER_ALGORITHM="AES/ECB/NoPadding"; // Random generate key - not used public static byte[] initkey() throws Exception{ //例項化金鑰生成器 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); KeyGenerator kg=KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); kg.init(256); kg.init(128); SecretKey secretKey=kg.generateKey(); return secretKey.getEncoded(); } // private key table public static byte[] initRootKey() throws Exception { /*return new byte[] { 0x08, 0x08, 0x04, 0x0b, 0x02, 0x0f, 0x0b, 0x0c, 0x01, 0x03, 0x09, 0x07, 0x0c, 0x03, 0x07, 0x0a, 0x04, 0x0f, 0x06, 0x0f, 0x0e, 0x09, 0x05, 0x01, 0x0a, 0x0a, 0x01, 0x09, 0x06, 0x07, 0x09, 0x0d };*/ return new byte[] { (byte) 0x9D, (byte) 0xD2, 0x00, 0x24, (byte) 0x84, 0x6A, 0x2E, (byte) 0xDA, 0x0C, (byte) 0xDD, 0x72, 0x7B, 0x05, (byte) 0xC5, 0x6B, 0x01, (byte) 0xFF, 0x17, (byte) 0xCD, (byte) 0x9F, (byte) 0x8C, 0x1E, 0x3E, 0x09, (byte) 0xCF, 0x2F, 0x0C, 0x77, (byte) 0x87, (byte) 0xEF, (byte) 0x8A, (byte) 0xEC }; } // generate key from private key public static Key toKey(byte[] key) throws Exception { SecretKey secretKey=new SecretKeySpec(key,KEY_ALGORITHM); return secretKey; } // encrypt function public byte[] encrypt(byte[] data) throws Exception { Key k = toKey(initRootKey()); Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, "BC"); cipher.init(Cipher.ENCRYPT_MODE, k); return cipher.doFinal(data); } // decrypt function public byte[] decrypt(byte[] data) throws Exception { Key k =toKey(initRootKey()); Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM, "BC"); cipher.init(Cipher.DECRYPT_MODE, k); return cipher.doFinal(data); } // hex to byte convert function public static byte[] hexToBytes(String hexString) { char[] hex = hexString.toCharArray(); //轉rawData長度減半 int length = hex.length / 2; byte[] rawData = new byte[length]; for (int i = 0; i < length; i++) { //先將hex資料轉10進位數值 int high = Character.digit(hex[i * 2], 16); int low = Character.digit(hex[i * 2 + 1], 16); //將第一個值的二進位值左平移4位,ex: 00001000 => 10000000 (8=>128) //然後與第二個值的二進位值作聯集ex: 10000000 | 00001100 => 10001100 (137) int value = (high << 4) | low; //與FFFFFFFF作補集 if (value > 127) value -= 256; //最後轉回byte就OK rawData [i] = (byte) value; } return rawData ; } // byte to hex convert function public static String byte2Hex(byte[] bytes){ StringBuffer stringBuffer = new StringBuffer(); String temp = null; for (int i=0;i<bytes.length;i++){ temp = Integer.toHexString(bytes[i] & 0xFF); if (temp.length()==1){ //1得到一位的進行補0操作 stringBuffer.append("0"); } stringBuffer.append(temp); } return stringBuffer.toString(); } // string split functions public static List<String> getStrListEnter(String inputString, int length) { int size = inputString.length() / length; if (inputString.length() % length != 0) { size += 1; } return getStrList(inputString, length, size); } public static List<String> getStrList(String inputString, int length, int size) { List<String> list = new ArrayList<String>(); for (int index = 0; index < size; index++) { String childStr = substring(inputString, index * length, (index + 1) * length); list.add(childStr); } return list; } public static String substring(String str, int f, int t) { if (f > str.length()) return null; if (t > str.length()) { return str.substring(f, str.length()); } else { return str.substring(f, t); } } } |
程式碼解析
首先我們使用的是AES加密算法
1 2 3 |
public static final String KEY_ALGORITHM="AES"; |
接著是以參數定義transformation,
1 2 3 |
public static final String CIPHER_ALGORITHM="AES/ECB/NoPadding"; |
參數意義如下表:
參數 | 值 | 意義 |
---|---|---|
algorithm | AES | 必填,使用之加密算法(EX: RSA, AES, SHA-256等) |
mode | ECB | 非必填,分組密碼運作模式。詳細請參考下方"Mode表"。 |
padding | NoPadding | 非必填,指定每次處理的bit數量。詳細請參考下方"Transform表"。 |
Mode表
Mode | 名稱 | 描述 |
---|---|---|
ECB | Electronic CodeBook | 使用相同密鑰對明文分組加密 |
CBC | Cipher Block Chaining | 加密算法之input為上個密文組合下個明文組之XOR。(加密算法=前一密文組⊕下一明文組) |
CFB | Cipher FeedBack | 與CBC相似,前一密文作為加密算法之input,產生之密文MSB (X bit)與明文之X bit XOR後產生密文之X bit。 |
OFB | Output FeedBack | 與CFB相似,只是加密算法之input為前次加密算法之output。 |
CTR | Counter | 每組明文都與一經過加密之counter XOR,之後counter遞增。 |
Transform表
Transform | 16byte加密後長度 | 不滿16byte加密後長度 |
---|---|---|
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 |
金鑰 (私鑰)
我們可以初始化一固定金鑰作為密鑰,也可以自動生成。
固定金鑰:
Key之byte[] 長度為16bytes。
1 2 3 4 5 6 |
public static byte[] initRootKey() throws Exception {return new byte[] { (byte) 0x9D, (byte) 0xD2, 0x00, 0x24, (byte) 0x84, 0x6A, 0x2E, (byte) 0xDA, 0x0C, (byte) 0xDD, 0x72, 0x7B, 0x05, (byte) 0xC5, 0x6B, 0x01, (byte) 0xFF, 0x17, (byte) 0xCD, (byte) 0x9F, (byte) 0x8C, 0x1E, 0x3E, 0x09, (byte) 0xCF, 0x2F, 0x0C, 0x77, (byte) 0x87, (byte) 0xEF, (byte) 0x8A, (byte) 0xEC };} |
自動生成金鑰
1 2 3 4 5 6 7 8 9 |
public static byte[] initkey() throws Exception{//例項化金鑰生成器 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); KeyGenerator kg=KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); kg.init(256); kg.init(128); SecretKey secretKey=kg.generateKey(); return secretKey.getEncoded();} |
加解密Cipher屬性設定
Cipher之7屬性:
Modifier and Type | Field and Description |
---|---|
static int | DECRYPT_MODE用於解密模式下的 Cipher 初始化。 |
static int | ENCRYPT_MODE用於加密模式下的 Cipher 初始化。 |
static int | PRIVATE_KEY用於說明解包模式下密鑰是是私鑰。 |
static int | PUBLIC_KEY用於說明解包模式下密鑰是公鑰。 |
static int | SECRET_KEY用於說明解包模式下密鑰是密鑰。 |
static int | UNWRAP_MODE用於解包密鑰模式下的 Cipher 初始化。 |
static int | WRAP_MODE用於包裝密鑰模式下的 Cipher 初始化。 |
1 |
cipher.init(Cipher.ENCRYPT_MODE, k); |
JAVA – 加密預設金鑰長度限制
如出現以下錯誤訊息:java.security.InvalidKeyException: Illegal key size or default parameters
請先至Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 下載policy檔案,後將local_policy.jar及US_export_policy.jar 取代jre/security底下的兩個檔案即可解除JAVA金鑰長度問題。
可能需用到之Functions
使用不同的Padding方式會影響當分組明文不足16bytes之加密過程,明文的編碼不同也需要先轉換編碼再進行加密。下方列出進行加密前可能所需的額外明文處理funtions。
HEX String to Byte[]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// hex to byte convert function public static byte[] hexToBytes(String hexString) { char[] hex = hexString.toCharArray(); //轉rawData長度減半 int length = hex.length / 2; byte[] rawData = new byte[length]; for (int i = 0; i < length; i++) { //先將hex資料轉10進位數值 int high = Character.digit(hex[i * 2], 16); int low = Character.digit(hex[i * 2 + 1], 16); //將第一個值的二進位值左平移4位,ex: 00001000 => 10000000 (8=>128) //然後與第二個值的二進位值作聯集ex: 10000000 | 00001100 => 10001100 (137) int value = (high << 4) | low; //與FFFFFFFF作補集 if (value > 127) value -= 256; //最後轉回byte就OK rawData [i] = (byte) value; } return rawData ; } |
Byte[] to HEX String
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// byte to hex convert function public static String byte2Hex(byte[] bytes){ StringBuffer stringBuffer = new StringBuffer(); String temp = null; for (int i=0;i<bytes.length;i++){ temp = Integer.toHexString(bytes[i] & 0xFF); if (temp.length()==1){ //1得到一位的進行補0操作 stringBuffer.append("0"); } stringBuffer.append(temp); } return stringBuffer.toString(); } |
String Split According to Input Length
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// string split functions public static List<String> getStrListEnter(String inputString, int length) { int size = inputString.length() / length; if (inputString.length() % length != 0) { size += 1; } return getStrList(inputString, length, size); } public static List<String> getStrList(String inputString, int length, int size) { List<String> list = new ArrayList<String>(); for (int index = 0; index < size; index++) { String childStr = substring(inputString, index * length, (index + 1) * length); list.add(childStr); } return list; } public static String substring(String str, int f, int t) { if (f > str.length()) return null; if (t > str.length()) { return str.substring(f, str.length()); } else { return str.substring(f, t); } } |
以上為JAVA實作AES256加解密之方法。
留言