HOWTO · Java

Encriptar palavra-passe em ficheiros de configuração em Java

Este artigo apresenta como criptografar senha em arquivos de configuração em Java

Criptografia é o processo de transformar informações de texto simples em uma forma ilegível usando um algoritmo de criptografia combinado com um parâmetro denominado chave de criptografia. O formato ilegível é geralmente conhecido como formato de ciphertext. Somente aqueles que possuem a chave de descriptografia podem descriptografar os dados e recuperar o texto original.

Podemos dividir o problema de criptografar senhas em arquivos de configuração em duas subtarefas a seguir.

  1. Criptografe a senha em texto simples que está no arquivo.
  2. Descriptografe a senha criptografada lida do arquivo.

Vamos primeiro criar um arquivo de configuração denominado arquivo config.properties no caminho src/conf/.

password = TestPassword123

Agora, para ler o arquivo de configuração, instancie a classe Properties. Podemos criar uma instância da classe FileInputStream usando seu construtor. Ele pega o caminho do arquivo de configuração como sua entrada. Agora, uma instância da classe de propriedades é usada para carregar as propriedades. Use o método load para carregar o arquivo de propriedades na classe, e isso leva a instância InputStreamReader como um parâmetro. Ele lança IllegalArgumentException se este fluxo de entrada contém uma sequência de escape Unicode malformada e IOException se um erro ocorreu durante a leitura do fluxo de entrada.

Depois que as propriedades forem carregadas com êxito, use o método getProperty() para pesquisar a propriedade com a chave especificada na lista de propriedades. O método retorna null se não for capaz de encontrar as propriedades. Faça uma verificação externa para lidar com essa situação e lance IllegalArgumentException se uma senha for considerada nula no arquivo.

salt é criado com qualquer string aleatória para adicionar à string da senha.

createSecretKey é um método definido pelo usuário que retorna a chave SecretKeySpec, e o uso da chave é para criptografar e descriptografar a senha. Os métodos encrypt e decrypt são métodos estáticos definidos pelo uso que foram fornecidos, na classe Encryption.

Abaixo está o código de exemplo que demonstra o mesmo.

package fileDataEncryption;

import static fileDataEncryption.Encryption.*;

import java.io.FileInputStream;
import java.util.Properties;
import javax.crypto.spec.SecretKeySpec;

public class ConfigFileEncryption {
  public static void main(String[] args) throws Exception {
    Properties properties = new Properties();
    FileInputStream inputStream = new FileInputStream("src/conf/config.properties");
    properties.load(inputStream);
    String password = properties.getProperty("password");

    if (password == null) {
      throw new IllegalArgumentException("No such parameter present in config file");
    }

    byte[] salt = new String("12345678").getBytes();
    int iterationCount = 40000;
    int keyLength = 128;
    SecretKeySpec key = createSecretKey(password.toCharArray(), salt, iterationCount, keyLength);

    String originalPassword = password;
    System.out.println("Original password: " + originalPassword);
    String encryptedPassword = encrypt(originalPassword, key);
    System.out.println("Encrypted password: " + encryptedPassword);
    String decryptedPassword = decrypt(encryptedPassword, key);
    System.out.println("Decrypted password: " + decryptedPassword);
  }
}

Uma descrição detalhada dos métodos definidos pelo usuário na classe Encryption é apresentada a seguir.

  1. O createSecretKey é uma função que recebe parâmetros como password, salt, iterationCount e keyLength. password é a senha real no arquivo de configuração. Na criptografia, um salt são dados aleatórios que usamos como uma entrada adicional que faz o hash dos dados, uma senha ou uma frase secreta. O uso de salts é para proteger as senhas no armazenamento. Usamos a variável iterationCount como o número de iterações que um algoritmo deve realizar. Diminuir o valor das velocidades variáveis ​​diminui o tempo de inicialização e, portanto, são úteis durante o teste, mas também torna mais fácil para atacantes de força bruta. A variável keyLength é o comprimento da chave que precisamos derivar. Lança a exceção lançada a partir dos métodos usados.
  2. O método getInstance percorre a lista de Provedores de segurança registrados, começando pelo Provedor preferido. Ele pega o nome padrão do algoritmo de chave secreta solicitado e retorna o novo objeto SecretKeyFactory. Ele lança NullPointerException se o algoritmo especificado for nulo e NoSuchAlgorithmException se nenhum Provedor suportar uma implementação SecretKeyFactorySpi para o algoritmo especificado.
  3. PBEKeySpec é um construtor de classe que pega uma senha, salt, contagem de iteração e comprimento de chave derivada para gerar PBEKey de cifras PBE de tamanho de chave variável. Ele lança NullPointerException se salt for null e IllegalArgumentException se sal estiver vazio.
  4. generateSecret gera um objeto SecretKey a partir da especificação-chave fornecida ou do material-chave. Leva a especificação da chave secreta. Ele lança InvalidKeySpecException se a especificação fornecida for inadequada para esta fábrica de chave secreta para produzir um valor de chave classificado.

Detalhes do método encrypt na classe Encryption.

  1. O método encrypt leva dois parâmetros, dados a serem criptografados e a chave. Este método lança exceções lançadas de métodos filhos nele.
  2. O método getInstance percorre a lista de Provedores de segurança registrados, começando pelo Provedor preferido. Leva o nome da transformação, que é AES / CBC / PKCS5Padding. Ele lança NoSuchAlgorithmException se uma alteração for nula, vazia, em um formato inválido e NoSuchPaddingException se a alteração contiver um esquema de preenchimento não disponível.
  3. O método init inicializa o Cipher para uma das seguintes quatro operações: criptografia, descriptografia, quebra de chave ou desempacotamento de chave, dependendo do valor do modo de operação. ENCRYPT_MODE no nosso caso. O método lança UnsupportedOperationException se o modo de operação for inválido e InvalidKeyException se a chave fornecida for inadequada.
  4. O getParameters retorna os parâmetros usados ​​com esta cifra.
  5. O getParameterSpec retorna uma especificação do objeto de parâmetro. O parâmetro paramSpec identifica a classe de especificação na qual os parâmetros devem retornar. Por exemplo, poderia ser DSAParameterSpec.class para indicar que os parâmetros devem retornar em uma instância da classe DSAParameterSpec.
  6. O método doFinal criptografa ou descriptografa dados em um trabalho de parte única ou conclui uma operação de várias partes. Os dados são criptografados ou descriptografados, dependendo de como inicializamos a cifra.
  7. base64Encode é um método privado que codifica a matriz de bytes especificada em uma string usando o esquema de codificação Base64. As funções usadas no método descriptografar são semelhantes ao método mencionado acima. A única diferença é que eles se comportam de maneira diferente com base no mode especificado na função, DECRYPT_MODE como um modo de operação.
package fileDataEncryption;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryption {
  public static SecretKeySpec createSecretKey(char[] password, byte[] salt, int iterationCount,
      int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
    SecretKey keyTmp = keyFactory.generateSecret(keySpec);
    return new SecretKeySpec(keyTmp.getEncoded(), "AES");
  }

  public static String encrypt(String dataToEncrypt, SecretKeySpec key)
      throws GeneralSecurityException, UnsupportedEncodingException {
    Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    pbeCipher.init(Cipher.ENCRYPT_MODE, key);
    AlgorithmParameters parameters = pbeCipher.getParameters();
    IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class);
    byte[] cryptoText = pbeCipher.doFinal(dataToEncrypt.getBytes("UTF-8"));
    byte[] iv = ivParameterSpec.getIV();
    return base64Encode(iv) + ":" + base64Encode(cryptoText);
  }

  private static String base64Encode(byte[] bytes) {
    return Base64.getEncoder().encodeToString(bytes);
  }

  public static String decrypt(String string, SecretKeySpec key)
      throws GeneralSecurityException, IOException {
    String iv = string.split(":")[0];
    String property = string.split(":")[1];
    Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(base64Decode(iv)));
    return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
  }

  private static byte[] base64Decode(String property) throws IOException {
    return Base64.getDecoder().decode(property);
  }
}

Abaixo está a saída do código escrito para criptografar e descriptografar a senha no arquivo de configuração.

Original password: TestPassword123
Encrypted password: Hy7fbIwpyKgp0oileu+oLg==:WNRknMJz/8u8GmWlCZFPFA==
Decrypted password: TestPassword123