iT邦幫忙

0

應用系統建置前準備工具 - RSAUtils 非對稱加密工具

  • 分享至 

  • xImage
  •  

RSAUtils 非對稱加密工具類別

概述

RSAUtils 是一個提供 RSA 非對稱加密相關功能的工具類別,包含金鑰對產生、加解密、以及金鑰檔案管理等功能。此類別設計為靜態工具類別,不允許建立實例。

專案相關程式

  • PropUtils
  • FileUtils

第三方元件(Dependency)

  • org.apache.commons.lang3.StringUtils;

主要功能

1. 金鑰管理

  • 生成 RSA 金鑰對(2048 位元)
  • 將金鑰對儲存至屬性檔
  • 從屬性檔載入公私鑰
  • 支援金鑰字串與檔案格式轉換

2. 加密功能

  • 使用公鑰加密(用於接收方解密)
  • 使用私鑰加密(用於數位簽章)
  • 支援字串及位元組陣列的加密
  • 自動處理大型資料的分段加密

3. 解密功能

  • 使用私鑰解密(對應公鑰加密)
  • 使用公鑰解密(對應私鑰加密,用於驗證簽章)
  • 支援字串及位元組陣列的解密
  • 自動處理分段解密

技術規格

  • 加密演算法:RSA
  • 金鑰長度:2048 位元
  • 最大加密區塊:245 位元組
  • 最大解密區塊:256 位元組
  • 編碼方式:Base64

使用方法

1. 金鑰對產生與儲存

// 產生新的金鑰對並儲存至預設屬性檔
RSAUtils.genKeyPairPropertyFile();

// 產生新的金鑰對並儲存至指定屬性檔
RSAUtils.genKeyPairPropertyFile("custom_rsa.properties");

// 只產生金鑰對(返回 Map 格式)
Map<String, String> keyPair = RSAUtils.genNewKeyPairMap();

2. 載入金鑰

// 載入預設屬性檔中的金鑰對
Map<String, String> keyPair = RSAUtils.loadKeyParisString();

// 載入特定屬性檔中的金鑰對
Map<String, String> keyPair = RSAUtils.loadKeyParisString("custom_rsa.properties");

// 只載入公鑰
String publicKey = RSAUtils.loadPublicKeyString();

// 只載入私鑰
String privateKey = RSAUtils.loadPrivateKeyString();

3. 使用公鑰加密

// 使用預設公鑰加密
String encrypted = RSAUtils.encryptStringByPublicKey("明文資料");

// 使用指定公鑰加密
String encrypted = RSAUtils.encryptStringByPublicKeyString("明文資料", publicKeyString);

// 加密位元組陣列
byte[] encrypted = RSAUtils.encryptFileByPublicKeyString(byteData, publicKeyString);

4. 使用私鑰解密

// 使用預設私鑰解密
String decrypted = RSAUtils.decryptStringByPrivateKey(encryptedString);

// 使用指定私鑰解密
String decrypted = RSAUtils.decryptStringByPrivateKeyString(encryptedString, privateKeyString);

// 解密位元組陣列
byte[] decrypted = RSAUtils.decryptFileByPrivateKeyString(encryptedBytes, privateKeyString);

重要注意事項

  1. 安全性考量

    • 私鑰必須安全保管,避免外洩
    • 建議定期更換金鑰對
    • 加密資料量有限制,需注意分段處理
  2. 效能考量

    • RSA 運算較耗資源,不適合加密大量資料
    • 建議搭配對稱加密(如 AES)使用
    • 注意分段加解密的區塊大小限制
  3. 異常處理
    常見異常包括:

    • InvalidKeyException
    • NoSuchAlgorithmException
    • InvalidKeySpecException
    • NoSuchPaddingException
    • IllegalBlockSizeException
    • BadPaddingException
    • IOException

單元測試範例

這些測試案例展示了以下功能的正確性:

  • RSA 金鑰對產生與管理
  • 檔案與記憶體中的金鑰處理
  • 公私鑰加解密功能
  • 字串與位元組陣列操作
  • 異常處理與邊界情況

1. 金鑰對產生測試

@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());
}

2. 金鑰檔案操作測試

@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"));
}

3. 公鑰加密私鑰解密測試

@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);
}

4. 私鑰加密公鑰解密測試

@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);
}

5. 位元組陣列加解密測試

@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);
}

測試案例說明

  1. 金鑰對產生測試

    • 驗證金鑰對產生功能
    • 確保公私鑰都被正確產生
    • 驗證金鑰內容不為空
  2. 金鑰檔案操作測試

    • 測試金鑰檔案的建立
    • 驗證檔案存取權限
    • 確認金鑰的正確載入
  3. 公鑰加密測試

    • 驗證公鑰加密功能
    • 確認加密後密文與原文不同
    • 測試私鑰解密還原
  4. 私鑰加密測試

    • 測試私鑰加密流程
    • 驗證公鑰解密功能
    • 確保資料完整性
  5. 位元組操作測試

    • 測試二進位資料處理
    • 驗證大型資料的分段處理
    • 確認資料完整還原

其他注意事項

  1. 測試環境設置

    • 每個測試前後自動清理測試檔案
    • 使用臨時檔案避免影響實際系統
    • 確保測試案例互不干擾
  2. 異常處理驗證

    • 檢查檔案不存在的情況
    • 驗證金鑰格式錯誤的處理
    • 確保異常訊息明確有用
  3. 效能考量

    • 注意大型資料的處理方式
    • 驗證分段加解密的正確性
    • 確保記憶體使用合理

程式碼 RSAUtils.java

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;
    }

}

單元測試程式碼 RSAUtilsTest.java

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);
    }
}


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言