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:
1
<context:property-placeholder location="classpath:application.properties"></context:property-placeholder>
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:
1
2
3
4
5
6
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
 <property name="driverClassName" value="${db.driverClassName}"></property>
 <property name="url" value="${db.url}"></property>
 <property name="username" value="${db.username}"></property>
 <property name="password" value="${db.password}"></property>
</bean>
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:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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 <a href="http://stackoverflow.com/questions/1132567/encrypt-password-in-configuration-files-java">Encrypt Password in Configuration Files</a>
 */
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:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 <a href="http://romiawasthy.blogspot.com.br/2012/02/encryptdecrpt-properties-in-spring.html">Encrypt/Decrypt Properties using PropertyPlaceholderConfigurer</a>
 */
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:
1
2
3
<bean class="br.com.willianantunes.springtest.init.DecryptPropertyConfigurer">
 <property name="location" value="classpath:application.properties"></property>
</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