feat(security): add support for message encryption with a key
This commit is contained in:
parent
c541119cf0
commit
d417a46a06
@ -1,5 +1,7 @@
|
||||
SPRING_PROFILES_ACTIVE=dev
|
||||
|
||||
APP_ENCRYPTION_SECRET=123456789
|
||||
|
||||
DB_NAME=EXAMPLE_DB
|
||||
DB_USER=EXAMPLE
|
||||
DB_PASSWORD=SECRET
|
||||
|
@ -1,6 +1,9 @@
|
||||
info:
|
||||
app:
|
||||
version: @project.version@
|
||||
app:
|
||||
encryption:
|
||||
secret: ${APP_ENCRYPTION_SECRET}
|
||||
spring:
|
||||
application:
|
||||
name: restemailbridge
|
||||
@ -26,6 +29,7 @@ springdoc:
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui
|
||||
show-actuator: true
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
|
@ -13,6 +13,7 @@ services:
|
||||
DB_PORT: ${DB_PORT}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
APP_ENCRYPTION_SECRET: ${APP_ENCRYPTION_SECRET}
|
||||
GMAIL_OAUTH_CLIENT_ID: ${GMAIL_OAUTH_CLIENT_ID}
|
||||
GMAIL_OAUTH_CLIENT_SECRET: ${GMAIL_OAUTH_CLIENT_SECRET}
|
||||
networks:
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.pablotj.restemailbridge.infrastructure.persistence;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
@Converter
|
||||
public class EncryptionConverter implements AttributeConverter<String, String> {
|
||||
|
||||
|
||||
@Value("${app.encryption.secret}")
|
||||
private String secret;
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(String attribute) {
|
||||
return attribute == null ? null : new EncryptionUtils(secret).encrypt(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToEntityAttribute(String dbData) {
|
||||
return dbData == null ? null : new EncryptionUtils(secret).decrypt(dbData);
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.pablotj.restemailbridge.infrastructure.persistence;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class EncryptionUtils {
|
||||
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
||||
private static final int TAG_LENGTH_BIT = 128;
|
||||
private static final int IV_LENGTH_BYTE = 12;
|
||||
private static final SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
private final SecretKey secretKey;
|
||||
|
||||
public EncryptionUtils(String secret) {
|
||||
if (secret == null || secret.getBytes(StandardCharsets.UTF_8).length != 32) {
|
||||
throw new IllegalArgumentException("Secret key must be 32 bytes for AES-256");
|
||||
}
|
||||
this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), ALGORITHM);
|
||||
}
|
||||
|
||||
public String encrypt(String plainText) {
|
||||
try {
|
||||
byte[] iv = new byte[IV_LENGTH_BYTE];
|
||||
secureRandom.nextBytes(iv);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
|
||||
|
||||
byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Guardamos IV + ciphertext juntos
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
|
||||
byteBuffer.put(iv);
|
||||
byteBuffer.put(cipherText);
|
||||
|
||||
return Base64.getEncoder().encodeToString(byteBuffer.array());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encrypt text", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String decrypt(String base64CipherText) {
|
||||
try {
|
||||
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherText);
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
|
||||
|
||||
byte[] iv = new byte[IV_LENGTH_BYTE];
|
||||
byteBuffer.get(iv);
|
||||
|
||||
byte[] cipherText = new byte[byteBuffer.remaining()];
|
||||
byteBuffer.get(cipherText);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
|
||||
|
||||
byte[] plainText = cipher.doFinal(cipherText);
|
||||
return new String(plainText, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to decrypt text", e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user