2020年11月18日 星期三

AES 加密 - JAVA 程式碼範例

紀錄一下 Java 使用 AES (Advanced Encryption Standard)  加解密的範例程式碼:

import java.io.UnsupportedEncodingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

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 org.apache.commons.codec.binary.Base64;

public class AESTest {

	public static void main(String[] args) {
		try {
			String seedForRandom = "TestSeed";
			String plaintext = "TestPlaintext";
			
			KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
			// 設定特定的隨機數種子,
			// 如隨機數種子不變,就會得到相同的隨機數,
			// 這裡相當於每次都使用同樣的金鑰
			SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
			secureRandom.setSeed(seedForRandom.getBytes("UTF-8"));
			//產生 AES 金鑰
			keyGenerator.init(256, secureRandom);
			SecretKey secretKey = keyGenerator.generateKey();
			SecretKey AES_secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
			// 取得 AES 加解密器
			Cipher cipher = Cipher.getInstance("AES");
			
			//進行加密
			cipher.init(Cipher.ENCRYPT_MODE, AES_secretKey);
			byte[] encryptedText_byteArray = cipher.doFinal(plaintext.getBytes("UTF-8"));
			String encryptedText = new String(encryptedText_byteArray, "UTF-8");
			System.out.println(encryptedText); // W?\?? ???	U]?
			
			//對加密後的 byte[] 進行解密 (再取得一個新的 AES 加解密器來做)
			cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, AES_secretKey);
			byte[] decryptedText_byteArray = cipher.doFinal(encryptedText_byteArray);
			String decryptedText = new String(decryptedText_byteArray, "UTF-8");
			System.out.println(decryptedText); // TestPlaintext
			
			//注意!
			//加密後的 byte[] (decryptedText_byteArray) 不能與 String 互轉,
			//會流失一些東西,即上述的 encryptedText 會有轉不回 decryptedText_byteArray 的情況,
			//如果直接拿 encryptedText 來解密會出錯,例如以下例子會產生如下錯誤: 
			//javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
			//cipher = Cipher.getInstance("AES");
			//cipher.init(Cipher.DECRYPT_MODE, AES_secretKey);
			//byte[] decryptedText_byteArray2 = cipher.doFinal(encryptedText.getBytes("UTF-8"));
			// ==> Execption : javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
			
			//如上所述,替代方案可使用 base64 編碼,
			//因為加密後的 byte[] (即 decryptedText_byteArray) 可用 base64 編碼來在 byte[] 和 String 之間互轉,
			//如下所示:
			String base64Encoded_encryptedTextcryptedText = Base64.encodeBase64String(encryptedText_byteArray);
			System.out.println(base64Encoded_encryptedTextcryptedText); // V79c5vQIIP/R2wcWCVVdqA==
			
			cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, AES_secretKey);
			byte[] decryptedText_byteArray2 = cipher.doFinal(Base64.decodeBase64(base64Encoded_encryptedTextcryptedText));
			String decryptedText2 = new String(decryptedText_byteArray2, "UTF-8");
			System.out.println(decryptedText2); // TestPlaintext
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}


參考:

  1. 進階加密標準(Advanced Encryption Standard,AES)
  2. JAVA AES加密與解密
  3. Input length must be multiple of 16 when decrypting with padded cipher