No Third‑Party Dependencies: Build a Custom Spring Boot Dynamic Decryption Component
The article explains how to protect database passwords and API keys in Spring Boot 3.5.0 by implementing a lightweight, third‑party‑free dynamic decryption component that encrypts values with AES, stores them with a {cipher} prefix, and decrypts them at pre‑refresh using a custom ApplicationContextInitializer.
1. Introduction
In micro‑service architectures, storing database passwords, API keys and other sensitive configuration in plain text poses serious security and compliance risks. A common approach is to store them encrypted and decrypt at runtime. Although third‑party libraries such as Jasypt exist, the article demonstrates how to implement the same functionality using only Spring Boot core components, avoiding external supply‑chain dependencies.
2. Practical implementation
2.1 Encryption utility
public class SecretComonent {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
public static String encrypt(String content, String key) {
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("AES 加密失败", e);
}
}
public static String decrypt(String encryptedStr, String key) {
try {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.getDecoder().decode(encryptedStr);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES 解密失败", e);
}
}
}2.2 Container context initializer
public class DecryptApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger logger = LoggerFactory.getLogger(DecryptApplicationContextInitializer.class);
public static final String DECRYPTED_PROPERTY_SOURCE_NAME = "decrypted";
public static final String ENCRYPTED_PROPERTY_PREFIX = "{cipher}";
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
String key = environment.getProperty("pack.encrypt.key");
Map<String, Object> map = decrypt(key, propertySources);
if (!map.isEmpty()) {
propertySources.addFirst(new SystemEnvironmentPropertySource(DECRYPTED_PROPERTY_SOURCE_NAME, map));
}
}
protected Map<String, Object> decrypt(String key, PropertySources propertySources) {
Map<String, Object> properties = merge(propertySources);
decrypt(properties, key);
return properties;
}
protected Map<String, Object> merge(PropertySources propertySources) {
Map<String, Object> properties = new LinkedHashMap<>();
List<PropertySource<?>> sources = new ArrayList<>();
for (PropertySource<?> source : propertySources) {
sources.add(0, source);
}
for (PropertySource<?> source : sources) {
merge(source, properties);
}
return properties;
}
protected void merge(PropertySource<?> source, Map<String, Object> properties) {
if (source instanceof CompositePropertySource) {
List<PropertySource<?>> sources = new ArrayList<>(((CompositePropertySource) source).getPropertySources());
Collections.reverse(sources);
for (PropertySource<?> nested : sources) {
merge(nested, properties);
}
} else if (source instanceof EnumerablePropertySource<?> enumerable) {
for (String key : enumerable.getPropertyNames()) {
Object property = source.getProperty(key);
if (property != null) {
String value = property.toString();
if (value.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
properties.put(key, value);
}
}
}
}
}
protected void decrypt(Map<String, Object> properties, String secretKey) {
properties.replaceAll((k, v) -> {
String valueString = v.toString();
if (!valueString.startsWith(ENCRYPTED_PROPERTY_PREFIX)) {
return v;
}
return decrypt(k, valueString, secretKey);
});
}
protected String decrypt(String key, String original, String secretKey) {
String value = original.substring(ENCRYPTED_PROPERTY_PREFIX.length());
try {
value = SecretComonent.decrypt(value, secretKey);
return value;
} catch (Exception e) {
logger.warn("Cannot decrypt: key=", key, e);
return "";
}
}
}2.3 Configuration and testing
Add the initializer to META-INF/spring.factories:
org.springframework.context.ApplicationContextInitializer=\
com.pack.encrypt.initializer.DecryptApplicationContextInitializerEncrypt the password in application.yml using the {cipher} prefix:
common-datasource: &common-ds
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: '{cipher}BlgIeTDSKwIfwjdXk5TzKQ=='
type: com.zaxxer.hikari.HikariDataSource
spring:
datasource:
url: jdbc:mysql://localhost:3306/testjpa?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=UTF-8&rewriteBatchedStatements=true&cachePrepStmts=true
<<: *common-dsStart the application with the encryption key passed as a JVM argument: --pack.encrypt.key=aaaabbbbccccdddd If the initializer is correctly registered, the application starts successfully (see screenshot). Without it, startup fails with an error indicating that the encrypted property cannot be resolved (see screenshot).
By leveraging Spring Boot’s pre‑refresh hook and the standard Java cryptography API, the solution provides a lightweight, fully self‑contained way to keep sensitive configuration encrypted at rest while still being usable at runtime.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
