Hoy vamos a ver una interesante herramienta que nos permitirá encriptar nuestras propiedades. Es una mala praxis común, que se suban a repositorios de códigos o directamente se pongan 'a fuego' contraseñas a utilizar por nuestro código Java. Para evitar esto, podemos utilizar Jasypt, el cual como veremos es facil de usar y nos da múltiples formas de configuración y encriptación.
A lo largo del post, nos centraremos en como usarlo en base a su posible uso con la encriptación de propiedades. Que es el lugar donde habitualmente almacenamos información sensible. Pero antes de ver un ejemplo de como implementar, veremos cuales son las posibles opciones que nos provee Jasypt.
Para empezar y siempre que hablemos de encriptar String, tendremos cuatro posibles opciones:
- BasicPasswordEncryptor: Nos permite hacer una encriptación del código sencilla y poco costosa.
- StrongPasswordEncryptor: Nos permite hacer una encriptación más compleja pero también más costosa a nivel de consumo de CPU.
- StandardPBEStringEncryptor: Nos permite hacer encriptación bidirecional, en la cual podremos indicar distintos valores: el algoritmo a utilizar, el IvGenerator y lo más importante la clave a partir de la cual encriptar y desencriptar.
- PooledPBEStringEncryptor: Muy similar al anterior, solo que además crear un pool de StandardPBEStringEncryptor y por tanto mejora el rendimiento a la hora de utilizarlo. Muy util si vamos a utilizarlo mucho.
Su uso es realmente básico, y lo podremos ver más adelante. Pero igual necesitamos aclarar un par de conceptos antes. Sobre todo en lo referente a los StandardPBEStringEncryptor que permiten una mayor configuración.
- Clave maestra
Por un lado, tenemos que indicar cual es la clave sobre la que partiremos para poder realizar esta encriptacion/desencriptación. Esta puede se la que deseemos, siempre y cuando cumpla con unos mínimos de seguridad en cuanto a tipo de caracteres y mínimo de longitud. Pero aquí, lo más importante puede ser como almacenamos esa contraseña. Porque estamos ante la misma disyuntiva de tener que poner una clave en nuestro código. Para ello, la mejor solución, siguiendo la metodología de 12 factor, es almacenar dicha clave en una variable del entorno. Si usamos Linux podemos crear dicha variable en el fichero /etc/environment (pero con cuidado, que necesitamos reiniciar luego el sistema para que se tenga en cuenta). Y luego obtenerla fácilmente en nuestro código Java.
private String PBES_PWD = System.getenv("JASYPT_PWD");
- Algoritmo de encriptación
for (Provider provider : Security.getProviders()) { log.info("Provider: " + provider.getName()); for (Provider.Service service : provider.getServices()) { log.info(" Algorithm: " + service.getAlgorithm()); } }
- IvGenerator
Los algoritmos de tipo PBEWithDigestAndAES que son soportados por Java Cryptography Extension aka JCE, necesitan de un vector de inicialización aka IV. Este además debe de ser aleatorio y usado una única vez. Para ello Jasypt proporciona la clase RandomIvGenerator.
A continuación veremos un pequeño trozo de código que incluye las distintas configuraciones, para que se vean lo sencillas que son de usar. A tener en cuenta que las clases StandardPBEStringEncryptor y PooledPBEStringEncryptor tienen una configuración por defecto, por lo que no es necesario incluir todos los parámetros, pero al menos si hay que incluir la clave maestra.
private String toEncrypt = "pAsSw0Rd"; private String PBES_PWD = System.getenv("JASYPT_PWD"); private Long init; private Long end; @Test public void test() { log.info("basic"); BasicPasswordEncryptor passwordEncryptor = new BasicPasswordEncryptor(); String encryptedPassword = passwordEncryptor.encryptPassword(toEncrypt); assertThat(passwordEncryptor.checkPassword(toEncrypt, encryptedPassword), equalTo(true)); log.info("strong"); StrongPasswordEncryptor passwordEncryptor = new StrongPasswordEncryptor(); String encryptedPassword = passwordEncryptor.encryptPassword(toEncrypt); assertThat(passwordEncryptor.checkPassword(toEncrypt, encryptedPassword), equalTo(true)); log.info("standarPbes"); StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); encryptor.setPassword(PBES_PWD); // we HAVE TO set a password String encryptedPassword = encryptor.encrypt(toEncrypt); assertThat(encryptor.decrypt(encryptedPassword), equalTo(toEncrypt)); log.info("pooledPbes_configure"); PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); encryptor.setAlgorithm("PBEWithHmacSHA512AndAES_256"); encryptor.setIvGenerator(new RandomIvGenerator()); encryptor.setPoolSize(4); encryptor.setPassword(PBES_PWD); // we HAVE TO set a password String encryptedPassword = encryptor.encrypt(toEncrypt); assertThat(encryptor.decrypt(encryptedPassword), equalTo(toEncrypt)); }
Y si comparamos lo tiempos, tendremos la siguiente salida.
BasicPasswordEncryptor: 3 ms StrongPasswordEncryptor: 99 ms StandardPBEStringEncryptor: 7ms PooledPBEStringEncryptor without configuration: 3ms PooledPBEStringEncryptor with configuration: 8ms
Y ahora que sabemos el uso básico, vamos a aplicarlo a un fichero de propiedades que tengamos en nuestra aplicación. Para poder hacer uso de ello, solo tendremos que realizar estos pasos:
- Crear un objeto de tipo StringEncryptor tal y como hemos visto anteriormente.
- Crear un objeto de tipo Properties con ayuda de la clase EncryptableProperties. A esta clase le deberemos pasar el objeto StringEncryptor creado en el punto anterior.
- Almacenar las propiedades encriptadas encapsuladas entre ENC( ).
@Log4j2 public class EncryptorHelper { @Getter private static EncryptorHelper INSTANCE = new EncryptorHelper(); Properties props = null; private EncryptorHelper() { PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); encryptor.setAlgorithm("PBEWithHmacSHA512AndAES_256"); encryptor.setIvGenerator(new RandomIvGenerator()); encryptor.setPoolSize(4); encryptor.setPassword(System.getenv("JASYPT_PWD")); props = new EncryptableProperties(encryptor); try { props.load(new FileInputStream("src/main/resources/configuration.properties")); } catch (IOException except) { log.error("Error loading configuration.properties", except); } } public String getProperty(final String key) { return props.getProperty(key); } }
datasource.usr=root datasource.pwd=ENC(xwg7loJbeovRKn4R710cjEKyTBfwQV/PNKgPHZdQoOQ1AQ9W/UwfjnhRyHUyuKdD)
Hasta aquí hemos podido ver como funcionaría con una aplicación normal. Pero también veremos como hacer uso del mismo en una aplicación de Spring Boot.
Ya tenemos los conocimientos básicos de Jasypt y como siempre hacer funcionar cualquier librería con Spring será algo sencillo. Aunque en este caso tendremos que hacer uso de una librería no desarrollada por la propia Spring sino por un usuario, Ulises Bocchio. Pero en cuatro sencillos pasos podremos tener nuestra aplicación de Spring con propiedades encriptadas.
Primero debemos incluir las dependencias correspondientes para Spring. Las cuales se pueden facilitar si nuestro proyecto tiene como padre a spring-boot-starter-parent. Pero aparte deberemos incluir la dependencia que acople Jasypt con Spring, y ha sido realizada por el desarrollador externo:
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot</artifactId> <version>3.0.3</version> </dependency>
La segunda parte será la de añadir la anotación @EnableEncryptableProperties a nuestra clase principal de configuración:
@SpringBootApplication @ComponentScan(basePackages = { "es.home.example.encrypt" }) @EnableEncryptableProperties public class Aplicacion { public static void main(final String[] args) { SpringApplication.run(Aplicacion.class, args); } }
La tercera parte será añadir la configuración de Jasypt en el fichero de configuración de Spring, application.properties. En ella podremos indicar el algoritmo de encriptación o la clase de generación IV. Aparte por supuesto de nuestras variables encriptadas:
jasypt.encryptor.algorithm=PBEWithHmacSHA512AndAES_256 jasypt.encryptor.iv-generator-classname=org.jasypt.iv.RandomIvGenerator spring.datasource.jdbc-url=jdbc:oracle:thin:@localhost:1521/XE spring.datasource.username=root spring.datasource.password=ENC(xwg7loJbeovRKn4R710cjEKyTBfwQV/PNKgPHZdQoOQ1AQ9W/UwfjnhRyHUyuKdD) spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
Por último las propiedades tal y como hicimos en el ejemplo anterior. Y podriamos obtener los valores del fichero configuration.properties de la siguiente forma:
@ConfigurationProperties(prefix = "datasource") @Configuration("configuration") @Data public class ConfigurationPropService { private String usr; private String pwd; }
O si los almacenamos en el application.properties, los podriamos obtener de la siguiente forma:
@Service @Getter public class AppPropertyService { @Value("${spring.datasource.username}") private String usr; @Value("${spring.datasource.password}") private String pwd; }
Para que funcionase, debemos pasar la clave maestra de dos diferentes de formas:
- A través de una variable de entorno denominada JASYPT_ENCRYPTOR_PASSWORD.
- A través de un argumento de la JVM o Maven denominado jasypt_encryptor_password
Esta misma solución la podemos adoptar para una aplicación de Apache Camel pero que este gestionada por Spring Boot.
Como vemos, es una rápida y sencilla solución para evitar tener nuestro código con contraseñas sensibles. Como único inconveniente puede ser el que nuestras propiedades no se encuentren sincronizadas y necesitemos de reiniciar nuestra aplicación si queremos cambiarlas. Pero para eso ya veremos que hay soluciones como Hashicorp Vault o Apache Zookeeper.
Por último si quieres probarlo, aquí tienes el código.
No hay comentarios:
Publicar un comentario