feat(security): add support for message encryption with a key
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user