Criptografar/Descriptografar arquivos de propriedades utilizando o Spring

Geralmente, quando temos que referenciar um arquivo de propriedades, a maneira mais simples de se fazer é inserindo a seguinte tag no arquivo de contexto do Spring:

Dessa maneira podemos usar as propriedades contidas no arquivo application.properties para parametrizar atributos de configuração de algum bean, por exemplo. Podemos ver uma situação para o Data Source abaixo:
 
 
 
 
 

Para alterarmos as propriedades, entre outras coisas dela, o Spring fornece uma classe chamada PropertyPlaceHolderConfigurer. Ela permite alterarmos o comportamento padrão do property place holder em função de alguns métodos que estão como protected nela. Para o nosso caso, iremos criar uma classe que extenderá de PropertyPlaceholderConfigurer sobre escrevendo o método convertProperties. Porém, antes de criarmos ela, é importante termos uma outra classe que forneça serviços de criptografia e descriptografia em função de um password e de um algoritmo de cifragem, a fim de realizar a cifra com o combo password, valor da propriedade e do algoritmo, que para o caso desse tutorial será o PBEWithMD5AndDES, podendo ser alterado facilmente posteriormente. Segue um exemplo de implementação da classe:
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

/**
 * It's necessary to set the static attribute char[] PASSWORD. It will be use to encrypt and decrypt
 * a text. This basically means initialing a javax.crypto.Cipher with algorithm 
 * "PBEWithMD5AndDES" and getting a key from javax.crypto.SecretKeyFactory 
 * with the same algorithm.
 * @author Willian Antunes
 * @version 1.0.0
 * @see Encrypt Password in Configuration Files
 */
public class ProtectedConfigFile 
{
    private static final char[] PASSWORD = "ColocarSeuPasswordAqui".toCharArray();
    private static final byte[] SALT = {
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
    };
    
    private static final Logger logger = LogManager.getLogger(ProtectedConfigFile.class);

    public static String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException 
    {
     logger.debug("Calling method encrypt(String property)...");
     
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
        return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8")));
    }

    public static String decrypt(String property) throws GeneralSecurityException, IOException 
    {
     logger.debug("Calling method decrypt(String property)...");
     
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
    }
    
    private static String base64Encode(byte[] bytes) 
    {
     logger.debug("Calling method base64Encode(byte[] bytes)...");
     
     return Encryption.base64Encode(bytes);
    }
    
    private static byte[] base64Decode(String property) throws IOException 
    {
     logger.debug("Calling method base64Decode(String property)...");
     
        return Encryption.base64Decode(property);
    } 
}
Caso executássemos para criptografar o valor AtéQuando com o password ColocarSeuPasswordAqui, o valor retornado pelo método encrypt() seria Smzs1uObZlx21DsoCPaLoQ==. Beleza! Se no arquivo properties constava:

db.password = AtéQuando

Agora deve ficar como:

db.password = Smzs1uObZlx21DsoCPaLoQ==

Por fim, a classe com o override do método convertProperties:
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Enumeration;
import java.util.Properties;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.util.ObjectUtils;

import br.com.willianantunes.util.security.ProtectedConfigFile;

/**
 * Custom property place holder whose purpose is to convert a encrypted property to its normal value.
 * @author Willian Antunes
 * @version 1.0.0
 * @see Encrypt/Decrypt Properties using PropertyPlaceholderConfigurer
 */
public class DecryptPropertyConfigurer extends PropertyPlaceholderConfigurer 
{
 private final Logger logger = LogManager.getLogger(DecryptPropertyConfigurer.class);
 
 @Override
 protected void convertProperties(Properties props)
 {
  logger.debug("Calling method convertProperties(Properties propsy)...");
  
  Enumeration propertyNames = props.propertyNames();
  while (propertyNames.hasMoreElements()) 
  {
   String propertyName = (String) propertyNames.nextElement();
   String propertyValue = props.getProperty(propertyName);
 
   String convertedValue;
   
   try 
   {
    convertedValue = ProtectedConfigFile.decrypt(propertyValue);
    if(!ObjectUtils.nullSafeEquals(propertyValue, convertedValue))
    {
     logger.info(String.format("The following property was converted: %s", propertyName));
     props.setProperty(propertyName, convertedValue);
    }
   } 
   catch (GeneralSecurityException e) 
   {
    logger.debug(e);
   } 
   catch (IOException e) 
   {
    logger.debug(e);
   }
  }
 }
}
Para finalizar, iremos substituir a entrada padrão do property-placeholder (conforme exemplo no começo da postagem) pelo bean:

 

Caso esteja configurado corretamente o log4j, a seguinte mensagem aparecerá no console:

INFO DecryptPropertyConfigurer - The following property was converted: db.password

Aqlbras! Ao som de Por Tudo que for, Lobão.

Comentários