RSAUtils 是一個提供 RSA 非對稱加密相關功能的工具類別,包含金鑰對產生、加解密、以及金鑰檔案管理等功能。此類別設計為靜態工具類別,不允許建立實例。
// 產生新的金鑰對並儲存至預設屬性檔
RSAUtils.genKeyPairPropertyFile();
// 產生新的金鑰對並儲存至指定屬性檔
RSAUtils.genKeyPairPropertyFile("custom_rsa.properties");
// 只產生金鑰對(返回 Map 格式)
Map<String, String> keyPair = RSAUtils.genNewKeyPairMap();
// 載入預設屬性檔中的金鑰對
Map<String, String> keyPair = RSAUtils.loadKeyParisString();
// 載入特定屬性檔中的金鑰對
Map<String, String> keyPair = RSAUtils.loadKeyParisString("custom_rsa.properties");
// 只載入公鑰
String publicKey = RSAUtils.loadPublicKeyString();
// 只載入私鑰
String privateKey = RSAUtils.loadPrivateKeyString();
// 使用預設公鑰加密
String encrypted = RSAUtils.encryptStringByPublicKey("明文資料");
// 使用指定公鑰加密
String encrypted = RSAUtils.encryptStringByPublicKeyString("明文資料", publicKeyString);
// 加密位元組陣列
byte[] encrypted = RSAUtils.encryptFileByPublicKeyString(byteData, publicKeyString);
// 使用預設私鑰解密
String decrypted = RSAUtils.decryptStringByPrivateKey(encryptedString);
// 使用指定私鑰解密
String decrypted = RSAUtils.decryptStringByPrivateKeyString(encryptedString, privateKeyString);
// 解密位元組陣列
byte[] decrypted = RSAUtils.decryptFileByPrivateKeyString(encryptedBytes, privateKeyString);
安全性考量
效能考量
異常處理
常見異常包括:
InvalidKeyException
NoSuchAlgorithmException
InvalidKeySpecException
NoSuchPaddingException
IllegalBlockSizeException
BadPaddingException
IOException
這些測試案例展示了以下功能的正確性:
@Test
void testGenNewKeyPairMap() {
Map<String, String> keyPair = RSAUtils.genNewKeyPairMap();
assertNotNull(keyPair);
assertTrue(keyPair.containsKey("public"));
assertTrue(keyPair.containsKey("private"));
assertFalse(keyPair.get("public").isEmpty());
assertFalse(keyPair.get("private").isEmpty());
}
@Test
void testGenKeyPairPropertyFile() throws Exception {
RSAUtils.genKeyPairPropertyFile(TEST_KEY_FILE);
File file = new File(TEST_KEY_FILE);
assertTrue(file.exists());
Map<String, String> keyMap = RSAUtils.loadKeyParisString(TEST_KEY_FILE);
assertNotNull(keyMap);
assertTrue(keyMap.containsKey("PUBLIC_KEY"));
assertTrue(keyMap.containsKey("PRIVATE_KEY"));
}
@Test
void testEncryptDecryptWithPublicPrivateKey() throws Exception {
RSAUtils.genKeyPairPropertyFile(TEST_KEY_FILE);
String original = "HelloRSA";
String encrypted = RSAUtils.encryptStringByPublicKeyFile(original, TEST_KEY_FILE);
assertNotNull(encrypted);
assertNotEquals(original, encrypted);
String decrypted = RSAUtils.decryptStringByPrivateKeyFile(encrypted, TEST_KEY_FILE);
assertEquals(original, decrypted);
}
@Test
void testEncryptDecryptWithKeyStrings() throws Exception {
Map<String, String> keyMap = RSAUtils.genNewKeyPairMap();
String publicKey = keyMap.get("public");
String privateKey = keyMap.get("private");
String original = "TestKeyString";
String encrypted2 = RSAUtils.encryptStringByPrivateKeyString(original, privateKey);
String decrypted2 = RSAUtils.decryptStringByPublicKeyString(encrypted2, publicKey);
assertEquals(original, decrypted2);
}
@Test
void testEncryptDecryptByteArray() throws Exception {
Map<String, String> keyMap = RSAUtils.genNewKeyPairMap();
String publicKey = keyMap.get("public");
String privateKey = keyMap.get("private");
byte[] data = "ByteArrayTest".getBytes();
byte[] encrypted = RSAUtils.encryptFileByPublicKeyString(data, publicKey);
byte[] decrypted = RSAUtils.decryptFileByPrivateKeyString(encrypted, privateKey);
assertArrayEquals(data, decrypted);
}
金鑰對產生測試
金鑰檔案操作測試
公鑰加密測試
私鑰加密測試
位元組操作測試
測試環境設置
異常處理驗證
效能考量
package tw.lewishome.webapp.base.utility.common;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.commons.lang3.StringUtils;
import tw.lewishome.webapp.GlobalConstants;
/**
* RSAUtils 提供 RSA 公私鑰產生、加解密、金鑰存取等相關工具方法。
*
* 支援以檔案或字串方式儲存與載入 RSA 公私鑰,並可進行分段加解密處理。
*
* <ul>
* <li>產生 RSA 金鑰對並儲存至屬性檔</li>
* <li>載入公私鑰字串</li>
* <li>以公鑰或私鑰進行加密/解密(支援字串與位元組陣列)</li>
* <li>自動處理 RSA 分段加解密</li>
* </ul>
*
* 主要用途:資料加密、數位簽章、金鑰管理等。
*
*
* 注意事項:<br>
* - RSA 加密資料長度有限,需分段處理。<br>
* - 金鑰檔案請妥善保存,避免洩漏。
*
*
* @author Lewis
* @version 1.0
*/
public class RSAUtils {
/** Private constructor to prevent instantiation */
private RSAUtils() {
throw new IllegalStateException("This is a utility class and cannot be instantiated");
}
/**
* 加解密演算法
**/
private static final String ALGORITHM = "RSA";
/**
* default Key長度
**/
private static final Integer KEY_LENGTH = 2048;
/**
* RSA最大加密Key大小
**/
private static final int MAX_ENCRYPT_BLOCK = 245;
/**
* RSA最大解密Key大小
**/
private static final int MAX_DECRYPT_BLOCK = 256;
/**
* Flag for Do Encode
**/
private static final Boolean DO_ENCODE = true;
/**
* Flag for Do Decode
*/
private static final Boolean DO_DECODE = false;
/**
* Flag for Use Public Key
*/
private static final Boolean USE_PUBLIC_KEY = true;
/**
* Flag for Use Private Key
*/
private static final Boolean USE_PRIVATE_KEY = false;
/**
* Private Key Property Name
*/
public static final String PRIVATE_KEY = "PRIVATE_KEY";
/**
* Public Key Property Name
*/
public static final String PUBLIC_KEY = "PUBLIC_KEY";
/**
* 生成keyPair ,Public key和private key 的 Property File
*
* @throws IOException IOException
*/
public static void genKeyPairPropertyFile() throws IOException {
genKeyPairPropertyFile(GlobalConstants.RSA_KEYFILE);
}
/**
* 生成keyPair ,Public key和private key 的 Property File
*
* @param propertyFileName RSAKey property file
* @throws IOException IOException
*/
public static void genKeyPairPropertyFile(String propertyFileName) throws IOException {
Map<String, String> mapKeyPair = genNewKeyPairMap();
String publicKeyString = mapKeyPair.get("public");
String privateKeyString = mapKeyPair.get("private");
PropUtils.updateProperty(propertyFileName, PUBLIC_KEY, publicKeyString);
PropUtils.updateProperty(propertyFileName, PRIVATE_KEY, privateKeyString);
}
/**
* 生成keyPair ,Public key和private key 的 Map set
*
* @return a {@link java.util.Map} object
*/
public static Map<String, String> genNewKeyPairMap() {
Map<String, String> mapKeyPair = new HashMap<>();
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_LENGTH, new SecureRandom());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// PublicKey String
PublicKey publicKey = keyPair.getPublic();
String publicKeyString = new String(Base64.getEncoder().encodeToString(publicKey.getEncoded()));
mapKeyPair.put("public", publicKeyString);
// PrivateKey String
PrivateKey privateKey = keyPair.getPrivate();
String privateKeyString = new String(Base64.getEncoder().encodeToString(privateKey.getEncoded()));
mapKeyPair.put("private", privateKeyString);
} catch (Exception ex) {
ex.printStackTrace();
}
return mapKeyPair;
}
/**
* The method load checks if the pair of public and private key has been and
* load public key Pairs Strings
*
* @return Map flag indicating if the pair of keys were generated.
* @throws NullPointerException NullPointerException
* @throws IOException IOException
*/
public static String loadPublicKeyString() throws NullPointerException, IOException {
Map<String, String> mapKeyPairString = loadKeyParisString(GlobalConstants.RSA_KEYFILE);
if (mapKeyPairString == null) {
throw new NullPointerException("No Key Pair Found");
}
String publicKeyString = mapKeyPairString.get(PUBLIC_KEY);
return publicKeyString;
}
/**
* The method load checks if the pair of public and private key has been and
* load private key Pairs Strings
*
* @return Map flag indicating if the pair of keys were generated.
* @throws NullPointerException NullPointerException
* @throws IOException IOException
*/
public static String loadPrivateKeyString() throws NullPointerException, IOException {
Map<String, String> mapKeyPairString = loadKeyParisString(GlobalConstants.RSA_KEYFILE);
if (mapKeyPairString == null) {
throw new NullPointerException("No Key Pair Found");
}
String privateKeyString = mapKeyPairString.get(PRIVATE_KEY);
return privateKeyString;
}
/**
* The method load checks if the pair of public and private key has been and
* load key Pairs Strings
*
* @return Map flag indicating if the pair of keys were generated.
* @throws IOException IOException
*/
public static Map<String, String> loadKeyParisString() throws IOException {
return loadKeyParisString(GlobalConstants.RSA_KEYFILE);
}
/**
* The method load checks if the pair of public and private key has been and
* load key Pairs Strings
*
* @param propertyFileName RSAKey property file
* @return Map flag indicating if the pair of keys were generated.
* @throws IOException IOException
*/
public static Map<String, String> loadKeyParisString(String propertyFileName) throws IOException {
File keyPropertyFile = new File(propertyFileName);
if (keyPropertyFile.exists()) {
return PropUtils.loadProps(propertyFileName);
} else {
genKeyPairPropertyFile(propertyFileName);
return PropUtils.loadProps(propertyFileName);
}
}
/**
* The method get public key from key string
*
* @Param String public Key string
* @return Key Public Key
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
*/
private static Key getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// 得到Public key
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
// .decodeBase64(publicKey.getBytes());
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(x509EncodedKeySpec);
}
/**
* The method get private key from key string
*
* @Param String privateKey String
* @return Key private Key
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
**/
private static Key getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// 得到Private key
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec pKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePrivate(pKCS8EncodedKeySpec);
}
// ========== private Key 加密 ======================
/**
* Private key加密(String) with Default RSAKey Property File 需用 Public Key解密
*
* @param dataString Date String
* @return String 加密String
* @throws java.lang.Exception java.lang.Exception
*/
public static String encryptStringByPrivateKey(String dataString) throws Exception {
return encryptStringByPrivateKeyFile(dataString, GlobalConstants.RSA_KEYFILE);
}
/**
* Private key加密(String) with RSAKey Property File 需用 Public Key解密
*
* @param dataString Data String
* @param propertyFileName RSAKey Property File
* @return String 加密String
* @throws java.lang.Exception java.lang.Exception
*/
public static String encryptStringByPrivateKeyFile(String dataString, String propertyFileName) throws Exception {
String propertyFileAbsolute = FileUtils.getFileAbsolutePathInClass(propertyFileName);
if (StringUtils.isBlank(propertyFileAbsolute)) {
propertyFileAbsolute = GlobalConstants.RSA_KEYFILE;
}
Map<String, String> keyMap = loadKeyParisString(propertyFileAbsolute);
String privateKey = keyMap.get(PRIVATE_KEY);
return encryptStringByPrivateKeyString(dataString, privateKey);
}
/**
* Private key加密(String) with Private Key String 需用 Public Key解密
*
* @param dataString Date String
* @param privateKey Private Key String
* @return String 加密String
* @throws java.lang.Exception java.lang.Exception
*/
public static String encryptStringByPrivateKeyString(String dataString, String privateKey) throws Exception {
byte[] encryptStrByte = encryptFileByPrivateKeyString(dataString.getBytes(), privateKey);
return new String(Base64.getEncoder().encodeToString(encryptStrByte));
// .encodeBase64(encryptStrByte));
}
/**
* Private key加密 (Byte[]) with Private Key String 需用 Public Key解密
*
* @param data Data Byte[]
* @param privateKey Private Key String
* @return Byte[] 加密Byte[]
* @throws java.lang.Exception java.lang.Exception
*/
public static byte[] encryptFileByPrivateKeyString(byte[] data, String privateKey) throws Exception {
return doFileRSAEncodeDeCode(data, privateKey, USE_PRIVATE_KEY, DO_ENCODE);
}
// ========== public Key 加密 ======================
/**
* Public key加密(String) with Default RSAKey Property File 需用 Private Key解密
*
* @param dataString Data String
* @return String 加密 String
* @throws java.lang.Exception java.lang.Exception
*/
public static String encryptStringByPublicKey(String dataString) throws Exception {
return encryptStringByPublicKeyFile(dataString, GlobalConstants.RSA_KEYFILE);
}
/**
* Public key加密(String) with Property File 需用 Private Key解密
*
* @param dataString Data String
* @param propertyFileName Property File
* @return String 加密 String
* @throws IOException IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*/
public static String encryptStringByPublicKeyFile(String dataString, String propertyFileName)
throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
String propertyFileAbsolute = FileUtils.getFileAbsolutePathInClass(propertyFileName);
if (StringUtils.isBlank(propertyFileAbsolute)) {
propertyFileAbsolute = GlobalConstants.RSA_KEYFILE;
}
Map<String, String> keyMap = loadKeyParisString(propertyFileAbsolute);
if (keyMap == null) {
throw new NullPointerException("No Key Pair Found");
}
String publicKey = keyMap.get(PUBLIC_KEY);
return encryptStringByPublicKeyString(dataString, publicKey);
}
/**
* Public key加密(String) with Public Key String 需用 Private Key解密
*
* @param dataString Data String
* @param publicKey Public Key String
* @return String 加密 String
* @throws IOException IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*
*/
public static String encryptStringByPublicKeyString(String dataString, String publicKey)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
IllegalBlockSizeException, BadPaddingException, IOException {
byte[] encryptStrByte = encryptFileByPublicKeyString(dataString.getBytes(), publicKey);
return new String(Base64.getEncoder().encodeToString(encryptStrByte));
// .encodeBase64(encryptStrByte));
}
/**
* Public key加密 (Byte[]) with Public Key String 需用 Private Key解密
*
* @param data[] Data byte[]
* @param publicKey Public Key String
* @return byte[] 加密 byte[]
* @throws IOException IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*/
public static byte[] encryptFileByPublicKeyString(byte[] data, String publicKey)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
IllegalBlockSizeException, BadPaddingException, IOException {
return doFileRSAEncodeDeCode(data, publicKey, USE_PUBLIC_KEY, DO_ENCODE);
}
// ========== private Key 解密 ======================
/**
* Private key解密(String) with Default RSAKey Property File 需用 public Key 加密
*
* @param dataString 加密String
* @return String 解密String
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
* @throws IOException IOException
*/
public static String decryptStringByPrivateKey(String dataString)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
IllegalBlockSizeException, BadPaddingException, IOException {
return decryptStringByPrivateKeyFile(dataString, GlobalConstants.RSA_KEYFILE);
}
/**
* Private key解密(String) with RSAKey Property File 需用 public Key 加密
*
* @param dataString 加密String
* @param propertyFileName RSAKey Property File
* @return String 解密String
* @throws IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*/
public static String decryptStringByPrivateKeyFile(String dataString, String propertyFileName)
throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
String propertyFileAbsolute = FileUtils.getFileAbsolutePathInClass(propertyFileName);
if (StringUtils.isBlank(propertyFileAbsolute)) {
propertyFileAbsolute = GlobalConstants.RSA_KEYFILE;
}
Map<String, String> keyMap = loadKeyParisString(propertyFileAbsolute);
if (keyMap == null) {
throw new NullPointerException("No Key Pair Found");
}
String privateKey = keyMap.get(PRIVATE_KEY);
return decryptStringByPrivateKeyString(dataString, privateKey);
}
/**
* private key解密 (String) with Private Key String 需用 public Key 加密
*
* @param dataString 加密String
* @param privateKey Private Key String
* @return String 解密 String
* @throws IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*/
public static String decryptStringByPrivateKeyString(String dataString, String privateKey)
throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
byte[] encryptStrByte = Base64.getDecoder().decode(dataString);
byte[] decryptStrByte = decryptFileByPrivateKeyString(
Base64.getDecoder().decode(Base64.getEncoder().encode(encryptStrByte)), privateKey);
return new String(decryptStrByte);
}
/**
* private key解密 (Byte[]) with Private Key String 需用 public Key 加密
*
* @param data 加密 byte[]
* @param privateKey private key String
* @return byte[] 解密byte[]
* @throws IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*/
public static byte[] decryptFileByPrivateKeyString(byte[] data, String privateKey)
throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
return doFileRSAEncodeDeCode(data, privateKey, USE_PRIVATE_KEY, DO_DECODE);
}
// ========== Public Key 解密 ======================
/**
* Public key解密(String) with Default RSAKey Property File 需用 public Key 加密
*
* @param dataString 加密 String
* @return String 解密 String
* @throws IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*
*/
public static String decryptStringByPublicKey(String dataString)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
IllegalBlockSizeException, BadPaddingException, IOException {
return decryptStringByPublicKeyFile(dataString, GlobalConstants.RSA_KEYFILE);
}
/**
* Private key解密(String) with RSAKey Property File 需用 public Key 加密
*
* @param dataString 加密 String
* @param propertyFileName RSAKey Property File
* @return String 解密 String
* @throws IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException NoSuchAlgorithmException
*
*/
public static String decryptStringByPublicKeyFile(String dataString, String propertyFileName)
throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
String propertyFileAbsolute = FileUtils.getFileAbsolutePathInClass(propertyFileName);
if (StringUtils.isBlank(propertyFileAbsolute)) {
propertyFileAbsolute = GlobalConstants.RSA_KEYFILE;
}
Map<String, String> keyMap = loadKeyParisString(propertyFileAbsolute);
if (keyMap == null) {
throw new NullPointerException("No Key Pair Found");
}
String publicKey = keyMap.get(PUBLIC_KEY);
return decryptStringByPublicKeyString(dataString, publicKey);
}
/**
* Public key解密 (String) with Public Key String 需用 private Key 加密
*
* @param dataString 加密 String
* @param publicKey public Key string
* @return String 解密 String
* @throws IOException IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*
*/
public static String decryptStringByPublicKeyString(String dataString, String publicKey)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
IllegalBlockSizeException, BadPaddingException, IOException {
byte[] encryptStrByte = Base64.getDecoder().decode(dataString);
// public 解密
byte[] decryptStrByte = decryptFileByPublicKeyString(
Base64.getDecoder().decode(Base64.getEncoder().encodeToString(encryptStrByte)), publicKey);
return new String(decryptStrByte);
}
/**
* Public key解密 (Byte[]) with Public Key String 需用 private Key 加密
*
* @param data 加密 byte[]
* @param publicKey Public key String
* @return byte[] 解密 byte[]
* @throws IOException IOException IOException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws InvalidKeySpecException IllegalBlockSizeException
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeyException InvalidKeyException
*
*/
public static byte[] decryptFileByPublicKeyString(byte[] data, String publicKey)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
IllegalBlockSizeException, BadPaddingException, IOException {
return doFileRSAEncodeDeCode(data, publicKey, USE_PUBLIC_KEY, DO_DECODE);
}
/**
* Public key解密 (Byte[]) with Public Key String 需用 private Key 加密
*
* @param data 加密 byte[]
* @param keyString key String
* @param usePublic Public or Private
* @param doEncode Encode or Decode
* @return byte[] 解密 byte[]
* @throws NoSuchPaddingException NoSuchPaddingException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws InvalidKeyException InvalidKeyException
* @throws BadPaddingException BadPaddingException
* @throws IllegalBlockSizeException IllegalBlockSizeException
* @throws IOException IOException IOException
*/
public static byte[] doFileRSAEncodeDeCode(byte[] data, String keyString, Boolean usePublic, Boolean doEncode)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException, IOException {
// 得到Public key
Key key = null;
if (Boolean.TRUE.equals(usePublic)) {
key = getPublicKey(keyString);
} else {
key = getPrivateKey(keyString);
}
// 解密數據,分段解密
Cipher cipher = Cipher.getInstance(ALGORITHM);
int maxBLOCK = 0;
if (Boolean.TRUE.equals(doEncode)) {
cipher.init(Cipher.ENCRYPT_MODE, key);
maxBLOCK = MAX_ENCRYPT_BLOCK;
} else {
cipher.init(Cipher.DECRYPT_MODE, key);
maxBLOCK = MAX_DECRYPT_BLOCK;
}
int inputLength = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
while (inputLength - offset > 0) {
if (inputLength - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(data, offset, maxBLOCK);
} else {
cache = cipher.doFinal(data, offset, inputLength - offset);
}
out.write(cache);
i++;
offset = i * maxBLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
}
package tw.lewishome.webapp.base.utility.common;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import java.io.File;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
/**
* RSAUtils 的單元測試類別,涵蓋金鑰產生、加解密、金鑰檔案操作等功能的測試。
*
* 測試內容包含:
* <ul>
* <li>產生新的 RSA 金鑰對(Map 形式)</li>
* <li>產生 RSA 金鑰對並儲存至屬性檔案</li>
* <li>載入金鑰檔案,若不存在則自動建立</li>
* <li>使用公開金鑰加密、私密金鑰解密字串</li>
* <li>使用私密金鑰加密、公開金鑰解密字串</li>
* <li>直接以金鑰字串進行加解密</li>
* <li>以 byte array 進行加解密</li>
* <li>載入公開金鑰與私密金鑰字串</li>
* </ul>
* 每個測試皆會於執行前後自動清理測試用金鑰檔案,確保測試環境乾淨。
*
* @author Lewis
* @version 1.0
*/
class RSAUtilsTest {
private static final String TEST_KEY_FILE = "TestRSAKey.properties";
/**
* @throws Exception
*/
@BeforeEach
void setUp() {
// Clean up before each test
File file = new File(TEST_KEY_FILE);
if (file.exists()) {
file.delete();
}
}
@AfterEach
void tearDown() {
// Clean up after each test
File file = new File(TEST_KEY_FILE);
if (file.exists()) {
file.delete();
}
}
@Test
void testGenNewKeyPairMap() {
Map<String, String> keyPair = RSAUtils.genNewKeyPairMap();
assertNotNull(keyPair);
assertTrue(keyPair.containsKey("public"));
assertTrue(keyPair.containsKey("private"));
assertFalse(keyPair.get("public").isEmpty());
assertFalse(keyPair.get("private").isEmpty());
}
/**
* @throws Exception
*/
@Test
void testGenKeyPairPropertyFile() throws Exception {
RSAUtils.genKeyPairPropertyFile(TEST_KEY_FILE);
File file = new File(TEST_KEY_FILE);
assertTrue(file.exists());
Map<String, String> keyMap = RSAUtils.loadKeyParisString(TEST_KEY_FILE);
assertNotNull(keyMap);
assertTrue(keyMap.containsKey("PUBLIC_KEY"));
assertTrue(keyMap.containsKey("PRIVATE_KEY"));
}
@Test
void testLoadKeyParisStringCreatesFileIfNotExist() throws IOException {
File file = new File(TEST_KEY_FILE);
assertFalse(file.exists());
Map<String, String> keyMap = RSAUtils.loadKeyParisString(TEST_KEY_FILE);
assertNotNull(keyMap);
assertTrue(file.exists());
assertTrue(keyMap.containsKey("PUBLIC_KEY"));
assertTrue(keyMap.containsKey("PRIVATE_KEY"));
}
/**
* @throws Exception
*/
@Test
void testEncryptDecryptWithPublicPrivateKey() throws Exception {
RSAUtils.genKeyPairPropertyFile(TEST_KEY_FILE);
String original = "HelloRSA";
String encrypted = RSAUtils.encryptStringByPublicKeyFile(original, TEST_KEY_FILE);
assertNotNull(encrypted);
assertNotEquals(original, encrypted);
String decrypted = RSAUtils.decryptStringByPrivateKeyFile(encrypted, TEST_KEY_FILE);
assertEquals(original, decrypted);
}
/**
* @throws Exception
*/
@Test
void testEncryptDecryptWithPrivatePublicKey() throws Exception {
RSAUtils.genKeyPairPropertyFile(TEST_KEY_FILE);
String original = "HelloPrivate";
String encrypted = RSAUtils.encryptStringByPrivateKeyFile(original, TEST_KEY_FILE);
assertNotNull(encrypted);
assertNotEquals(original, encrypted);
String decrypted = RSAUtils.decryptStringByPublicKeyFile(encrypted, TEST_KEY_FILE);
assertEquals(original, decrypted);
}
/**
* @throws Exception
*/
@Test
void testEncryptDecryptWithKeyStrings() throws Exception {
Map<String, String> keyMap = RSAUtils.genNewKeyPairMap();
String publicKey = keyMap.get("public");
String privateKey = keyMap.get("private");
String original = "TestKeyString";
String encrypted = RSAUtils.encryptStringByPublicKeyString(original, publicKey);
String decrypted = RSAUtils.decryptStringByPrivateKeyString(encrypted, privateKey);
assertEquals(original, decrypted);
String encrypted2 = RSAUtils.encryptStringByPrivateKeyString(original, privateKey);
String decrypted2 = RSAUtils.decryptStringByPublicKeyString(encrypted2, publicKey);
assertEquals(original, decrypted2);
}
/**
* @throws Exception
*/
@Test
void testEncryptDecryptByteArray() throws Exception {
Map<String, String> keyMap = RSAUtils.genNewKeyPairMap();
String publicKey = keyMap.get("public");
String privateKey = keyMap.get("private");
byte[] data = "ByteArrayTest".getBytes();
byte[] encrypted = RSAUtils.encryptFileByPublicKeyString(data, publicKey);
byte[] decrypted = RSAUtils.decryptFileByPrivateKeyString(encrypted, privateKey);
assertArrayEquals(data, decrypted);
byte[] encrypted2 = RSAUtils.encryptFileByPrivateKeyString(data, privateKey);
byte[] decrypted2 = RSAUtils.decryptFileByPublicKeyString(encrypted2, publicKey);
assertArrayEquals(data, decrypted2);
}
/**
* @throws Exception
*/
@Test
void testLoadPublicPrivateKeyString() throws Exception {
RSAUtils.genKeyPairPropertyFile(TEST_KEY_FILE);
// Temporarily override BaseSystemConstants.RsaKeyFile if possible
// Otherwise, just check that the methods return non-null
String publicKey = RSAUtils.loadKeyParisString(TEST_KEY_FILE).get("PUBLIC_KEY");
String privateKey = RSAUtils.loadKeyParisString(TEST_KEY_FILE).get("PRIVATE_KEY");
assertNotNull(publicKey);
assertNotNull(privateKey);
}
}