How to Encrypt and Decrypt Property Values Using the @EncryptedValue Annotation in Owner
Mark sensitive property getters with @EncryptedValue, implement the Decryptor interface, and wire it via @DecryptorClass to enable transparent runtime decryption of encrypted configuration values.
The Owner configuration library (matteobaccan/owner) protects sensitive data like passwords and API keys by letting you store encrypted values in property files and decrypt them automatically at runtime. This functionality centers on the @EncryptedValue annotation, which integrates with custom Decryptor implementations to handle cryptographic transformation transparently.
Core Components of the Encryption System
Three elements work together to enable property encryption in Owner:
-
@EncryptedValueannotation – Declared in [Config.javaat lines 102-104](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/Config.java#L102-L104), this method-level annotation marks properties requiring decryption. It accepts an optionalClass<? extends Decryptor>parameter to specify a custom decryptor for that specific method. -
Decryptorinterface – Defined in [Decryptor.java](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/Decryptor.java), this contract requires a single methodString decrypt(String value)that transforms encrypted strings into clear text. -
@DecryptorClassannotation – Located in [Config.javaat lines 108-115](https://github.com/matteobaccan/owner/blob/master/owner/src/main/java/org/aeonbits/owner/Config.java#L108-L115), this type-level annotation sets a defaultDecryptorfor all methods in the configuration interface, which individual methods can override.
Implementing a Custom Decryptor
Owner ships with IdentityDecryptor, a no-op implementation that returns values unchanged. For real encryption, extend AbstractDecryptor or implement Decryptor directly. The test suite provides a working AES example in [SampleDecryptor.java](https://github.com/matteobaccan/owner/blob/master/owner/src/test/java/org/aeonbits/owner/crypto/SampleDecryptor.java):
public class SampleDecryptor extends AbstractEncryptor {
private final StandardEncryptor encrypter;
public SampleDecryptor(String algorithm, String secretKey) {
this.encrypter = StandardEncryptor.newInstance(algorithm, secretKey);
}
@Override
public String decrypt(String value) {
return encrypter.decrypt(value);
}
@Override
public String encrypt(String value) {
return encrypter.encrypt(value);
}
}
This implementation uses StandardEncryptor for AES encryption and returns Base64-encoded strings. Instantiate it with any algorithm and secret key your security policy requires.
Wiring the Decryptor to Your Configuration
Attach a decryptor to an entire configuration interface using @DecryptorClass, then mark individual methods with @EncryptedValue:
@DecryptorClass(AesDecryptor.class)
public interface SecureConfig extends Config {
@Key("db.password")
@EncryptedValue
@DefaultValue("tzH7IKLCVc0AC72fh5DiZA==")
String dbPassword();
@Key("legacy.token")
@EncryptedValue(LegacyDecryptor.class) // overrides class-level default
@DefaultValue("k9Jf...")
String legacyToken();
}
In this example patterned after [CryptoConfigTest.java](https://github.com/matteobaccan/owner/blob/master/owner/src/test/java/org/aeonbits/owner/crypto/CryptoConfigTest.java), the dbPassword() method uses the class-level AesDecryptor, while legacyToken() specifies its own decryptor via the annotation value attribute.
How Runtime Decryption Works
When you invoke config.dbPassword(), Owner executes the following steps:
- Retrieves the raw Base64 string from the underlying
Propertiessource. - Detects the
@EncryptedValueannotation on the method. - Resolves the
Decryptorimplementation using the precedence: method-level annotation value → type-level@DecryptorClass→IdentityDecryptor. - Invokes
decrypt()on the resolved implementation. - Returns the clear-text result to your application code.
This process is completely transparent to calling code; the decryption happens automatically during property access.
Encrypting Values for Your Configuration Files
Owner does not perform encryption automatically—you must generate encrypted strings beforehand and paste them into your @DefaultValue annotations or .properties files. Use your Decryptor implementation's encrypt method in a one-off utility:
public class EncryptionUtility {
public static void main(String[] args) {
SampleDecryptor decryptor = new SampleDecryptor("AES", "ABCDEFGH12345678");
String encrypted = decryptor.encrypt("MySuperSecretPassword");
System.out.println(encrypted); // Output: tzH7IKLCVc0AC72fh5DiZA==
}
}
Run this utility locally or in your CI pipeline, then commit only the encrypted Base64 strings. The secret key remains in your decryptor implementation or environment configuration, never in the property files themselves.
Complete Implementation Example
The following interface demonstrates a fully working encrypted configuration using components from the Owner source:
package com.example.config;
import org.aeonbits.owner.Config;
import org.aeonbits.owner.ConfigFactory;
import org.aeonbits.owner.DecryptorClass;
import org.aeonbits.owner.EncryptedValue;
import org.aeonbits.owner.Key;
import org.aeonbits.owner.DefaultValue;
@DecryptorClass(MyAesDecryptor.class)
public interface DatabaseConfig extends Config {
@Key("database.password")
@EncryptedValue
@DefaultValue("tzH7IKLCVc0AC72fh5DiZA==")
String password();
@Key("api.key")
@EncryptedValue
@DefaultValue("xJ9sLm2...")
String apiKey();
}
Access decrypted values at runtime:
DatabaseConfig config = ConfigFactory.create(DatabaseConfig.class);
System.out.println(config.password()); // prints: MySuperSecretPassword
Summary
- Implement the
Decryptorinterface (or extendAbstractDecryptor) to define your cryptographic algorithm, as shown inSampleDecryptor.java. - Apply
@DecryptorClassto your configuration interface to set a default decryptor for all encrypted properties. - Mark sensitive getters with
@EncryptedValue, optionally specifying a method-specific decryptor class. - Generate encrypted values offline using your decryptor's encrypt method, then store the Base64 output in
@DefaultValueor external property files. - Owner automatically decrypts values at runtime when you access the configuration methods.
Frequently Asked Questions
What encryption algorithms does Owner support?
Owner itself is algorithm-agnostic. The encryption mechanism depends entirely on your Decryptor implementation. The test suite in CryptoConfigTest.java demonstrates AES encryption via StandardEncryptor, but you can implement DES, RSA, or custom XOR schemes (as shown in EncryptedPropertiesExample.java) by writing the corresponding decrypt() logic.
How do I handle different encryption keys for different properties?
Specify method-level decryptors using @EncryptedValue(CustomDecryptor.class). Create separate Decryptor classes for each key or algorithm, then reference them individually on the relevant getter methods. This overrides the class-level @DecryptorClass annotation for that specific property.
Is the secret key stored in the configuration files?
No. The encrypted values (Base64 ciphertext) reside in your property files or annotations, but the secret keys and algorithm specifications live in your Decryptor implementation classes or are loaded via environment variables. Keep your decryptor classes separate from your configuration interfaces to maintain security boundaries.
Can I use @EncryptedValue with external properties files instead of @DefaultValue?
Yes. The @EncryptedValue annotation works with any property source Owner supports, including .properties files, system properties, or environment variables. Store the encrypted Base64 strings in your external files, mark the interface methods with @EncryptedValue, and Owner decrypts the values when accessed regardless of the source.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →