En el desarrollo de aplicaciones modernas, garantizar la seguridad de los datos es fundamental. Almacenar credenciales y propiedades sensibles en texto plano dentro de los archivos de configuración representa un riesgo significativo. Jasypt proporciona una integración nativa con Spring Boot para cifrar y descifrar estas propiedades de manera transparente, evitando la exposición de información crítica en el código fuente.
Dependencias del Proyecto
Para integrar Jasypt en un proyecto basado en Spring Boot 3.x, se debe incluir la siguiente dependencia en el archivo pom.xml:
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
Resolución de Conflictos en Entornos Reactivos
Al utilizar Spring Cloud Gateway (basado en WebFlux) junto con componentes que requieren Jasypt (basado en el modelo Servlet) para descifrar contraseñas de bases de datos como Redis, pueden surgir conflictos de contexto durante el inicio de la aplicación. Para mitigar este problema de incompatibilidad entre entornos reactivos y bloqueantes, es necesario forzar la carga temprana del bean de cifrado mediante la configuración de bootstrap.
Configuración del Bean de Cifrado
En lugar de pasar la contraseña maestra a través de argumentos de la línea de comandos (lo cual puede exponerla en los procesos de Docker o en el historial del sistema), se recomienda definir el bean de cifrado directamente en una clase de configuración. Esto permite gestionar la contraseña maestra a través de variables de entorno del sistema operativo.
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SecurityEncryptionConfig {
@Bean("customTextEncryptor")
public StringEncryptor textEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
// La contraseña maestra se obtiene de las variables de entorno o propiedades del sistema
config.setPassword(System.getProperty("jasypt.encryptor.password"));
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("100000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}
Utilidad de Cifrado y Descifrado
Se ha refactorizado la clase de utilidad para eliminar la duplicación de código en la configuración del algoritmo y utilizar la inyección de dependencias de Spring en lugar de métodos estáticos para obtener el bean del contexto.
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.EnvironmentPBEConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class CipherManager {
private final ApplicationContext context;
private static final String DEFAULT_ALGORITHM = "PBEWITHHMACSHA512ANDAES_256";
public CipherManager(ApplicationContext context) {
this.context = context;
}
private EnvironmentPBEConfig buildConfig(String algorithm, String masterPassword) {
EnvironmentPBEConfig config = new EnvironmentPBEConfig();
config.setAlgorithm(algorithm);
config.setPassword(masterPassword);
config.setKeyObtentionIterations("100000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
return config;
}
public String encryptData(String plainText, String masterPassword) {
return encryptData(plainText, DEFAULT_ALGORITHM, masterPassword);
}
public String encryptData(String plainText, String algorithm, String masterPassword) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setConfig(buildConfig(algorithm, masterPassword));
return encryptor.encrypt(plainText);
}
public String decryptData(String cipherText, String masterPassword) {
return decryptData(cipherText, DEFAULT_ALGORITHM, masterPassword);
}
public String decryptData(String cipherText, String algorithm, String masterPassword) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setConfig(buildConfig(algorithm, masterPassword));
return encryptor.decrypt(cipherText);
}
public String encryptWithSpringBean(String plainText) {
StringEncryptor encryptor = context.getBean("customTextEncryptor", StringEncryptor.class);
return encryptor.encrypt(plainText);
}
public String decryptWithSpringBean(String cipherText) {
StringEncryptor encryptor = context.getBean("customTextEncryptor", StringEncryptor.class);
return encryptor.decrypt(cipherText);
}
}
Configuración en application.yml
Para que Jasypt identifique qué propiedades deben ser descifradas automáticamente al iniciar la aplicación, los valores cifrados deben estar envueltos en la sintaxis ENC(valor_cifrado). A continuación, se muestra la configuración necesaria para vincular el bean personalizado y resolver el conflicto de carga en entornos reactivos:
spring:
application:
name: core-microservice
cloud:
bootstrap:
# Carga anticipada para evitar conflictos en entornos reactivos (WebFlux)
sources: com.example.security.SecurityEncryptionConfig
jasypt:
encryptor:
# Referencia al bean configurado manualmente
bean: customTextEncryptor
# Ejemplo de propiedades de base de datos cifradas
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: db_user
password: ENC(aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890)