Securing Sensitive Data: Implementing Configuration Encryption at Rest in Java


Article

In Java applications, configuration files often contain sensitive information like database passwords, API keys, and encryption keys. Storing these values in plaintext—whether in application.properties, application.yml, or environment variables—poses a significant security risk. Configuration encryption at rest ensures that sensitive data is encrypted before being stored on disk, providing an essential layer of security for your Java applications.

For Java development teams, implementing proper encryption at rest is crucial for compliance, security audits, and protecting against credential theft in the event of unauthorized access to configuration stores.

What is Configuration Encryption at Rest?

Configuration encryption at rest refers to the practice of encrypting sensitive configuration values before they are persisted to storage (files, databases, configuration servers) and decrypting them only when needed in memory during application runtime.

Key Principles:

  • Encryption at Storage: Data is encrypted when written to disk
  • Decryption in Memory: Data is only decrypted when needed by the application
  • Key Separation: Encryption keys are stored separately from encrypted data
  • Minimal Exposure: Decrypted values exist only in memory for the shortest time possible

Why Java Applications Need Configuration Encryption

  1. Compliance Requirements: Regulations like GDPR, HIPAA, PCI-DSS mandate protection of sensitive data
  2. Security Best Practices: Defense in depth against configuration repository breaches
  3. CI/CD Security: Protect secrets in version control systems and deployment pipelines
  4. Container Security: Secure configuration in Docker images and Kubernetes ConfigMaps

Encryption Approaches for Java Applications

Approach 1: Jasypt (Java Simplified Encryption)

Jasypt is the most popular library for configuration encryption in Spring Boot applications.

1. Add Dependencies:

<!-- pom.xml -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>

2. Encrypt Your Values:

@Component
public class EncryptionUtils {
public String encrypt(String value, String password) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(password);
encryptor.setAlgorithm("PBEWithMD5AndDES");
return "ENC(" + encryptor.encrypt(value) + ")";
}
public String decrypt(String encryptedValue, String password) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(password);
encryptor.setAlgorithm("PBEWithMD5AndDES");
// Remove ENC() wrapper if present
if (encryptedValue.startsWith("ENC(") && encryptedValue.endsWith(")")) {
encryptedValue = encryptedValue.substring(4, encryptedValue.length() - 1);
}
return encryptor.decrypt(encryptedValue);
}
}

3. Encrypted Configuration File:

# application.properties
# Regular properties (unencrypted)
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=app_user
# Encrypted properties
spring.datasource.password=ENC(7js9S0c7JX4Q1jLk6p9Xz2vB8mN3cD6F)
api.secret.key=ENC(aB3cDeFgH1jK2mN3oP4qRsTuV6wX8yZ9a)
encryption.algorithm=PBEWithMD5AndDES
# Jasypt configuration
jasypt.encryptor.bean=encryptorBean

4. Custom Encryptor Configuration:

@Configuration
public class EncryptionConfig {
@Value("${ENCRYPTION_PASSWORD:defaultPassword}")
private String encryptionPassword;
@Bean("encryptorBean")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(encryptionPassword);
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}

Approach 2: Spring Cloud Config Server with Encryption

For centralized configuration management with built-in encryption.

1. Config Server Setup:

# application.yml for Config Server
spring:
cloud:
config:
server:
encrypt:
enabled: true
security:
user:
password: config-server-password
encrypt:
key: my-encryption-key-1234567890123456

2. Encrypt Values via REST API:

# Encrypt a value
curl -X POST http://localhost:8888/encrypt -d "mysecretpassword"
# Returns: 7js9S0c7JX4Q1jLk6p9Xz2vB8mN3cD6F
# Decrypt a value  
curl -X POST http://localhost:8888/decrypt -d "7js9S0c7JX4Q1jLk6p9Xz2vB8mN3cD6F"

3. Encrypted Configuration in Git Repository:

# application-prod.yml in Git repo
spring:
datasource:
password: '{cipher}7js9S0c7JX4Q1jLk6p9Xz2vB8mN3cD6F'
redis:
password: '{cipher}aB3cDeFgH1jK2mN3oP4qRsTuV6wX8yZ9a'
app:
secret-key: '{cipher}xYz1aB2cD3eF4gH5iJ6kL7mN8oP9qR0sT'

Approach 3: AWS KMS with Java

For cloud-native applications using AWS Key Management Service.

1. AWS Dependencies:

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>kms</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>core</artifactId>
<version>2.20.0</version>
</dependency>

2. KMS Encryption Service:

@Service
public class AwsKmsEncryptionService {
private final KmsClient kmsClient;
private final String keyId;
public AwsKmsEncryptionService(@Value("${aws.kms.keyId}") String keyId) {
this.keyId = keyId;
this.kmsClient = KmsClient.builder()
.region(Region.US_EAST_1)
.build();
}
public String encrypt(String plaintext) {
try {
EncryptRequest encryptRequest = EncryptRequest.builder()
.keyId(keyId)
.plaintext(SdkBytes.fromUtf8String(plaintext))
.build();
EncryptResponse encryptResponse = kmsClient.encrypt(encryptRequest);
return Base64.getEncoder().encodeToString(encryptResponse.ciphertextBlob().asByteArray());
} catch (Exception e) {
throw new RuntimeException("Encryption failed", e);
}
}
public String decrypt(String encryptedBase64) {
try {
byte[] ciphertextBlob = Base64.getDecoder().decode(encryptedBase64);
DecryptRequest decryptRequest = DecryptRequest.builder()
.ciphertextBlob(SdkBytes.fromByteArray(ciphertextBlob))
.build();
DecryptResponse decryptResponse = kmsClient.decrypt(decryptRequest);
return decryptResponse.plaintext().asUtf8String();
} catch (Exception e) {
throw new RuntimeException("Decryption failed", e);
}
}
}

3. Encrypted Configuration Properties:

@Configuration
public class EncryptedConfiguration {
@Value("${encrypted.db.password}")
private String encryptedDbPassword;
private final AwsKmsEncryptionService encryptionService;
public EncryptedConfiguration(AwsKmsEncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
@Bean
public String databasePassword() {
return encryptionService.decrypt(encryptedDbPassword);
}
}

Approach 4: HashiCorp Vault Integration

For comprehensive secret management with automatic encryption.

1. Spring Vault Dependencies:

<dependency>
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
<version>3.0.0</version>
</dependency>

2. Vault Configuration:

@Configuration
public class VaultConfig extends AbstractVaultConfiguration {
@Override
public UriCustomizer uriCustomizer() {
return UriCustomizer.create();
}
@Override
public VaultEndpoint vaultEndpoint() {
return VaultEndpoint.from(URI.create("https://vault.example.com:8200"));
}
@Override
public ClientAuthentication clientAuthentication() {
return new TokenAuthentication("your-vault-token");
}
}

3. Vault Template Usage:

@Service
public class VaultSecretService {
private final VaultTemplate vaultTemplate;
public VaultSecretService(VaultTemplate vaultTemplate) {
this.vaultTemplate = vaultTemplate;
}
public String getDatabasePassword() {
VaultResponse response = vaultTemplate.read("secret/data/database");
if (response != null && response.getData() != null) {
Map<String, Object> data = (Map<String, Object>) response.getData().get("data");
return (String) data.get("password");
}
throw new IllegalStateException("Database password not found in Vault");
}
public void storeApiKey(String apiKey) {
Map<String, Object> secrets = new HashMap<>();
secrets.put("api-key", apiKey);
vaultTemplate.write("secret/data/api-keys", Collections.singletonMap("data", secrets));
}
}

Key Management Strategies

Proper key management is crucial for encryption security.

1. Environment-Based Key Injection:

@Component
public class KeyProvider {
public String getEncryptionKey() {
// Check environment variables first
String key = System.getenv("APP_ENCRYPTION_KEY");
if (key != null && !key.trim().isEmpty()) {
return key;
}
// Check system properties
key = System.getProperty("app.encryption.key");
if (key != null && !key.trim().isEmpty()) {
return key;
}
// Fallback to configuration file (less secure)
// This should only be used in development
throw new IllegalStateException("Encryption key not found in environment");
}
}

2. Kubernetes Secrets for Key Management:

# encryption-key-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-encryption-key
namespace: java-apps
type: Opaque
data:
encryption-key: base64-encoded-key-here
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
image: myapp:latest
env:
- name: APP_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: app-encryption-key
key: encryption-key

3. Key Rotation Strategy:

@Service
public class KeyRotationService {
private final Map<String, String> keyVersions = new ConcurrentHashMap<>();
private String currentKeyVersion = "v1";
public void rotateKey(String newKey, String version) {
keyVersions.put(version, newKey);
currentKeyVersion = version;
// Re-encrypt all configuration with new key
// This should be done carefully in production
}
public String decryptWithKeyVersion(String encryptedData, String keyVersion) {
String key = keyVersions.get(keyVersion);
if (key == null) {
throw new IllegalArgumentException("Unknown key version: " + keyVersion);
}
// Implement decryption with specific key version
return decrypt(encryptedData, key);
}
}

Best Practices for Java Applications

  1. Never Store Keys in Version Control: Use environment variables, Kubernetes Secrets, or dedicated key management systems
  2. Use Strong Algorithms: Prefer AES-256 over weaker algorithms like DES
  3. Implement Key Rotation: Regularly rotate encryption keys
  4. Secure Key Storage: Use HSMs or cloud KMS for production keys
  5. Audit Encryption Usage: Log encryption/decryption operations for security monitoring
  6. Use Different Keys per Environment: Development, staging, and production should use separate keys
@Component
public class EncryptionSecurityAspect {
private static final Logger logger = LoggerFactory.getLogger(EncryptionSecurityAspect.class);
@Around("execution(* com.example..*EncryptionService.*(..))")
public Object logEncryptionOperations(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
if (methodName.contains("decrypt")) {
logger.info("Decryption operation performed for: {}", 
args.length > 0 ? maskSensitive(args[0]) : "unknown");
}
return joinPoint.proceed();
}
private String maskSensitive(Object data) {
if (data instanceof String) {
String str = (String) data;
return str.length() > 8 ? 
str.substring(0, 4) + "***" + str.substring(str.length() - 4) : "***";
}
return "***";
}
}

Testing Encrypted Configuration

@SpringBootTest
@TestPropertySource(properties = {
"jasypt.encryptor.password=testPassword",
"encrypted.property=ENC(encryptedValueHere)"
})
public class EncryptedConfigTest {
@Autowired
private Environment environment;
@Test
public void testEncryptedPropertyDecryption() {
String decryptedValue = environment.getProperty("encrypted.property");
assertEquals("expectedValue", decryptedValue);
}
}

Conclusion

Implementing configuration encryption at rest is an essential security practice for Java applications handling sensitive data. By leveraging libraries like Jasypt, cloud services like AWS KMS, or dedicated secret management tools like HashiCorp Vault, Java teams can ensure that credentials and sensitive configuration remain protected even if configuration stores are compromised.

The key to successful implementation lies in proper key management, secure deployment practices, and maintaining a balance between security and operational simplicity. By adopting these patterns, Java development teams can build more secure, compliant, and enterprise-ready applications that protect sensitive data throughout the application lifecycle.

Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)

https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.

https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.

https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.

https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.

https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.

https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.

https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.

https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.

https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper