DEV Community

Cover image for Spring Boot Security Boost: Encrypt Configs with Jasypt for Ironclad Protection
Aarav Joshi
Aarav Joshi

Posted on

Spring Boot Security Boost: Encrypt Configs with Jasypt for Ironclad Protection

Spring Boot's configuration management is a crucial aspect of building robust and secure applications. Let's explore how to level up our config game using Jasypt encryption.

First things first, we need to add the Jasypt dependency to our project. In our pom.xml, we'll include:

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Now, let's encrypt some sensitive properties. We'll use the Jasypt command-line tools for this. Here's how we can encrypt a database password:

java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="mySecretPassword" password=myEncryptionKey algorithm=PBEWithMD5AndDES
Enter fullscreen mode Exit fullscreen mode

This command will output an encrypted string. We can now use this in our application.yml:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: ENC(encrypted_password_here)
Enter fullscreen mode Exit fullscreen mode

To make Spring Boot understand these encrypted properties, we need to provide the encryption key. We can do this by setting the jasypt.encryptor.password property. However, hardcoding this in our application.yml would defeat the purpose of encryption. Instead, we can set it as an environment variable or pass it as a command-line argument when starting our application:

java -Djasypt.encryptor.password=myEncryptionKey -jar myapp.jar
Enter fullscreen mode Exit fullscreen mode

But what if we want more control over the encryption process? We can create a custom EncryptablePropertyResolver. Here's an example:

@Component
public class CustomEncryptablePropertyResolver implements EncryptablePropertyResolver {

    private final StringEncryptor encryptor;

    public CustomEncryptablePropertyResolver(StringEncryptor encryptor) {
        this.encryptor = encryptor;
    }

    @Override
    public String resolvePropertyValue(String value) {
        if (value != null && value.startsWith("ENC(") && value.endsWith(")")) {
            return encryptor.decrypt(value.substring(4, value.length() - 1));
        }
        return value;
    }
}
Enter fullscreen mode Exit fullscreen mode

This resolver will decrypt any property value that starts with "ENC(" and ends with ")". We can register this resolver in our configuration:

@Configuration
@EnableEncryptableProperties
public class JasyptConfig {

    @Bean
    public EncryptablePropertyResolver encryptablePropertyResolver(StringEncryptor encryptor) {
        return new CustomEncryptablePropertyResolver(encryptor);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, let's take it up a notch with asymmetric encryption. This adds an extra layer of security by using a public key for encryption and a private key for decryption. We'll need to generate a key pair first:

openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
Enter fullscreen mode Exit fullscreen mode

We can then use the public key to encrypt our properties and the private key in our application for decryption. Here's how we might set this up:

@Configuration
public class AsymmetricEncryptionConfig {

    @Bean
    public StringEncryptor asymmetricStringEncryptor() throws Exception {
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        String privateKeyPath = System.getProperty("PRIVATE_KEY_PATH");
        String privateKey = new String(Files.readAllBytes(Paths.get(privateKeyPath)));
        AsymmetricCryptography crypto = new AsymmetricCryptography();
        crypto.setPrivateKey(privateKey);
        encryptor.setAlgorithm("RSA");
        encryptor.setPrivateKey(crypto.getPrivateKey());
        return encryptor;
    }
}
Enter fullscreen mode Exit fullscreen mode

This setup allows us to encrypt properties with the public key offline and decrypt them in our application using the private key.

But what about key rotation? We don't want to use the same encryption key forever. We can implement a key rotation strategy without application downtime. Here's a basic approach:

  1. Add a new property in our configuration to specify the current key version.
  2. Implement a custom PropertySource that can handle multiple versions of encrypted properties.
  3. When rotating keys, update properties with the new key and increment the key version.

Here's a sketch of how this might look:

@Component
public class VersionedEncryptablePropertySource extends PropertySource<Map<String, Object>> {

    private final Map<Integer, StringEncryptor> encryptors;
    private final int currentVersion;

    public VersionedEncryptablePropertySource(Map<String, Object> source, 
                                              Map<Integer, StringEncryptor> encryptors, 
                                              int currentVersion) {
        super("versionedEncryptableProperties", source);
        this.encryptors = encryptors;
        this.currentVersion = currentVersion;
    }

    @Override
    public Object getProperty(String name) {
        Object value = source.get(name);
        if (value instanceof String) {
            String stringValue = (String) value;
            if (stringValue.startsWith("ENC(") && stringValue.endsWith(")")) {
                String[] parts = stringValue.substring(4, stringValue.length() - 1).split(":");
                int version = parts.length > 1 ? Integer.parseInt(parts[0]) : currentVersion;
                String encrypted = parts.length > 1 ? parts[1] : parts[0];
                return encryptors.get(version).decrypt(encrypted);
            }
        }
        return value;
    }
}
Enter fullscreen mode Exit fullscreen mode

This PropertySource can handle encrypted properties with version information. When we rotate keys, we can update our properties to include the new version number:

my.secret: ENC(2:newlyEncryptedValue)
Enter fullscreen mode Exit fullscreen mode

We can also encrypt specific parts of property values. This is useful when we only need to protect a portion of a configuration string. Here's how we might implement this:

@Component
public class PartialEncryptablePropertyResolver implements EncryptablePropertyResolver {

    private final StringEncryptor encryptor;

    public PartialEncryptablePropertyResolver(StringEncryptor encryptor) {
        this.encryptor = encryptor;
    }

    @Override
    public String resolvePropertyValue(String value) {
        if (value == null) return null;
        Pattern pattern = Pattern.compile("ENC\\(([^)]+)\\)");
        Matcher matcher = pattern.matcher(value);
        StringBuffer result = new StringBuffer();
        while (matcher.find()) {
            String encrypted = matcher.group(1);
            String decrypted = encryptor.decrypt(encrypted);
            matcher.appendReplacement(result, Matcher.quoteReplacement(decrypted));
        }
        matcher.appendTail(result);
        return result.toString();
    }
}
Enter fullscreen mode Exit fullscreen mode

This resolver will decrypt only the parts of a property value that are enclosed in "ENC()".

Handling encrypted properties in different profiles is another important consideration. We can have profile-specific encrypted properties:

spring:
  profiles:
    active: dev

---
spring:
  config:
    activate:
      on-profile: dev
database:
  password: ENC(devPasswordEncrypted)

---
spring:
  config:
    activate:
      on-profile: prod
database:
  password: ENC(prodPasswordEncrypted)
Enter fullscreen mode Exit fullscreen mode

This setup allows us to use different encrypted values for different environments.

Lastly, let's talk about integrating with external key management systems. For enterprise-grade deployments, we might want to store our encryption keys in a secure vault like HashiCorp Vault or AWS Key Management Service. Here's a basic example of how we might integrate with Vault:

@Configuration
public class VaultConfig {

    @Bean
    public StringEncryptor stringEncryptor(VaultTemplate vaultTemplate) {
        return new StringEncryptor() {
            @Override
            public String encrypt(String message) {
                VaultResponseSupport<Map<String, Object>> response = 
                    vaultTemplate.read("secret/myapp/encryption-key");
                String key = (String) response.getData().get("key");
                // Use the key to encrypt the message
                // Implementation details depend on the encryption algorithm
            }

            @Override
            public String decrypt(String encryptedMessage) {
                VaultResponseSupport<Map<String, Object>> response = 
                    vaultTemplate.read("secret/myapp/encryption-key");
                String key = (String) response.getData().get("key");
                // Use the key to decrypt the message
                // Implementation details depend on the encryption algorithm
            }
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

This setup fetches the encryption key from Vault whenever it's needed for encryption or decryption.

By implementing these advanced techniques, we can create a robust, flexible, and secure configuration management system for our Spring Boot applications. We've covered encrypting entire property values, parts of values, handling different profiles, key rotation, and integration with external key management systems. These approaches allow us to protect sensitive information while maintaining the flexibility and ease of use that Spring Boot is known for.

Remember, security is an ongoing process. Regularly review and update your encryption strategies, rotate keys, and stay informed about the latest security best practices. With these tools in your arsenal, you're well-equipped to handle configuration management in even the most demanding enterprise environments.


Our Creations

Be sure to check out our creations:

Investor Central | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Top comments (0)