ga('set', 'anonymizeIp', 1);
本文介紹AES加密,並以JAVA實作AES256加解密程式。
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 | 密鑰長度(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:每一行的四個位元組透過線性變換互相結合。
假設共有N rounds。
ROUND 0
- Add round key
ROUND 1 ~ ROUND (N-1)
- SubBytes()
- ShiftRows()
- MixColumns()
- AddRoundKey()
ROUND N
- SubBytes()
- ShiftRows()
- AddRoundKey()
以回合數相反方向開始。
ROUND N
- AddRoundKey()
- ShiftRows()
- SubBytes()
ROUND (N-1) ~ ROUND 1
- AddRoundKey()
- MixColumns()
- ShiftRows()
- SubBytes()
ROUND 0
- AddRoundKey()
以下內容使用JAVA實作AES-256加解密。
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
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
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
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 10000000 (8=>128)
//然後與第二個值的二進位值作聯集ex: 10000000 | 00001100 => 10001100 (137)
int value = (high 127)
value -= 256;
//最後轉回byte就OK
rawData [i] = (byte) value;
}
return rawData ;
}
// byte to hex convert
public static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i 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 getStrList(String inputString, int length,
int size) {
List list = new ArrayList();
for (int index = 0; index str.length())
return null;
if (t > str.length()) {
return str.substring(f, str.length());
} else {
return str.substring(f, t);
}
}
}
首先我們使用的是AES加密算法
public static final String KEY_ALGORITHM="AES";
接著是以參數定義transformation,
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。
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 };}
自動生成金鑰
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之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 初始化。 |
cipher.init(Cipher.ENCRYPT_MODE, k);
如出現以下錯誤訊息: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金鑰長度問題。
使用不同的Padding方式會影響當分組明文不足16bytes之加密過程,明文的編碼不同也需要先轉換編碼再進行加密。下方列出進行加密前可能所需的額外明文處理funtions。
HEX String to Byte[]
// hex to byte convert
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 10000000 (8=>128)
//然後與第二個值的二進位值作聯集ex: 10000000 | 00001100 => 10001100 (137)
int value = (high 127)
value -= 256;
//最後轉回byte就OK
rawData [i] = (byte) value;
}
return rawData ;
}
Byte[] to HEX String
// byte to hex convert
public static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i
String Split According to Input Length
// string split s
public static List 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 getStrList(String inputString, int length,
int size) {
List list = new ArrayList();
for (int index = 0; index str.length())
return null;
if (t > str.length()) {
return str.substring(f, str.length());
} else {
return str.substring(f, t);
}
}
以上為JAVA實作AES256加解密之方法。