最後編輯時間 2017/05/27

平台

Android Studio

  • compileSdkVersion 25
  • buildToolsVersion “25.0.1”

Base64的轉換有使用到Android的函式庫,若是純Java的使用者必須自行搜尋替代方案。

金鑰初始化

AES的金鑰為SecretKey類型,以下是產生範例。

1
2
3
4
5
6
public SecretKey genAESKey() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException {
    // AES key
    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(256, new SecureRandom());
    return keyGen.generateKey();
}

初始向量初始化

相同的資料在相同的金鑰下會被加密成同樣結果,因此我們必須在每次加密使用不同初始化向量(被知道也無妨),來避免同樣的加密結果。相當於灑鹽的功效。
以下是初始化向量的產生方法。

1
2
3
4
5
6
public IvParameterSpec genIV() throws NoSuchPaddingException, NoSuchAlgorithmException {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    byte [] iVAES = new byte[ cipher.getBlockSize() ];
    new SecureRandom().nextBytes(iVAES);
    return new IvParameterSpec(iVAES);
}

將字串加密

金鑰初始模式為 ENCRYPT_MODE,代表接下來要執行加密。
要記得Cipher並非thread-safe(線程安全),因此每次加密最好重新產生一個Cipher。

在AES加密完之後,用Base64把結果轉成可讀的字串以便以http傳送。雖然不做Base64亦可,只是到時候要以byte為單位做傳送。

1
2
3
4
5
6
public String encrypt(String content, SecretKey secretKey, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
    byte [] temp = cipher.doFinal( content.getBytes() );
    return Base64.encodeToString(temp, Base64.DEFAULT);
}

將字串解密

金鑰初始模式為 DECRYPT_MODE,代表接下來要執行解密。

1
2
3
4
5
6
7
public String decrypt(String content, SecretKey secretKey, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    byte [] temp = Base64.decode(content, Base64.DEFAULT);
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
    byte [] decodeBytes = cipher.doFinal(temp);
    return new String(decodeBytes);
}

匯出金鑰與初始化向量

SecretKey使用getEncoded()可以獲得金鑰,為byte形式,若要方便閱讀與傳送可以轉換為Base64。
初始化向量本身有getIV()這個method可以使用,一樣為byte形式,可轉換為Base64。

1
2
3
4
5
6
7
public String secretKeyToBase64(SecretKey secretKey){
    return Base64.encodeToString(secretKey.getEncoded(), Base64.DEFAULT);
}

public String ivToBase64(IvParameterSpec iv){
    return Base64.encodeToString(iv.getIV(), Base64.DEFAULT);
}

匯入金鑰

想把之前匯出為Base64的金鑰重新匯入的話,要使用SercretKeySpec這個類別。

1
2
3
4
5
public SecretKey base64ToSecretKey(String base64Key){
    byte [] key = Base64.decode(base64Key, Base64.DEFAULT);
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
    return secretKeySpec;
}

簡單使用範例

以下為簡單的使用範例,將字串”Hello world”加密後輸出加密結果。再將加密資料解密回原始字串並輸出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 預備好金鑰和初始向量
String data = "Hello world";
SecretKey secretKey = genAESKey();
IvParameterSpec iv = genIV();

// 加密
String encryptedData = encrypt(data, secretKey, iv);
System.out.println("Encrypted Data:" + encryptedData);

// 解密
String decryptedData = decrypt(encryptedData, secretKey, iv);
System.out.println("Decrypted Data:" + decryptedData);

程式碼Github連結