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

Rashmi Patidar 12 outubro 2023
Encriptar palavra-passe em ficheiros 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
Rashmi Patidar avatar Rashmi Patidar avatar

Rashmi is a professional Software Developer with hands on over varied tech stack. She has been working on Java, Springboot, Microservices, Typescript, MySQL, Graphql and more. She loves to spread knowledge via her writings. She is keen taking up new things and adopt in her career.

LinkedIn