From 6dfc59af8fec81004234c199fb23858f990f0d20 Mon Sep 17 00:00:00 2001 From: croway Date: Wed, 2 Sep 2026 14:50:02 +0200 Subject: [PATCH] CAMEL-24506: camel-jasypt-starter - modern default algorithm and safer usage example The starter defaulted `camel.component.jasypt.algorithm` to `PBEWithMD5AndDES`, a 56-bit DES cipher with MD5-based key derivation, and that value flowed into `EnvironmentStringPBEConfig.setAlgorithm` on every decryption path. The starter already carries `PBEWITHHMACSHA256ANDAES_256` in `JasyptEncryptedPropertiesUtils.ALGORITHMS_THAT_REQUIRE_IV`, so making it the default engages the existing `RandomIvGenerator` path automatically, with no new machinery. The default is now `PBEWITHHMACSHA256ANDAES_256`. This is a breaking change for values encrypted under the previous default; those can still be read by pinning `camel.component.jasypt.algorithm = PBEWithMD5AndDES`. The javadoc and the generated configuration metadata document both the new default and the opt-back. The upstream `camel-jasypt` component leaves the algorithm unset, so it still falls back to the Jasypt library default of `PBEWithMD5AndDES`. Aligning it is a separate change; the documentation therefore spells out that the encryption tooling must be given a matching `-a` and a random IV generator, since the Jasypt CLI installs no IV generator unless asked. The usage example also placed `camel.component.jasypt.password` in the same properties block as the `ENC(...)` value, which defeats the purpose of encrypting it. It now uses `sysenv:JASYPT_PASSWORD`, carries a warning that the master password must not live beside the ciphertext, and documents the `sysenv:` and `sys:` prefixes the code already supports. Tests: `JasyptDefaultAlgorithmTest` covers the default algorithm and its IV generator, an encrypt/decrypt round trip under the default, decryption of a legacy-algorithm value with the opt-back set, and the failure of that value under the new default. `EncryptedPropertiesUtilsTest` is updated for the IV auto-detection now triggered by the default, with a new case for an algorithm that does not require an IV. Co-Authored-By: Claude Opus 5 --- .../src/main/doc/usage.adoc | 41 +++++++- .../src/main/docs/jasypt.json | 6 +- ...asyptEncryptedPropertiesConfiguration.java | 10 +- .../EncryptedPropertiesUtilsTest.java | 11 ++- .../JasyptDefaultAlgorithmTest.java | 94 +++++++++++++++++++ .../modules/ROOT/pages/starters/jasypt.adoc | 45 +++++++-- 6 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/JasyptDefaultAlgorithmTest.java diff --git a/components-starter/camel-jasypt-starter/src/main/doc/usage.adoc b/components-starter/camel-jasypt-starter/src/main/doc/usage.adoc index a12863cd3c4e..b539d6ec8605 100644 --- a/components-starter/camel-jasypt-starter/src/main/doc/usage.adoc +++ b/components-starter/camel-jasypt-starter/src/main/doc/usage.adoc @@ -1,14 +1,47 @@ -Add encrypted values to your `application.properties` using the `ENC(...)` syntax: +Add encrypted values to your `application.properties` using the `ENC(...)` syntax, and point the starter at the master +password: [source,properties] ---- my.secret = ENC(encrypted-value-here) -camel.component.jasypt.password = the-master-password +camel.component.jasypt.password = sysenv:JASYPT_PASSWORD ---- -Use Camel's Jasypt tooling to encrypt values: +WARNING: The master password must never be stored in the same file, or the same repository, as the encrypted values it +protects. A file that carries both the ciphertext and the key that unlocks it is no better than a plaintext file. Supply +the master password from the environment or from an external secret store at deployment time. + +`camel.component.jasypt.password` understands two prefixes that keep the password out of the configuration file: + +* `sysenv:` looks the password up in the OS environment variable ``. +* `sys:` looks the password up in the JVM system property ``. + +Any other value is used as the password verbatim, which is only appropriate when the property itself is injected by an +external secret store (for example a mounted secret or a config server) rather than checked in. + +=== Encryption algorithm + +The default algorithm is `PBEWITHHMACSHA256ANDAES_256`. It requires an initialization vector, which the starter +generates automatically (`org.jasypt.iv.RandomIvGenerator`) unless `camel.component.jasypt.iv-generator-class-name` is +set explicitly. + +Use Jasypt tooling to encrypt values, passing the *same* algorithm and a random IV generator; a value encrypted under a +different algorithm, or without an IV generator, cannot be decrypted at runtime: [source,bash] ---- -camel jasypt encrypt --password=the-master-password --input=my-secret-value +jbang org.apache.camel:camel-jasypt: \ + -c encrypt -p "$JASYPT_PASSWORD" -i my-secret-value \ + -a PBEWITHHMACSHA256ANDAES_256 -riga SHA1PRNG +---- + +NOTE: The `camel-jasypt` CLI entrypoint is deprecated. The `encrypt.sh` script shipped in the +https://github.com/jasypt/jasypt/releases/tag/jasypt-1.9.3[Jasypt distribution] provides the same workflow, and takes +the algorithm and IV generator through its own `algorithm` and `ivGeneratorClassName` arguments. + +To keep reading values that were encrypted with the previous default algorithm, pin it explicitly: + +[source,properties] +---- +camel.component.jasypt.algorithm = PBEWithMD5AndDES ---- diff --git a/components-starter/camel-jasypt-starter/src/main/docs/jasypt.json b/components-starter/camel-jasypt-starter/src/main/docs/jasypt.json index 1b83e21b472f..2656cc8774f0 100644 --- a/components-starter/camel-jasypt-starter/src/main/docs/jasypt.json +++ b/components-starter/camel-jasypt-starter/src/main/docs/jasypt.json @@ -10,9 +10,9 @@ { "name": "camel.component.jasypt.algorithm", "type": "java.lang.String", - "description": "The algorithm to be used for decryption. Default: PBEWithMD5AndDES", + "description": "The algorithm to be used for decryption. Default: PBEWITHHMACSHA256ANDAES_256. This algorithm requires an initialization vector, which is generated automatically unless ivGeneratorClassName is set explicitly. Values encrypted with a different algorithm can only be decrypted by setting this option to that algorithm, for example PBEWithMD5AndDES.", "sourceType": "org.apache.camel.component.jasypt.springboot.JasyptEncryptedPropertiesConfiguration", - "defaultValue": "PBEWithMD5AndDES" + "defaultValue": "PBEWITHHMACSHA256ANDAES_256" }, { "name": "camel.component.jasypt.early-decryption-enabled", @@ -37,7 +37,7 @@ { "name": "camel.component.jasypt.password", "type": "java.lang.String", - "description": "The master password used by Jasypt for decrypting the values. This option supports prefixes which influence the master password lookup behaviour: sysenv: means to lookup the OS system environment with the given key. sys: means to lookup a JVM system property.", + "description": "The master password used by Jasypt for decrypting the values. This option supports prefixes which influence the master password lookup behaviour: sysenv: means to lookup the OS system environment with the given key. sys: means to lookup a JVM system property. The master password should be supplied through one of those prefixes, or from an external secret store, and should not be stored alongside the encrypted values it protects.", "sourceType": "org.apache.camel.component.jasypt.springboot.JasyptEncryptedPropertiesConfiguration" }, { diff --git a/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/JasyptEncryptedPropertiesConfiguration.java b/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/JasyptEncryptedPropertiesConfiguration.java index e721dfad1649..a753a16d63cb 100644 --- a/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/JasyptEncryptedPropertiesConfiguration.java +++ b/components-starter/camel-jasypt-starter/src/main/java/org/apache/camel/component/jasypt/springboot/JasyptEncryptedPropertiesConfiguration.java @@ -37,14 +37,18 @@ public class JasyptEncryptedPropertiesConfiguration { private boolean earlyDecryptionEnabled; /** - * The algorithm to be used for decryption. Default: PBEWithMD5AndDES + * The algorithm to be used for decryption. Default: PBEWITHHMACSHA256ANDAES_256. This algorithm requires an + * initialization vector, which is generated automatically unless ivGeneratorClassName is set explicitly. Values + * encrypted with a different algorithm can only be decrypted by setting this option to that algorithm, for example + * PBEWithMD5AndDES. */ - private String algorithm = "PBEWithMD5AndDES"; + private String algorithm = "PBEWITHHMACSHA256ANDAES_256"; /** * The master password used by Jasypt for decrypting the values. This option supports prefixes which influence the * master password lookup behaviour: sysenv: means to lookup the OS system environment with the given key. sys: - * means to lookup a JVM system property. + * means to lookup a JVM system property. The master password should be supplied through one of those prefixes, or + * from an external secret store, and should not be stored alongside the encrypted values it protects. */ private String password; diff --git a/components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/EncryptedPropertiesUtilsTest.java b/components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/EncryptedPropertiesUtilsTest.java index 1070a51b9cf3..982cbaf2ecd7 100644 --- a/components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/EncryptedPropertiesUtilsTest.java +++ b/components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/EncryptedPropertiesUtilsTest.java @@ -30,9 +30,18 @@ public class EncryptedPropertiesUtilsTest { @Test public void noIvGeneratorPropertyTest() { - // IVGenerator is null + // IVGenerator is not configured, the default algorithm requires one so it is auto detected JasyptEncryptedPropertiesConfiguration configuration = new JasyptEncryptedPropertiesConfiguration(); IvGenerator ivGenerator = getIVGenerator(configuration); + assertThat(ivGenerator).isInstanceOf(RandomIvGenerator.class); + } + + @Test + public void noIvGeneratorPropertyWithAlgorithmThatDoesNotNeedIvTest() { + // IVGenerator is not configured and the algorithm does not require one + JasyptEncryptedPropertiesConfiguration configuration = new JasyptEncryptedPropertiesConfiguration(); + configuration.setAlgorithm("PBEWithMD5AndDES"); + IvGenerator ivGenerator = getIVGenerator(configuration); assertThat(ivGenerator).isInstanceOf(NoIvGenerator.class); } diff --git a/components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/JasyptDefaultAlgorithmTest.java b/components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/JasyptDefaultAlgorithmTest.java new file mode 100644 index 000000000000..b4485f96b4b8 --- /dev/null +++ b/components-starter/camel-jasypt-starter/src/test/java/org/apache/camel/component/jasypt/springboot/JasyptDefaultAlgorithmTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.jasypt.springboot; + +import org.jasypt.encryption.StringEncryptor; +import org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig; +import org.jasypt.exceptions.EncryptionOperationNotPossibleException; +import org.jasypt.iv.NoIvGenerator; +import org.jasypt.iv.RandomIvGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Verifies the default encryption algorithm of the starter, and that values encrypted with the previous default can + * still be read by pinning that algorithm explicitly. + */ +public class JasyptDefaultAlgorithmTest { + + private static final String DEFAULT_ALGORITHM = "PBEWITHHMACSHA256ANDAES_256"; + + private static final String LEGACY_ALGORITHM = "PBEWithMD5AndDES"; + + private static final String PLAIN_TEXT = "mysecret"; + + private static final String MASTER_PASSWORD = "legacy-master-password"; + + /** + * {@value #PLAIN_TEXT} encrypted with {@value #LEGACY_ALGORITHM}, the master password {@value #MASTER_PASSWORD} and + * no initialization vector generator, which is how the starter encrypted values before the default changed. + */ + private static final String LEGACY_ENCRYPTED_VALUE = "+1thxaTtHh5z+yuyvFlCl5gafagmagQV"; + + private final ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JasyptEncryptedPropertiesAutoconfiguration.class)) + .withPropertyValues("camel.component.jasypt.password=" + MASTER_PASSWORD); + + @Test + public void defaultAlgorithmRequiresAndGetsAnInitializationVector() { + runner.run(context -> { + assertThat(context.getBean(JasyptEncryptedPropertiesConfiguration.class).getAlgorithm()) + .isEqualTo(DEFAULT_ALGORITHM); + EnvironmentStringPBEConfig config = context.getBean(EnvironmentStringPBEConfig.class); + assertThat(config.getAlgorithm()).isEqualTo(DEFAULT_ALGORITHM); + assertThat(config.getIvGenerator()).isInstanceOf(RandomIvGenerator.class); + }); + } + + @Test + public void defaultAlgorithmEncryptsAndDecrypts() { + runner.run(context -> { + StringEncryptor encryptor = context.getBean(StringEncryptor.class); + String encrypted = encryptor.encrypt(PLAIN_TEXT); + assertThat(encrypted).isNotEqualTo(PLAIN_TEXT); + assertThat(encryptor.decrypt(encrypted)).isEqualTo(PLAIN_TEXT); + }); + } + + @Test + public void legacyAlgorithmCanBePinnedExplicitly() { + runner.withPropertyValues("camel.component.jasypt.algorithm=" + LEGACY_ALGORITHM).run(context -> { + EnvironmentStringPBEConfig config = context.getBean(EnvironmentStringPBEConfig.class); + assertThat(config.getAlgorithm()).isEqualTo(LEGACY_ALGORITHM); + assertThat(config.getIvGenerator()).isInstanceOf(NoIvGenerator.class); + assertThat(context.getBean(StringEncryptor.class).decrypt(LEGACY_ENCRYPTED_VALUE)).isEqualTo(PLAIN_TEXT); + }); + } + + @Test + public void legacyValueIsNotReadableUnderTheDefaultAlgorithm() { + runner.run(context -> { + StringEncryptor encryptor = context.getBean(StringEncryptor.class); + assertThatExceptionOfType(EncryptionOperationNotPossibleException.class) + .isThrownBy(() -> encryptor.decrypt(LEGACY_ENCRYPTED_VALUE)); + }); + } +} diff --git a/docs/spring-boot/modules/ROOT/pages/starters/jasypt.adoc b/docs/spring-boot/modules/ROOT/pages/starters/jasypt.adoc index 444cdcae0682..bdeb2ba772a9 100644 --- a/docs/spring-boot/modules/ROOT/pages/starters/jasypt.adoc +++ b/docs/spring-boot/modules/ROOT/pages/starters/jasypt.adoc @@ -19,19 +19,52 @@ This starter integrates http://www.jasypt.org[Jasypt] with both Camel's property == Usage -Add encrypted values to your `application.properties` using the `ENC(...)` syntax: +Add encrypted values to your `application.properties` using the `ENC(...)` syntax, and point the starter at the master +password: [source,properties] ---- my.secret = ENC(encrypted-value-here) -camel.component.jasypt.password = the-master-password +camel.component.jasypt.password = sysenv:JASYPT_PASSWORD ---- -Use Camel's Jasypt tooling to encrypt values: +WARNING: The master password must never be stored in the same file, or the same repository, as the encrypted values it +protects. A file that carries both the ciphertext and the key that unlocks it is no better than a plaintext file. Supply +the master password from the environment or from an external secret store at deployment time. + +`camel.component.jasypt.password` understands two prefixes that keep the password out of the configuration file: + +* `sysenv:` looks the password up in the OS environment variable ``. +* `sys:` looks the password up in the JVM system property ``. + +Any other value is used as the password verbatim, which is only appropriate when the property itself is injected by an +external secret store (for example a mounted secret or a config server) rather than checked in. + +=== Encryption algorithm + +The default algorithm is `PBEWITHHMACSHA256ANDAES_256`. It requires an initialization vector, which the starter +generates automatically (`org.jasypt.iv.RandomIvGenerator`) unless `camel.component.jasypt.iv-generator-class-name` is +set explicitly. + +Use Jasypt tooling to encrypt values, passing the *same* algorithm and a random IV generator; a value encrypted under a +different algorithm, or without an IV generator, cannot be decrypted at runtime: [source,bash] ---- -camel jasypt encrypt --password=the-master-password --input=my-secret-value +jbang org.apache.camel:camel-jasypt: \ + -c encrypt -p "$JASYPT_PASSWORD" -i my-secret-value \ + -a PBEWITHHMACSHA256ANDAES_256 -riga SHA1PRNG +---- + +NOTE: The `camel-jasypt` CLI entrypoint is deprecated. The `encrypt.sh` script shipped in the +https://github.com/jasypt/jasypt/releases/tag/jasypt-1.9.3[Jasypt distribution] provides the same workflow, and takes +the algorithm and IV generator through its own `algorithm` and `ivGeneratorClassName` arguments. + +To keep reading values that were encrypted with the previous default algorithm, pin it explicitly: + +[source,properties] +---- +camel.component.jasypt.algorithm = PBEWithMD5AndDES ---- == Spring Boot Auto-Configuration @@ -41,11 +74,11 @@ The starter supports 9 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== | Name | Description | Default | Type -| camel.component.jasypt.algorithm | The algorithm to be used for decryption. Default: PBEWithMD5AndDES | PBEWithMD5AndDES | String +| camel.component.jasypt.algorithm | The algorithm to be used for decryption. Default: PBEWITHHMACSHA256ANDAES_256. This algorithm requires an initialization vector, which is generated automatically unless ivGeneratorClassName is set explicitly. Values encrypted with a different algorithm can only be decrypted by setting this option to that algorithm, for example PBEWithMD5AndDES. | PBEWITHHMACSHA256ANDAES_256 | String | camel.component.jasypt.early-decryption-enabled | Enable the early properties decryption during Spring Start Up. Enabling this feature, encrypted properties can be decrypted before the Spring Boot AutoConfiguration kicks in, for example, server.port=ENC(oBpQDDUvFY0c4WNAG0o4LIS5bWqmlxYlUUDTW2iXJIAZFYvM+3vOredaMcVfL4xW) will be decrypted to 8082, and the application will start using that port. | false | Boolean | camel.component.jasypt.enabled | Enable the component | false | Boolean | camel.component.jasypt.iv-generator-class-name | The initialization vector (IV) generator applied in decryption operations. Default: org.jasypt.iv. | | String -| camel.component.jasypt.password | The master password used by Jasypt for decrypting the values. This option supports prefixes which influence the master password lookup behaviour: sysenv: means to lookup the OS system environment with the given key. sys: means to lookup a JVM system property. | | String +| camel.component.jasypt.password | The master password used by Jasypt for decrypting the values. This option supports prefixes which influence the master password lookup behaviour: sysenv: means to lookup the OS system environment with the given key. sys: means to lookup a JVM system property. The master password should be supplied through one of those prefixes, or from an external secret store, and should not be stored alongside the encrypted values it protects. | | String | camel.component.jasypt.provider-name | The class name of the security provider to be used for obtaining the encryption algorithm. | | String | camel.component.jasypt.random-iv-generator-algorithm | The algorithm for the random iv generator | SHA1PRNG | String | camel.component.jasypt.random-salt-generator-algorithm | The algorithm for the salt generator | SHA1PRNG | String