Cifrado y descifrado como servicio con Vault

Escrito por picodotdev el .
gnu-linux planeta-codigo
Enlace permanente Comentarios

Implementar la seguridad en una aplicación no es sencillo, cuando un sistema se compone de múltiples aplicaciones los posibles fallos de seguridad se multiplican. Vault es una herramienta que permite centralizar y delegar varios aspectos de las aplicaciones relativos a la seguridad, uno de ellos es el cifrado y descifrado de los datos para su almacenamiento y recuperación de una base de datos. Entre sus funcionalidades Vault ofrece como servicio el cifrado y descifrado de datos.

La seguridad en una aplicación involucra a los datos, tanto en la transmisión entre aplicaciones como en el almacenamiento a ambos se les conoce por la seguridad de los datos en tránsito o transit y por la seguridad en persistencia o rest. La seguridad de los datos en tránsito se consigue utilizando protocolos de comunicación seguros como TLS y la seguridad de los datos en persistencia se consigue cifrando los datos ya sea a nivel de sistema de archivos, base de datos o datos específicos aplicando un algoritmo de cifrado.

No es fácil implementar la seguridad, por un lado hay que utilizar algoritmos de cifrado considerados seguros, en un sistema grande en el que hay múltiples aplicaciones que utilizan potencialmente diferentes lenguajes de programación y librerías han de tener soporte para esos algoritmos de cifrado. Por otro lado, al tener múltiples aplicaciones requiere que cada una de ellas mantenga seguras las claves privadas en las que se fundamenta la seguridad de cifrado y descifrado, con múltiples aplicaciones los posibles puntos vulnerables son varios.

La seguridad de los datos es muy importante, ciertos datos personales sensibles y que permiten identificar a personas están protegidos por leyes. El no cumplimiento de las leyes implica potencialmente a una empresa recibir importantes multas o descrédito que afecte a la viabilidad del negocio o suponga una reducción de facturación. Algunos datos candidatos a ser cifrados o transformados al guardarse en base de datos son, datos personales e identificativos como nombre y apellidos, DNI, dirección, tarjetas de crédito, bancarios u otros datos que estén regulados por las leyes de protección de datos.

Los datos protegidos incluso no es deseable que sean accesibles por cualesquiera trabajadores de la propia empresa, solo debería tener acceso a ellos aquellos trabajadores que los necesitan para desempeñar su trabajo y ofrecer el servicio que proporciona la empresa. Para el desarrollo de una aplicación los programadores necesitan una base de datos con el mismo esquema de la base de datos de producción y un conjunto de datos, una opción es obtener una copia de la base de datos de producción, sin embargo, obtener una copia de la base de datos de producción otorga acceso a los programadores acceso a los datos, al hacer la copia es posible aplicar un proceso que ofusque los datos sin embargo esto sigue sin solventar el problema de mantener seguros los datos en la propia base de datos de producción o sus réplicas. Si algunos datos se guardan cifrados aunque se tenga acceso a la base de datos los datos cifrados siguen protegidos.

Una de las funcionalidades que ofrece Vault de HashiCorp es ofrecer el cifrado y descifrado como servicio. Este artículo está basado en la guía de Vault sobre la funcionalidad de cifrado y descifrado.

Vault como servicio de cifrado y descifrado

Vault es una herramienta dedicada a la seguridad de la empresa HashiCorp. Tiene diferentes funcionalidades como servir de almacenamiento de secretos en su base de datos de claves y valores, generar credenciales de acceso bajo demanda a recursos como bases de datos entre otras como cifrado y descifrado como servicio. En todas estás funcionalidades diversos aspectos de la seguridad se centralizan en un único componente del sistema.

Las claves de cifrado únicamente se almacenan en Vault, por otro lado las aplicaciones no han de mantener credenciales para el acceso a una base de datos sino que es Vault el que crea las credenciales válidas únicamente por un periodo de tiempo corto con posibilidad de renovación. Esto aumenta la seguridad ya que una aplicación no ha de mantener unas credenciales para la base de datos válidas por un tiempo indefinido, al mismo tiempo las claves de cifrado están centralizadas en vez estar incluidas en cada aplicación. En caso necesario Vault es capaz de revocar las credenciales de cualquier aplicación.

Vault ofrece dos servicios para proteger los datos, el servicio de cifrado y descifrado está disponible en Vault y el de transformación requiere la versión Enterprise.

Servicio de cifrado y descifrado

El servicio de cifrado y descifrado de Vault consisten simplemente en aplicar un algoritmo de cifrado a un dato en texto plano y devolverlo cifrado y realizar la operación contraria aplicar el algoritmo de descifrado a un dato cifrado y devolverlo en texto plano. Además de mantener las claves de cifrado con la posibilidad rotarlas, es decir, crear nuevas claves.

El proceso de cifrado de Vault transforma el dato original en un valor que no tiene ningún sentido sin aplicar el proceso de descifrado. El formato del dato original se pierde, esto es, si el dato original es un número de teléfono con el formato (+34) 666554433 el dato cifrado es una secuencia de caracteres de cierta longitud con otro formato. Esta pérdida de formato es un inconveniente al guardar el dato en la base de datos para solventarlo Vault ofrece el servicio de transformación.

Uso del servicio de cifrado y descifrado de Vault

Servicio de transformación

En vez de cifrado Vault también ofrece un servicio de transformación que permite obtener un dato ofuscado pero que conserva el mismo formato y longitud que el original. Que el dato tenga el formato original es importante en una base de datos relacional ya que la longitud y formato de la columna para guardarlo será el mismo que el original, en el caso de un dato cifrado el campo se ha ade adaptar al resultado cifrado lo que no es deseable.

1
2
$ vault secrets enable transform
...
vault-transform.sh

En este ejemplo se codifica y descodifica un número de tarjeta de crédito conservando el formato.

1
2
3
4
$ vault write transform/encode/payments value=1111-2222-3333-4444
Key              Value
---              -----
encoded_value    9300-3376-4943-8903
vault-transform-encode.sh
1
2
3
4
$ vault write transform/decode/payments value=9300-3376-4943-8903
Key              Value
---              -----
decoded_value    1111-2222-3333-4444
vault-transform-decode.sh

Proveedor de claves

En un caso de uso en el que es necesario cifrar volúmenes de datos grandes, como blobs de 1 GB, requiere codificar en base64 y enviar a Vault por red y obtener la respuesta de tal volumen de datos, esto no es deseable para obtener el mejor rendimiento. En vez de enviar los datos se pueden cifrar los datos localmente con la clave obtenida de Vault. La idea es permitir a la aplicación cifrar y descifrar los datos sin necesidad de llamadas y retornos a Vault con grandes volúmenes de datos.

La respuesta para obtener la clave de cifrado contiene la clave de datos tanto en texto plano como en forma cifrada. Con la clave de datos en texto plano se pueden cifrar los datos y almacenar la clave de datos cifrada junto a los datos. Al necesitar descifrar los datos se solicita a Vault descifrar la clave de datos cifrada para obtener la clave de datos en texto plano permitiendo de esta forma descifrar los datos localmente. Esto es, una vez que el blob está cifrado no es necesario almacenar la clave de datos, solo se necesita almacenar la versión cifrada de la misma.

Esta idea permite cifrar y descifrar grandes volúmenes de datos a la aplicación sin realizar comunicaciones de red costosas con Vault. En este caso Vault no proporciona el servicio de cifrado y descifrado sino que lo hace la aplicación, sin embargo, Vault administra la gestión de las claves usadas por la aplicación que no ha de mantener ninguna clave privada.

Ejemplo de cifrado y descifrado de datos

Vault dispone tres métodos de acceso a sus funcionalidades entre ellas el servicio de cifrado y descifrado. Los tres métodos son mediante línea de comandos, mediante API REST o mediante la consola web de administración. En este ejemplo solo se muestra la versión de línea de comandos, la opción mediante API REST es posible probarla mediante una herramienta de linea de comandos como curl.

El primer paso es iniciar Vault, en este caso por simplicidad en modo desarrollo y habilitar el transit engine que proporciona el servicio de cifrado y descifrado.

1
2
$ vault server -dev
$ export VAULT_ADDR='http://127.0.0.1:8200'
vault-start.sh
1
2
$ vault secrets enable transit

vault-enable-transit.sh

El siguiente paso es obtener una clave, Vault la devuelve en texto plano o plaintext y cifrada o ciphertext.

1
2
$ vault write -f transit/keys/app

vault-create-key.sh
1
2
3
4
5
6
$ vault write -f transit/datakey/plaintext/app
Key            Value                                                                                                                                           │
---            -----                                                                                                                                           │
ciphertext     vault:v1:l5Y2HZn+LLq6O5ttSlquXo+x2OMzNH/7ReLpgi47DOWeIGUXmdxHBkk0OtqJe4hqmpyz5QCSx99kwyZu                                                       │
key_version    1                                                                                                                                               │
plaintext      k0n6o+vBAy0g2IFGqoRi5G4t1pMypljY9G4+wWewrEg=
vault-key.sh

Una vez creada la clave, se solicita a Vault cifrar y descifrar datos. Los datos han de proporcionase en codificados en base64.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ vault write transit/encrypt/app plaintext=$(base64 <<< "4111 1111 1111 1111")
Key            Value
---            -----
ciphertext     vault:v1:kYVkH1OxTEai1zjO+uQ9FKiHanlbaQ2bF5b5GYwUiEef5d31ProquZ5grVJfDWrc
key_version    1

$ vault write transit/decrypt/app ciphertext="vault:v1:kYVkH1OxTEai1zjO+uQ9FKiHanlbaQ2bF5b5GYwUiEef5d31ProquZ5grVJfDWrc"
Key          Value
---          -----
plaintext    NDExMSAxMTExIDExMTEgMTExMQo=

$ base64 --decode <<< "NDExMSAxMTExIDExMTEgMTExMQo="
4111 1111 1111 1111
vault-encrypt-decrypt.sh

Algunas aplicaciones para aumentar la seguridad y evitar usar una única clave que en el tiempo quede comprometida Vault proporciona la opción de generar una nueva versión de la misma, la clave antigua sigue siendo válida pero los datos serán cifrados con la última versión. Una vez todos los datos hayan sido cifrados con una versión más reciente las versiones antiguas se pueden deshabilitar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ vault write -f transit/keys/app/rotate
$ vault write transit/encrypt/app plaintext=$(base64 <<< "4111 1111 1111 1111")
Key            Value
---            -----
ciphertext     vault:v2:aVuvvlcyPX5J4sPYXFWHL53sIDx3HP9oBBTNjhY6NyshliMZzw8g8Ir9+BRpI8FJ
key_version    2

$ vault write transit/encrypt/app plaintext=$(base64 <<< "4111 1111 1111 1111")
Key            Value
---            -----
ciphertext     vault:v2:8coGwMB2WZsWb8Ogm4Fi8zGgzJq45V+VgYXYaMLHoVSCv9IJXs7Js6Jp5bqDGTUV
key_version    2

$ vault write transit/decrypt/app ciphertext="vault:v2:8coGwMB2WZsWb8Ogm4Fi8zGgzJq45V+VgYXYaMLHoVSCv9IJXs7Js6Jp5bqDGTUV"
Key          Value
---          -----
plaintext    NDExMSAxMTExIDExMTEgMTExMQo=

$ base64 --decode <<< "NDExMSAxMTExIDExMTEgMTExMQo="
4111 1111 1111 1111
vault-rotate-key.sh

Ejemplo aplicación con Spring

Spring proporciona clases de soporte para el acceso al servicio de Vault. Tanto para la configuración de acceso a Vault como para usar sus servicios mediante una API de clases Java sin tener que recurrir a la API REST de Vault directamente. La clase VaultOperations contiene las referencias de clases para el acceso a las API de Vault, para el caso de el servicio de cifrado y descifrado con la clase VaultTransitOperations.

Uso del servicio de cifrado y descifrado de Vault en una aplicación Java

Para usar el servicio de cifrado y descifrado en una aplicación de Spring, Vault permite la autenticación mediante el mecanismo AppRole. AppRole es un método de autenticación destinadas a las aplicaciones, básicamente proporciona unas credenciales como un usuario y contraseña. El policy se asocia con las credenciales de la aplicación para permitirle el acceso a la clave de cifrado y descifrado app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ vault auth enable approle

$ vault policy write app -<<EOF
path "transit/encrypt/app" {
   capabilities = [ "update" ]
}
path "transit/decrypt/app" {
   capabilities = [ "update" ]
}
EOF

$ vault write auth/approle/role/app \
    secret_id_ttl=10m \
    token_num_uses=10 \
    token_ttl=20m \
    token_max_ttl=30m \
    secret_id_num_uses=40 \
    policies=app
app-vault-approle.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ vault read auth/approle/role/app/role-id
Key        Value
---        -----
role_id    c0b643d1-9507-dae1-ffbd-6e405e082f1d

$ vault write -f auth/approle/role/app/secret-id
Key                   Value
---                   -----
secret_id             9b1b6fe6-0ee5-4182-c08d-245d20a59351
secret_id_accessor    b9f5af8e-29ee-694c-2751-6f65d5361caf
app-vault-role.sh

Credo el rol app para la aplicación las credenciales formadas por el role_id y secret_id ponen en el archivo de configuración de la aplicación de Spring.

1
2
3
4
5
6
7
8
spring.cloud.vault:
  uri: http://127.0.0.1:8200
  authentication: APPROLE
  app-role:
    role-id: c0b643d1-9507-dae1-ffbd-6e405e082f1d
    secret-id: 9b1b6fe6-0ee5-4182-c08d-245d20a59351
    role: app
    app-role-path: approle
application.yml

El cifrado y descifrado en la aplicación consiste simplemente en hacer uso de la API que proporciona Spring para el acceso a Vault, esta API hace transparente las llamadas REST subyacentes que se hacen a Vault.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.github.picodotdev.blogbitix.springcloudvaultcipher;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.vault.core.VaultOperations;

@SpringBootApplication
public class Main implements CommandLineRunner {

    @Autowired
    private VaultOperations vault;

    @Override
    public void run(String... args) {
        String plaintext = "Hello World!";
        String encrypted = vault.opsForTransit().encrypt("app", plaintext);
        String decrypted = vault.opsForTransit().decrypt("app", encrypted);

        System.out.println("Plaintext: " + plaintext);
        System.out.println("Encrypted: " + encrypted);
        System.out.println("Decrypted: " + decrypted);
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Main.class, args);
    }
}
Main.java

El resultado de cifrar y descifrar el dato se muestra como salida en la consola.

1
2
3
Plaintext: Hello World!
Encrypted: vault:v2:arPaLH7cRy221vCTZvNd8csswtmzOc42caVjBQ+T32SW7+x1tLEH0Q==
Decrypted: Hello World!
System.out

En el archivo de dependencias de la aplicación se ha de incluir la que proporciona Spring para añadir el soporte a Vault.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
plugins {
    id 'java'
    id 'application'
}

mainClassName = 'io.github.picodotdev.blogbitix.springcloudvaultcipher.Main'

repositories {
    mavenCentral()
}

configurations {
	all {
		exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
	}
}

dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:2.4.3')
    implementation platform('org.springframework.cloud:spring-cloud-dependencies:2020.0.1')

    implementation('org.springframework.boot:spring-boot-starter')
    implementation('org.springframework.boot:spring-boot-starter-log4j2')
    implementation('org.springframework.cloud:spring-cloud-starter-config')
    implementation('org.springframework.cloud:spring-cloud-starter-vault-config')

    runtimeOnly 'com.fasterxml.jackson.core:jackson-databind:2.12.1'
    runtimeOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.12.1'
}
build.gradle

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

Comparte el artículo: