Encrypt Password in Configuration Files in Java

Encryption is the process of transforming plaintext information into an unreadable form using an encryption algorithm combined with a parameter called encryption key. The unreadable format is often known as the ciphertext format. Only those who possess the decryption key can decrypt the data and recover the original plaintext.

We can break the problem of encrypting passwords in configuration files into two following sub-tasks.

  1. Encrypt the plaintext password that is there in the file.
  2. Decrypt the encrypted password read in from the file.

Let us first make a configuration file named config.properties file at the src/conf/ path.

password=TestPassword123

Now to read the configuration file, instantiate the Properties class. We can create an instance of the FileInputStream class using its constructor. It takes the path of the configuration file as its input. Now an instance of properties class is used to load the properties. Use the load method for loading the properties file in the class, and this takes the InputStreamReader instance as a parameter. It throws IllegalArgumentExceptionif this input stream contains a malformed Unicode escape sequence and IOException if an error occurred when reading from the input stream.

Once the properties are successfully loaded, use the getProperty() method to search for the property with the specified key in the property list. The method returns null if it is unable to find the properties. Place an external check to handle such a situation and throw IllegalArgumentException if a password is found null from the file.

salt is created with any random String to add to the password string.

createSecretKey is a user-defined method that returns the SecretKeySpec key, and the use of the key is to encrypt and decrypt the password. encrypt and decrypt methods are used-defined static methods that have been given, in the Encryption class.

Below is the sample code that demonstrates the same.

package fileDataEncryption;

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

import static fileDataEncryption.Encryption.*;

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);
    }
}

A detailed description of user-defined methods in the Encryption class is as below.

  1. The createSecretKey is a function that takes parameters like password, salt, iteration count, and key length. password is the actual password in the configuration file. In cryptography, a salt is random data that we use as an additional input that hashes data, a password, or a passphrase. The use of salts is to safeguard passwords in storage. We useiterationCount variable as the number of iterations that an Algorithm should take. Decreasing the value of the variable speeds down the start-up time and hence are helpful during testing, but it also makes it easier for brute force attackers. The keyLength variable is the length of the key that we ultimately need to derive. Throws the exception thrown from the methods used it.
  2. The getInstance method traverses the list of registered security Providers, starting with the most preferred Provider. It takes the standard name of the requested secret-key algorithm and returns the new SecretKeyFactory object. It throws NullPointerException if the specified algorithm is null and NoSuchAlgorithmException if no Provider supports a SecretKeyFactorySpi implementation for the specified algorithm.
  3. PBEKeySpec is a class constructor that takes a password, salt, iteration count, and to-be-derived key length for generating PBEKey of variable-key-size PBE ciphers. It throws NullPointerException if salt is null and IllegalArgumentException if salt is empty.
  4. generateSecret generates a SecretKey object from the provided key-specification or the key-material. It takes the specification of the secret key. It throws InvalidKeySpecException if the given specification is inappropriate for this secret-key factory to produce a classified key value.

Details of encrypt method in Encryption class.

  1. The encrypt method takes two parameters, data to be encrypted and the key. This method throws exceptions thrown from child methods in it.
  2. The getInstance method traverses the list of registered security Providers, starting with the most preferred Provider. It takes the name of the transformation, that is AES/CBC/PKCS5Padding. It throws NoSuchAlgorithmException if a change is null, empty, in an invalid format and NoSuchPaddingException if change contains a padding scheme that is not available.
  3. init method initializes the Cipher for one of the following four operations: encryption, decryption, key wrapping, or key unwrapping, depending on the operation mode value. ENCRYPT_MODE in our case. The method throws UnsupportedOperationException if the operation mode is invalid and InvalidKeyException if the given key is inappropriate.
  4. The getParameters returns the parameters used with this cipher.
  5. The getParameterSpec returns a specification of the parameter object. TheparamSpec parameter identifies the specification class in which the parameters must return. For example, it could be the DSAParameterSpec.class to indicate that the parameters must return in an instance of the DSAParameterSpec class.
  6. doFinal method encrypts or decrypts data in a single-part working or finishes a multiple-part operation. The data is encrypted or decrypted, depending on how we initialize the cipher.
  7. base64Encode is a private method that encodes the specified byte array into a string using the Base64 encoding scheme. The functions used in the decrypt method are similar to the above mention method. The only difference is they behave differently based on the mode specified in the function, DECRYPT_MODE as an operation mode.
package fileDataEncryption;

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;
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;

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);
    }
}

Below is the output of the code written to encrypt and decrypt the password in the configuration file.

Original password: TestPassword123
Encrypted password: Hy7fbIwpyKgp0oileu+oLg==:WNRknMJz/8u8GmWlCZFPFA==
Decrypted password: TestPassword123
Contribute
DelftStack is a collective effort contributed by software geeks like you. If you like the article and would like to contribute to DelftStack by writing paid articles, you can check the write for us page.