Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support custom provider in AES encryption. #401

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<FQCN>
#OR
jasypt.encryptor.provider-name=<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.
Expand Down
5 changes: 5 additions & 0 deletions jasypt-spring-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
* <p>SimpleGCMByteEncryptor class.</p>
Expand All @@ -40,14 +43,15 @@ public class SimpleGCMByteEncryptor implements ByteEncryptor {
private final Singleton<SecretKey> key;
private final String algorithm;
private final Singleton<IvGenerator> ivGenerator;
private final Supplier<Cipher> cipherSupplier;

/** {@inheritDoc} */
@SneakyThrows
@Override
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);

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

/**
* <p>Constructor for SimpleGCMByteEncryptor.</p>
*
Expand All @@ -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()
.<Supplier<Cipher>>map(p -> () -> this.instantiateCipher(p))
.orElseGet(() -> Optional.ofNullable(config.getProviderName()).filter(e -> !e.trim().isEmpty())
.<Supplier<Cipher>>map(e -> () -> instantiateCipher(config.getProviderName())).orElseGet(() -> this::instantiateCipher)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.Provider;
import java.util.Optional;

/**
Expand All @@ -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)
Expand Down Expand Up @@ -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<Provider> getActualProvider() {
return Optional.ofNullable(this.providerClassName).filter(e -> !e.trim().isEmpty()).map(this::instantiateProvider);
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
jasypt.encryptor.gcm-secret-key-password = mypassword
test.encrypted.name = ENC(+S1jSOsxZdgqKCabNKycD29tl+Vwr7pMGIY1lhv7JEjdXaXm)
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<jasypt.version>1.9.3</jasypt.version>
<maven.compiler.version>3.10.1</maven.compiler.version>
<system.rules.version>1.19.0</system.rules.version>
<bcfips.version>2.0.0</bcfips.version>
</properties>

<modules>
Expand Down Expand Up @@ -212,6 +213,11 @@
<artifactId>system-rules</artifactId>
<version>${system.rules.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
<version>${bcfips.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
Expand Down