資料庫密碼、第三方 API Key、系統間驗證 Token 等敏感資訊不可明文寫入配置檔。本工具類提供 encode()/decode() 靜態方法,啟動時自動解密注入屬性,配合 Spring PropertyPlaceholderConfigurer 即可無感整合。
package com.cncounter.util.comm;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.SecureRandom;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class DESUtils {
private static final String ALGO = "DES";
private static final String CHARSET = "UTF-8";
private static final String SEED = "encrypt@cncounter.com"; // 種子,建議從環境變數讀取
private static SecretKey key;
static {
try {
KeyGenerator kg = KeyGenerator.getInstance(ALGO);
kg.init(new SecureRandom(SEED.getBytes(CHARSET)));
key = kg.generateKey();
} catch (Exception e) { throw new RuntimeException("DES 初始化失敗", e); }
}
/** 明文 → Base64(DES(明文)) */
public static String encode(String plain) {
try {
byte[] data = plain.getBytes(CHARSET);
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(data);
return new BASE64Encoder().encode(encrypted);
} catch (Exception e) { throw new RuntimeException("加密失敗", e); }
}
/** Base64(DES(明文)) → 明文 */
public static String decode(String cipherText) {
try {
byte[] data = new BASE64Decoder().decodeBuffer(cipherText);
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(data);
return new String(decrypted, CHARSET);
} catch (Exception e) { throw new RuntimeException("解密失敗", e); }
}
// 單元測試
public static void main(String[] args) {
String email = "renfufei@qq.com";
String enc = DESUtils.encode(email);
String dec = DESUtils.decode(enc);
System.out.println("原文: " + email);
System.out.println("密文: " + enc);
System.out.println("解密: " + dec);
System.out.println("一致: " + email.equals(dec));
}
}
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:jdbc.properties"/>
<property name="propertiesPersister">
<bean class="com.cncounter.util.comm.EncryptedPropertyPersister"/> <!-- 自訂解密載入器 -->
</property>
</bean>
EncryptedPropertyPersister 繼承 DefaultPropertiesPersister,覆寫 load() 時對以 ENC( 開頭的值呼叫 DESUtils.decode()。
| 項目 | 現狀 | 建議升級 |
|---|---|---|
| 演算法 | DES (56-bit) | AES-256-GCM |
| 金鑰管理 | 固定種子字串 | 環境變數 / Vault / KMS 注入 |
| IV/Nonce | ECB 模式隱含固定 IV | CBC/GCM 模式 + 隨機 IV 存於密文前綴 |
| 完整性 | 無 | GCM 標籤或 HMAC-SHA256 |
public static String encryptAES(String plain, byte[] keyBytes) {
try {
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] cipherText = cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8));
// 組合:IV(12) + cipherText+tag
byte[] combined = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
return Base64.getEncoder().encodeToString(combined);
} catch (Exception e) { throw new RuntimeException(e); }
}
DES 版本適合快速原型、內部低敏感度專案;生產環境務必升級 AES-GCM並引入外部金鑰管理,避免金鑰硬編碼洩露風險。