diff --git a/devtools/cli/pom.xml b/devtools/cli/pom.xml
index 9ba49274bea8b4..a9f1575a938679 100644
--- a/devtools/cli/pom.xml
+++ b/devtools/cli/pom.xml
@@ -73,6 +73,11 @@
assertj-core
test
+
+ io.smallrye.config
+ smallrye-config-crypto
+ test
+
io.quarkus
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/Config.java b/devtools/cli/src/main/java/io/quarkus/cli/Config.java
new file mode 100644
index 00000000000000..bb1891a961e6ba
--- /dev/null
+++ b/devtools/cli/src/main/java/io/quarkus/cli/Config.java
@@ -0,0 +1,29 @@
+package io.quarkus.cli;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import io.quarkus.cli.common.OutputOptionMixin;
+import io.quarkus.cli.config.Encryptor;
+import io.quarkus.cli.config.SetConfig;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "config", header = "Manage Quarkus configuration", subcommands = { SetConfig.class, Encryptor.class })
+public class Config implements Callable {
+ @CommandLine.Mixin(name = "output")
+ protected OutputOptionMixin output;
+
+ @CommandLine.Spec
+ protected CommandLine.Model.CommandSpec spec;
+
+ @CommandLine.Unmatched // avoids throwing errors for unmatched arguments
+ List unmatchedArgs;
+
+ @Override
+ public Integer call() throws Exception {
+ CommandLine.ParseResult result = spec.commandLine().getParseResult();
+ CommandLine appCommand = spec.subcommands().get("set");
+ return appCommand.execute(result.originalArgs().stream().filter(x -> !"config".equals(x)).toArray(String[]::new));
+ }
+}
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java
index 3db8a4d8c3af2c..c8e94ce0f656b6 100644
--- a/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java
+++ b/devtools/cli/src/main/java/io/quarkus/cli/QuarkusCli.java
@@ -45,7 +45,15 @@
import picocli.CommandLine.UnmatchedArgumentException;
@CommandLine.Command(name = "quarkus", subcommands = {
- Create.class, Build.class, Dev.class, Run.class, Test.class, ProjectExtensions.class, Image.class, Deploy.class,
+ Create.class,
+ Build.class,
+ Dev.class,
+ Run.class,
+ Test.class,
+ Config.class,
+ ProjectExtensions.class,
+ Image.class,
+ Deploy.class,
Registry.class,
Info.class,
Update.class,
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/config/BaseConfigCommand.java b/devtools/cli/src/main/java/io/quarkus/cli/config/BaseConfigCommand.java
new file mode 100644
index 00000000000000..2cd4b16318d49b
--- /dev/null
+++ b/devtools/cli/src/main/java/io/quarkus/cli/config/BaseConfigCommand.java
@@ -0,0 +1,32 @@
+package io.quarkus.cli.config;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Base64;
+
+import io.quarkus.cli.common.OutputOptionMixin;
+import picocli.CommandLine;
+
+public class BaseConfigCommand {
+ @CommandLine.Mixin(name = "output")
+ protected OutputOptionMixin output;
+
+ @CommandLine.Spec
+ protected CommandLine.Model.CommandSpec spec;
+
+ Path projectRoot;
+
+ protected Path projectRoot() {
+ if (projectRoot == null) {
+ projectRoot = output.getTestDirectory();
+ if (projectRoot == null) {
+ projectRoot = Paths.get(System.getProperty("user.dir")).toAbsolutePath();
+ }
+ }
+ return projectRoot;
+ }
+
+ protected String encodeToString(byte[] data) {
+ return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
+ }
+}
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/config/Encryptor.java b/devtools/cli/src/main/java/io/quarkus/cli/config/Encryptor.java
new file mode 100644
index 00000000000000..cc9f88077a950f
--- /dev/null
+++ b/devtools/cli/src/main/java/io/quarkus/cli/config/Encryptor.java
@@ -0,0 +1,94 @@
+package io.quarkus.cli.config;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.concurrent.Callable;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+@Command(name = "encryptor", aliases = "enc", header = "Encrypt Secrets using AES/GCM/NoPadding algorithm by default")
+public class Encryptor extends BaseConfigCommand implements Callable {
+ @Option(required = true, names = { "-s", "--secret" }, description = "Secret")
+ String secret;
+
+ @Option(names = { "-k", "--key" }, description = "Encryption Key")
+ String encryptionKey;
+
+ @Option(names = { "-b" }, description = "Encryption Key in Base64 format", defaultValue = "false")
+ private boolean base64EncryptionKey;
+
+ @Option(hidden = true, names = { "-a", "--algorithm" }, description = "Algorithm", defaultValue = "AES")
+ String algorithm;
+
+ @Option(hidden = true, names = { "-m", "--mode" }, description = "Mode", defaultValue = "GCM")
+ String mode;
+
+ @Option(hidden = true, names = { "-p", "--padding" }, description = "Algorithm", defaultValue = "NoPadding")
+ String padding;
+
+ @Option(hidden = true, names = { "-q", "--quiet" }, defaultValue = "false")
+ boolean quiet;
+
+ private String encryptedSecret;
+
+ @Override
+ public Integer call() throws Exception {
+ if (encryptionKey == null) {
+ encryptionKey = encodeToString(generateEncryptionKey().getEncoded());
+ } else {
+ if (!base64EncryptionKey) {
+ encryptionKey = encodeToString(encryptionKey.getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
+ Cipher cipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding);
+ byte[] iv = new byte[12];
+ new SecureRandom().nextBytes(iv);
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ sha256.update(encryptionKey.getBytes(StandardCharsets.UTF_8));
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sha256.digest(), "AES"), new GCMParameterSpec(128, iv));
+
+ byte[] encrypted = cipher.doFinal(secret.getBytes(StandardCharsets.UTF_8));
+
+ ByteBuffer message = ByteBuffer.allocate(1 + iv.length + encrypted.length);
+ message.put((byte) iv.length);
+ message.put(iv);
+ message.put(encrypted);
+
+ this.encryptedSecret = Base64.getUrlEncoder().withoutPadding().encodeToString((message.array()));
+ if (!quiet) {
+ System.out.println("Encrypted Secret: " + encryptedSecret);
+ System.out.println("Encryption Key: " + encryptionKey);
+ }
+
+ return 0;
+ }
+
+ private SecretKey generateEncryptionKey() {
+ try {
+ return KeyGenerator.getInstance(algorithm).generateKey();
+ } catch (Exception e) {
+ System.err.println("Error while generating the encryption key: " + e);
+ System.exit(-1);
+ }
+ return null;
+ }
+
+ public String getEncryptedSecret() {
+ return encryptedSecret;
+ }
+
+ public String getEncryptionKey() {
+ return encryptionKey;
+ }
+}
diff --git a/devtools/cli/src/main/java/io/quarkus/cli/config/SetConfig.java b/devtools/cli/src/main/java/io/quarkus/cli/config/SetConfig.java
new file mode 100644
index 00000000000000..f3164bfe4b6613
--- /dev/null
+++ b/devtools/cli/src/main/java/io/quarkus/cli/config/SetConfig.java
@@ -0,0 +1,103 @@
+package io.quarkus.cli.config;
+
+import java.io.BufferedWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import io.smallrye.config.ConfigValue;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "set")
+public class SetConfig extends BaseConfigCommand implements Callable {
+ @CommandLine.Option(required = true, names = { "-n", "--name" }, description = "Configuration name")
+ String name;
+ @CommandLine.Option(names = { "-a", "--value" }, description = "Configuration value")
+ String value;
+ @CommandLine.Option(names = { "-k", "--encrypt" }, description = "Encrypt value")
+ boolean encrypt;
+
+ @Override
+ public Integer call() throws Exception {
+ Path properties = projectRoot().resolve("src/main/resources/application.properties");
+ if (!properties.toFile().exists()) {
+ System.out.println("Could not find an application.properties file");
+ return 0;
+ }
+
+ List lines = Files.readAllLines(properties);
+
+ if (encrypt) {
+ Encryptor encryptor = new Encryptor();
+ List args = new ArrayList<>();
+ args.add("-q");
+ if (value == null) {
+ value = findKey(lines, name).getValue();
+ }
+ args.add("--secret=" + value);
+ if (value == null || value.length() == 0) {
+ System.out.println("Cannot encrypt an empty value");
+ return -1;
+ }
+
+ ConfigValue encryptionKey = findKey(lines, "smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key");
+ if (encryptionKey.getValue() != null) {
+ args.add("-b");
+ args.add("--key=" + encryptionKey.getValue());
+ }
+
+ int execute = new CommandLine(encryptor).execute(args.toArray(new String[] {}));
+ if (execute < 0) {
+ System.exit(execute);
+ }
+ value = "${aes-gcm-nopadding::" + encryptor.getEncryptedSecret() + "}";
+ if (encryptionKey.getValue() == null) {
+ lines.add(encryptionKey.getName() + "=" + encryptor.getEncryptionKey());
+ }
+ }
+
+ int nameLineNumber = -1;
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ if (line.startsWith(name + "=")) {
+ nameLineNumber = i;
+ break;
+ }
+ }
+
+ if (nameLineNumber != -1) {
+ if (value != null) {
+ System.out.println("Setting " + name + " to " + value);
+ lines.set(nameLineNumber, name + "=" + value);
+ } else {
+ System.out.println("Removing " + name);
+ lines.remove(nameLineNumber);
+ }
+ } else {
+ System.out.println("Adding " + name + " with " + value);
+ lines.add(name + "=" + (value != null ? value : ""));
+ }
+
+ try (BufferedWriter writer = Files.newBufferedWriter(properties)) {
+ for (String i : lines) {
+ writer.write(i);
+ writer.newLine();
+ }
+ }
+
+ return 0;
+ }
+
+ public static ConfigValue findKey(List lines, String name) {
+ ConfigValue configValue = ConfigValue.builder().withName(name).build();
+ for (int i = 0; i < lines.size(); i++) {
+ final String line = lines.get(i);
+ if (line.startsWith(configValue.getName() + "=")) {
+ return configValue.withValue(line.substring(name.length() + 1)).withLineNumber(i);
+ }
+ }
+ return configValue;
+ }
+}
diff --git a/devtools/cli/src/test/java/io/quarkus/cli/config/EncryptorTest.java b/devtools/cli/src/test/java/io/quarkus/cli/config/EncryptorTest.java
new file mode 100644
index 00000000000000..f9382226a228e1
--- /dev/null
+++ b/devtools/cli/src/test/java/io/quarkus/cli/config/EncryptorTest.java
@@ -0,0 +1,25 @@
+package io.quarkus.cli.config;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.nio.file.Paths;
+import java.util.Scanner;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.cli.CliDriver;
+import io.smallrye.config.crypto.AESGCMNoPaddingSecretKeysHandler;
+
+class EncryptorTest {
+ @Test
+ void encrypt() throws Exception {
+ CliDriver.Result result = CliDriver.execute(Paths.get(System.getProperty("user.dir")), "config", "encryptor",
+ "--secret=12345678");
+ Scanner scanner = new Scanner(result.getStdout());
+ String secret = scanner.nextLine().split(": ")[1];
+ String encryptionKey = scanner.nextLine().split(": ")[1];
+
+ AESGCMNoPaddingSecretKeysHandler handler = new AESGCMNoPaddingSecretKeysHandler(encryptionKey);
+ assertEquals("12345678", handler.decode(secret));
+ }
+}
diff --git a/devtools/cli/src/test/java/io/quarkus/cli/config/SetConfigTest.java b/devtools/cli/src/test/java/io/quarkus/cli/config/SetConfigTest.java
new file mode 100644
index 00000000000000..151db16bd352cd
--- /dev/null
+++ b/devtools/cli/src/test/java/io/quarkus/cli/config/SetConfigTest.java
@@ -0,0 +1,124 @@
+package io.quarkus.cli.config;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.FileOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import io.quarkus.cli.CliDriver;
+import io.smallrye.config.PropertiesConfigSource;
+import io.smallrye.config.SmallRyeConfig;
+import io.smallrye.config.SmallRyeConfigBuilder;
+
+class SetConfigTest {
+ @TempDir
+ Path tempDir;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ Path resources = tempDir.resolve("src/main/resources");
+ Files.createDirectories(resources);
+ Files.createFile(resources.resolve("application.properties"));
+ }
+
+ @Test
+ void createConfiguration() throws Exception {
+ CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "--value=1234");
+ assertEquals(0, result.getExitCode());
+ assertTrue(result.getStdout().contains("Adding foo.bar with 1234"));
+ assertEquals("1234", config().getRawValue("foo.bar"));
+ }
+
+ @Test
+ void updateConfiguration() throws Exception {
+ Path propertiesFile = tempDir.resolve("src/main/resources/application.properties");
+ Properties properties = new Properties();
+ properties.load(propertiesFile.toUri().toURL().openStream());
+ properties.put("foo.bar", "1234");
+ properties.store(new FileOutputStream(propertiesFile.toFile()), "");
+
+ CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "--value=5678");
+ assertEquals(0, result.getExitCode());
+ assertTrue(result.getStdout().contains("Setting foo.bar to 5678"));
+ assertEquals("5678", config().getRawValue("foo.bar"));
+ }
+
+ @Test
+ void deleteConfiguration() throws Exception {
+ Path propertiesFile = tempDir.resolve("src/main/resources/application.properties");
+ Properties properties = new Properties();
+ properties.load(propertiesFile.toUri().toURL().openStream());
+ properties.put("foo.bar", "1234");
+ properties.store(new FileOutputStream(propertiesFile.toFile()), "");
+
+ CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar");
+ assertEquals(0, result.getExitCode());
+ assertTrue(result.getStdout().contains("Removing foo.bar"));
+ assertNull(config().getConfigValue("foo.bar").getValue());
+ }
+
+ @Test
+ void createEncryptedConfiguration() throws Exception {
+ CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "--value=1234", "-k");
+ assertEquals(0, result.getExitCode());
+ assertTrue(result.getStdout().contains("Adding foo.bar"));
+
+ SmallRyeConfig config = config();
+ assertEquals("aes-gcm-nopadding", config.getConfigValue("foo.bar").getExtendedExpressionHandler());
+ assertEquals("1234", config.getConfigValue("foo.bar").getValue());
+
+ String encryption = config.getRawValue("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key");
+ assertNotNull(encryption);
+
+ result = CliDriver.execute(tempDir, "config", "set", "--name=foo.baz", "--value=5678", "-k");
+ assertEquals(0, result.getExitCode());
+ assertTrue(result.getStdout().contains("Adding foo.baz"));
+
+ config = config();
+
+ assertEquals("aes-gcm-nopadding", config.getConfigValue("foo.bar").getExtendedExpressionHandler());
+ assertEquals("1234", config.getConfigValue("foo.bar").getValue());
+ assertTrue(config.isPropertyPresent("foo.baz"));
+
+ // TODO - radcortez - Requires update in SmallRye Config
+ //assertEquals("aes-gcm-nopadding", config.getConfigValue("foo.baz").getExtendedExpressionHandler());
+ //assertEquals("5678", config.getConfigValue("foo.baz").getValue());
+ }
+
+ @Test
+ void updateEncryptedConfiguration() throws Exception {
+ Path propertiesFile = tempDir.resolve("src/main/resources/application.properties");
+ Properties properties = new Properties();
+ properties.load(propertiesFile.toUri().toURL().openStream());
+ properties.put("foo.bar", "1234");
+ properties.store(new FileOutputStream(propertiesFile.toFile()), "");
+
+ CliDriver.Result result = CliDriver.execute(tempDir, "config", "set", "--name=foo.bar", "-k");
+ assertEquals(0, result.getExitCode());
+
+ SmallRyeConfig config = config();
+ assertEquals("aes-gcm-nopadding", config.getConfigValue("foo.bar").getExtendedExpressionHandler());
+ assertEquals("1234", config.getConfigValue("foo.bar").getValue());
+ }
+
+ private SmallRyeConfig config() throws Exception {
+ final PropertiesConfigSource propertiesConfigSource = new PropertiesConfigSource(
+ tempDir.resolve("src/main/resources/application.properties").toUri().toURL());
+ System.out.println(propertiesConfigSource.getProperties());
+ return new SmallRyeConfigBuilder()
+ .addDefaultInterceptors()
+ .addDiscoveredSecretKeysHandlers()
+ .withSources(propertiesConfigSource)
+ .withDefaultValue("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key", "default")
+ .build();
+ }
+}
diff --git a/docs/src/main/asciidoc/config-secrets.adoc b/docs/src/main/asciidoc/config-secrets.adoc
new file mode 100644
index 00000000000000..3f35d49bc2ddae
--- /dev/null
+++ b/docs/src/main/asciidoc/config-secrets.adoc
@@ -0,0 +1,161 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
+////
+= Secrets in Configuration
+include::_attributes.adoc[]
+:diataxis-type: core
+:categories: security
+
+Use encrypted configuration values to protect sensitive passwords, secrets, tokens and keys.
+
+A secret configuration may be expressed as `${handler::value}`, where the `handler` is the name of a
+`io.smallrye.config.SecretKeysHandler` to decode or decrypt the `value`.
+
+== Encrypt Configuration values
+
+To encrypt and later decrypt configuration values, add the following managed dependency:
+
+[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"]
+.pom.xml
+----
+
+ io.smallrye.config
+ smallrye-config-crypto
+
+----
+
+Use the Quarkus CLI command to add a new encrypted value or encrypt an existent value in `application.properties`:
+
+[role="primary asciidoc-tabs-sync-cli"]
+.CLI
+****
+[source, bash]
+----
+quarkus config set --encrypt --name=my.secret --value=1234
+----
+
+_For more information about how to install the Quarkus CLI and use it, please refer to xref:cli-tooling.adoc[the Quarkus CLI guide]._
+****
+
+The configuration property `my.secret` will be added to `application.properties` with the value `1234` encrypted and
+encoded in *Base64* and an expression `${aes-gcm-nopadding::}`, with the required secret handler to decrypt the value.
+If it doesn't exist, an encryption key is also generated and set into
+`smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key`.
+
+[source,properties]
+----
+my.secret=${aes-gcm-nopadding::DNNH8kR9i5EWBfuEwG1nhpkpov5MeFF9sjv-eqOgdHKf}
+
+smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key=ATa0tt4awR_GeQPpuLlFiA
+----
+
+NOTE: The default secret handler uses the `AES/GCM/NoPadding` algorithm and requires the expression
+`${aes-gcm-nopadding::value}` to decrypt the `value`.
+
+== Read Encrypted Configuration
+
+Quarkus configuration system, will automatically decrypt the configuration value when looking up `my.secret`.
+
+The encryption key used to encrypt the value must be the same used to decrypt the value and set into
+`smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key`.
+
+[source,java]
+----
+class BusinessBean {
+ @Inject
+ SmallRyeConfig config;
+
+ public void businessMethod() {
+ ConfigValue mySecret = config.getConfigValue("my.secret");
+ mySecret.getValue(); <1>
+ }
+}
+----
+<1> Returns the value `1234`.
+
+== Store secrets in a Keystore
+
+While having encrypted values, is better than plain values, we would still like to avoid having these set up in
+`application.properties`.
+
+Java KeyStore is used as a file-based `Vault`. Sensitive data can be imported to and securely stored in this `Vault`
+as Java `SecretKey` values. To use the `KeyStore` `ConfigSource` add the following managed dependency:
+
+```xml
+
+ io.smallrye.config
+ smallrye-config-source-keystore
+
+```
+
+=== Create a KeyStore
+
+The following command creates a simple KeyStore:
+
+[source, bash]
+----
+echo DNNH8kR9i5EWBfuEwG1nhpkpov5MeFF9sjv | keytool -importpass -alias my.secret -keystore properties -storepass arealpassword -storetype PKCS12 -v
+----
+
+The `-alias my.secret` option stores the configuration property name `my.secret` in the KeyStore with the value
+`DNNH8kR9i5EWBfuEwG1nhpkpov5MeFF9sjv`.
+
+The `-storepass secret` is the password required to access the keystore. It can also be encrypted with the Quarkus CLI:
+
+[role="primary asciidoc-tabs-sync-cli"]
+.CLI
+****
+[source, bash]
+----
+quarkus config encryptor -b --secret arealpassword --key ATa0tt4awR_GeQPpuLlFiA
+----
+****
+
+Generate the `KeyStore` with the encrypted `storepass` instead:
+
+[source, bash]
+----
+echo DNNH8kR9i5EWBfuEwG1nhpkpov5MeFF9sjv | keytool -importpass -alias my.secret -keystore properties -storepass DO6SYBiXqMRjl5tcQWRKI-OLdNy2fEnoiFGRnGmPA8YOluQrKA -storetype PKCS12 -v
+----
+
+We also need to safely store the encryption key. You shouldn't store the key with the rest of the secrets, so we can
+create another `KeyStore` for the key:
+
+[source, bash]
+----
+echo ATa0tt4awR_GeQPpuLlFiA | keytool -importpass -alias smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key -keystore key -storepass anotherpassword -storetype PKCS12 -v
+----
+
+=== Use the KeyStore
+
+To use the newly created `KeyStore`s, add the following configuration to `application.properties`:
+
+[source,properties]
+----
+smallrye.config.source.keystore."properties".path=properties <1>
+smallrye.config.source.keystore."properties".password=${aes-gcm-nopadding::DO6SYBiXqMRjl5tcQWRKI-OLdNy2fEnoiFGRnGmPA8YOluQrKA} <2>
+smallrye.config.source.keystore."properties".handler=aes-gcm-nopadding <3>
+
+smallrye.config.source.keystore."key".path=key <4>
+smallrye.config.source.keystore."key".password=anotherpassword <5>
+----
+<1> The path to the ´KeyStore` with properties secrets
+<2> The `KeyStore` password to be able to extract the `KeyStore` secrets
+<3> The `SecretKeyHandler` to decrypt the `KeyStore` secrets
+<4> The path to the ´KeyStore` with encryption key.
+<5> The `KeyStore` password to be able to extract the encryption key
+
+== Protect the KeyStore password
+
+You need to specify a `KeyStore` password in `application.properties` for Quarkus be able to extract secrets from the
+keystore. This keystore password is a sensitive value, and therefore you should consider how to minimize a risk of
+leaking it and how to protect it.
+
+One important thing you should be aware of is that leaking this password does not necessarily mean the actual secrets
+stored in the keystore will also be leaked since an unauthorized person will also need to access the actual keystore
+file. Restricting access to the keystore file to a limited number of roles and having Quarkus processes running in one
+of these roles will make it harder for anyone outside the group access the keystore. The keystore password can be set
+as an environment variable and this password should be periodically changed to limit a window during which an attacker
+can try to get to the keystore.