diff --git a/README.md b/README.md index ace7787..3779b6b 100644 --- a/README.md +++ b/README.md @@ -781,8 +781,18 @@ public StringEncryptor stringEncryptor() { return new SimpleGCMStringEncryptor(config); } ``` + +### Supply custom provider +Different provider can be configured as shown below. +```properties +jasypt.encryptor.provider-class-name= +#OR +jasypt.encryptor.provider-name= +``` + ### Encrypting properties with AES GCM-256 -You can use the [Maven Plugin](#maven-plugin) or follow a similar strategy as explained in [Asymmetric Encryption](#asymmetric-encryption)'s [Encrypting Properties](#encrypting-properties) +You can use the [Maven Plugin](#maven-plugin) or follow a similar strategy as explained in [Asymmetric Encryption](#asymmetric-encryption)'s [Encrypting Properties](#encrypting-properties) + ## Demo App The [jasypt-spring-boot-demo-samples](https://github.com/ulisesbocchio/jasypt-spring-boot-samples) repo contains working Spring Boot app examples. The main [jasypt-spring-boot-demo](https://github.com/ulisesbocchio/jasypt-spring-boot-samples/tree/master/jasypt-spring-boot-demo) Demo app explicitly sets a System property with the encryption password before the app runs. diff --git a/jasypt-spring-boot/pom.xml b/jasypt-spring-boot/pom.xml index 13d2bf5..033ad38 100644 --- a/jasypt-spring-boot/pom.xml +++ b/jasypt-spring-boot/pom.xml @@ -43,6 +43,11 @@ spring-boot-configuration-processor true + + org.bouncycastle + bc-fips + test + diff --git a/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/configuration/StringEncryptorBuilder.java b/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/configuration/StringEncryptorBuilder.java index b82981e..83933c5 100644 --- a/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/configuration/StringEncryptorBuilder.java +++ b/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/configuration/StringEncryptorBuilder.java @@ -75,6 +75,8 @@ private StringEncryptor createGCMDefault() { config.setSecretKeyAlgorithm(get(configProps::getGcmSecretKeyAlgorithm, propertyPrefix + ".gcm-secret-key-algorithm", "PBKDF2WithHmacSHA256")); config.setSecretKeyIterations(get(configProps::getKeyObtentionIterationsInt, propertyPrefix + ".key-obtention-iterations", 1000)); config.setIvGeneratorClassName(get(configProps::getIvGeneratorClassname, propertyPrefix + ".iv-generator-classname", "org.jasypt.iv.RandomIvGenerator")); + config.setProviderName(get(configProps::getProviderName, propertyPrefix + ".provider-name", null)); + config.setProviderClassName(get(configProps::getProviderClassName, propertyPrefix + ".provider-class-name", null)); return new SimpleGCMStringEncryptor(config); } diff --git a/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMByteEncryptor.java b/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMByteEncryptor.java index 6e04573..9446955 100644 --- a/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMByteEncryptor.java +++ b/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMByteEncryptor.java @@ -17,9 +17,12 @@ import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; +import java.security.Provider; import java.security.SecureRandom; import java.security.spec.KeySpec; import java.util.Base64; +import java.util.Optional; +import java.util.function.Supplier; /** *

SimpleGCMByteEncryptor class.

@@ -40,6 +43,7 @@ public class SimpleGCMByteEncryptor implements ByteEncryptor { private final Singleton key; private final String algorithm; private final Singleton ivGenerator; + private final Supplier cipherSupplier; /** {@inheritDoc} */ @SneakyThrows @@ -47,7 +51,7 @@ public class SimpleGCMByteEncryptor implements ByteEncryptor { public byte[] encrypt(byte[] message) { byte[] iv = this.ivGenerator.get().generateIv(GCM_IV_LENGTH); - Cipher cipher = Cipher.getInstance(this.algorithm); + Cipher cipher = cipherSupplier.get(); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.ENCRYPT_MODE, key.get(), gcmParameterSpec); @@ -63,7 +67,7 @@ public byte[] encrypt(byte[] message) { @SneakyThrows @Override public byte[] decrypt(byte[] encryptedMessage) { - Cipher cipher = Cipher.getInstance(this.algorithm); + Cipher cipher = cipherSupplier.get(); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, encryptedMessage, 0, GCM_IV_LENGTH); cipher.init(Cipher.DECRYPT_MODE, key.get(), gcmParameterSpec); return cipher.doFinal(encryptedMessage, GCM_IV_LENGTH, encryptedMessage.length - GCM_IV_LENGTH); @@ -135,6 +139,21 @@ public static SecretKey getAESKeyFromPassword(char[] password, SaltGenerator sal return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); } + @SneakyThrows + private Cipher instantiateCipher(Provider provider) { + return Cipher.getInstance(this.algorithm, provider); + } + + @SneakyThrows + private Cipher instantiateCipher(String providerName) { + return Cipher.getInstance(this.algorithm, providerName); + } + + @SneakyThrows + private Cipher instantiateCipher() { + return Cipher.getInstance(this.algorithm); + } + /** *

Constructor for SimpleGCMByteEncryptor.

* @@ -144,5 +163,10 @@ public SimpleGCMByteEncryptor(SimpleGCMConfig config) { this.key = Singleton.from(this::loadSecretKey, config); this.ivGenerator = Singleton.from(config::getActualIvGenerator); this.algorithm = config.getAlgorithm(); + this.cipherSupplier = config.getActualProvider() + .>map(p -> () -> this.instantiateCipher(p)) + .orElseGet(() -> Optional.ofNullable(config.getProviderName()).filter(e -> !e.trim().isEmpty()) + .>map(e -> () -> instantiateCipher(config.getProviderName())).orElseGet(() -> this::instantiateCipher) + ); } } diff --git a/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMConfig.java b/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMConfig.java index 8c4d300..61bd507 100644 --- a/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMConfig.java +++ b/jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/encryptor/SimpleGCMConfig.java @@ -13,6 +13,7 @@ import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; +import java.security.Provider; import java.util.Optional; /** @@ -37,6 +38,8 @@ public class SimpleGCMConfig { private SaltGenerator saltGenerator = null; private IvGenerator ivGenerator = null; private String ivGeneratorClassName = "org.jasypt.iv.RandomIvGenerator"; + private String providerClassName; + private String providerName; private Resource loadResource(Resource asResource, String asString, String asLocation) { return Optional.ofNullable(asResource) @@ -93,4 +96,13 @@ private IvGenerator instantiateIvGenerator() { public IvGenerator getActualIvGenerator() { return Optional.ofNullable(ivGenerator).orElseGet(this::instantiateIvGenerator); } + + @SneakyThrows + private Provider instantiateProvider(String className) { + return (Provider) Class.forName(this.providerClassName).newInstance(); + } + + public Optional getActualProvider() { + return Optional.ofNullable(this.providerClassName).filter(e -> !e.trim().isEmpty()).map(this::instantiateProvider); + } } diff --git a/jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/AesGcmEncryptorIntgnTest.java b/jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/AesGcmEncryptorIntgnTest.java new file mode 100644 index 0000000..85b833b --- /dev/null +++ b/jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/AesGcmEncryptorIntgnTest.java @@ -0,0 +1,98 @@ +package com.ulisesbocchio.jasyptspringboot; + +import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; +import org.springframework.test.context.TestPropertySource; + +import java.security.NoSuchProviderException; +import java.security.Security; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +@DisplayName("Aes Gcm Custom Provider") +class AesGcmEncryptorIntgnTest { + + static { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + @EnableEncryptableProperties + static class TestConfig { + } + + @Nested + @DisplayName("With Provider Class name") + @SpringBootTest(classes = AesGcmEncryptorIntgnTest.TestConfig.class) + @TestPropertySource(locations = "classpath:aes-gcm-provider-class.properties", + properties = {"jasypt.encryptor.provider-class-name=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider"}) + class WithProviderClassName { + + @Autowired + private Environment env; + + @Test + @DisplayName("Decrypt app properties") + void decryptAppProperties() { + assertEquals("John Doe", env.getProperty("test.encrypted.name")); + } + } + + @Nested + @DisplayName("With provider name") + @SpringBootTest(classes = AesGcmEncryptorIntgnTest.TestConfig.class) + @TestPropertySource(locations = "classpath:aes-gcm-provider-class.properties", + properties = {"jasypt.encryptor.provider-name=BCFIPS"}) + class WithProviderName { + + @Autowired + private Environment env; + + @Test + @DisplayName("Decrypt app properties") + void decryptAppProperties() { + assertEquals("John Doe", env.getProperty("test.encrypted.name")); + } + } + + @Nested + @DisplayName("With Invalid Provider Class name") + @SpringBootTest(classes = AesGcmEncryptorIntgnTest.TestConfig.class) + @TestPropertySource(locations = "classpath:aes-gcm-provider-class.properties", + properties = {"jasypt.encryptor.provider-class-name=org.bouncycastle.jashoua.provider.BouncyCastleFipsProvider"}) + class WithInvalidProviderClassName { + + @Autowired + private Environment env; + + @Test + @DisplayName("Decrypt app properties") + void decryptAppProperties() { + assertThrows(ClassNotFoundException.class, () -> env.getProperty("test.encrypted.name")); + } + } + + @Nested + @DisplayName("With invalid provider name") + @SpringBootTest(classes = AesGcmEncryptorIntgnTest.TestConfig.class) + @TestPropertySource(locations = "classpath:aes-gcm-provider-class.properties", + properties = {"jasypt.encryptor.provider-name=BCSPIF"}) + class WithInvalidProviderName { + + @Autowired + private Environment env; + + @Test + @DisplayName("Decrypt app properties") + void decryptAppProperties() { + assertThrows(NoSuchProviderException.class, () -> env.getProperty("test.encrypted.name")); + } + } +} diff --git a/jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/EncryptorTest.java b/jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/EncryptorTest.java index 67ef965..284c970 100644 --- a/jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/EncryptorTest.java +++ b/jasypt-spring-boot/src/test/java/com/ulisesbocchio/jasyptspringboot/EncryptorTest.java @@ -3,29 +3,32 @@ import com.ulisesbocchio.jasyptspringboot.encryptor.*; import com.ulisesbocchio.jasyptspringboot.util.AsymmetricCryptography; import lombok.SneakyThrows; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.jasypt.salt.RandomSaltGenerator; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.core.io.ClassPathResource; import org.springframework.util.FileCopyUtils; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.security.NoSuchProviderException; import java.security.Provider; import java.security.SecureRandom; import java.security.Security; import java.util.Base64; import java.util.List; import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class EncryptorTest { @@ -403,4 +406,57 @@ public void test_GcmPooledEncryptor_encryption_concurrency() { customThreadPool.shutdown(); } } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" ", "\t", "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider"}) + public void test_GcmKeyProviderClsEncryptor_encryption(String providerClassName) { + final String message = "This is the secret message... BOOHOOO!"; + SimpleGCMConfig config = new SimpleGCMConfig(); + config.setSecretKey(gcmKey); + config.setProviderClassName(providerClassName); + SimpleGCMStringEncryptor gcmKeyProviderClsEncryptor = new SimpleGCMStringEncryptor(config); + + final String ciphertext = gcmKeyProviderClsEncryptor.encrypt(message); + + final String decrypted = gcmKeyProviderClsEncryptor.decrypt(ciphertext); + + assertEquals(message, decrypted); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" ", "\t", BouncyCastleFipsProvider.PROVIDER_NAME}) + public void test_GcmKeyProviderNameEncryptor_encryption(String providerName) { + final String message = "This is the secret message... BOOHOOO!"; + Security.addProvider(new BouncyCastleFipsProvider()); + SimpleGCMConfig config = new SimpleGCMConfig(); + config.setSecretKey(gcmKey); + config.setProviderName(providerName); + SimpleGCMStringEncryptor gcmKeyProviderNameEncryptor = new SimpleGCMStringEncryptor(config); + + final String ciphertext = gcmKeyProviderNameEncryptor.encrypt(message); + + final String decrypted = gcmKeyProviderNameEncryptor.decrypt(ciphertext); + + assertEquals(message, decrypted); + } + + @Test + public void test_GcmKeyProviderClsEncryptor_encryptionfails() { + SimpleGCMConfig config = new SimpleGCMConfig(); + config.setSecretKey(gcmKey); + config.setProviderClassName("unkown.class.name"); + assertThrows(ClassNotFoundException.class, () -> new SimpleGCMStringEncryptor(config)); + } + + @Test + public void test_GcmKeyProviderNameEncryptor_encryptionfails() { + final String message = "This is the secret message... BOOHOOO!"; + SimpleGCMConfig config = new SimpleGCMConfig(); + config.setSecretKey(gcmKey); + config.setProviderName("unknown provider name"); + SimpleGCMStringEncryptor gcmKeyProviderNameEncryptor = new SimpleGCMStringEncryptor(config); + assertThrows(NoSuchProviderException.class, () -> gcmKeyProviderNameEncryptor.encrypt(message)); + } } diff --git a/jasypt-spring-boot/src/test/resources/aes-gcm-provider-class.properties b/jasypt-spring-boot/src/test/resources/aes-gcm-provider-class.properties new file mode 100644 index 0000000..5a43d5c --- /dev/null +++ b/jasypt-spring-boot/src/test/resources/aes-gcm-provider-class.properties @@ -0,0 +1,2 @@ +jasypt.encryptor.gcm-secret-key-password = mypassword +test.encrypted.name = ENC(+S1jSOsxZdgqKCabNKycD29tl+Vwr7pMGIY1lhv7JEjdXaXm) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 37be680..9180be9 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 1.9.3 3.10.1 1.19.0 + 2.0.0 @@ -212,6 +213,11 @@ system-rules ${system.rules.version} + + org.bouncycastle + bc-fips + ${bcfips.version} +