[JAVA] Decrypt the entire bean in a properties file with some values encrypted

Status

-** We are developing with JavaVM (6 or later) **, which is far from the latest. -In the property file, ** some item values such as "password" are encrypted with OpenSSL (Base64) **. -The target items to be decrypted may increase or decrease **. ・ I don't like to decrypt the items to be decrypted one by one **.

Purpose

I don't want to bother with decryption. I want to operate a bean as if it was defined in plain text from the beginning. In other words, if you do Dawn with Bean, you want to use it with Pan. (vocabulary)

Articles that I used as a reference

Achieve OpenSSL compatible encryption with Java / PHP

Java version

6 or later. This article conforms to 6 for the time being.

Library used

Since Base64 is used, use ApacheCommonsCodec.

Characters (things to prepare)

Property file

First of all, I want to convert with JAXB, so it is a property file in XML format.

Encrypted annotation

Custom annotation for beans.

PropertyBean class

A bean that contains the values in the properties file.

EncDecConverter class

As the title suggests, it is a class that will make you a pan if you do it with Dawn.

That's all the resources used in Java. Separately, prepare the master password used for encryption / decryption in your brain. The encryption method is "128-bit key AES CBC mode".

Practice

1. 1. Create a property file.

The decryption target is xxUser and xxPass. Place it in a directory that is in your classpath.

property.xml


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
	<!--Decryption target "xxUser"-->
	<!--A character string in which the plaintext "hogeyama" is encrypted with the password "yakisoba".-->
	<xxUser>U2FsdGVkX1/k+6dPcXrci4AbyQ0TNtytubkVFCxzcF4=</xxUser>
	<!--Decryption target "xxPass"-->
	<!--A character string in which the plaintext "hogeyamano password" is encrypted with the password "yakisoba".-->
	<xxPass>U2FsdGVkX19XLVe01kx2ahoKVKnSXLhBQ2aiRrdUlbjgtKu1IXD3EuYDSADab5vA</xxPass>
	<!--Plaintext-->
	<miscItem1>hoge</miscItem1>
	<!--Plaintext-->
	<miscItem2>fuga</miscItem2>
</root>

Create the actual encrypted character string with the openssl command (below) or the above reference URL.

bash


$echo [plaintext]| openssl enc -e -aes-128-cbc -base64 -k [Master password]
2. Create an Encrypted annotation.

Encrypted annotation


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Encrypted {}
3. 3. Create a PropertyBean.

At this time, add the Encrypted annotation to the field where the encrypted value is entered.

PropertyBean



public class PropertyBean {
	@Encrypted
	private String xxUser = null;
	@Encrypted
	private String xxPass= null;
	private String miscItem1 = null;
	private String miscItem2 = null;

	public String getXxUser() {
		return xxUser;
	}
	public void setXxUser(String xxUser) {
		this.xxUser = xxUser;
	}
	public String getXxPass() {
		return xxPass;
	}
	public void setXxPass(String xxPass) {
		this.xxPass = xxPass;
	}
	public String getMiscItem1() {
		return miscItem1;
	}
	public void setMiscItem1(String miscItem1) {
		this.miscItem1 = miscItem1;
	}
	public String getMiscItem2() {
		return miscItem2;
	}
	public void setMiscItem2(String miscItem2) {
		this.miscItem2 = miscItem2;
	}

}
4. The EncDecConverter class is as follows.

EncDecConverter class


import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;


public class EncDecConverter<T> {
	private T t = null;
	private String password = null;
	private Charset charset = null;

	@SuppressWarnings("unused")
	private EncDecConverter() {}

	public EncDecConverter(T t,String password){
	    this.t = t;
	    this.password = password;
	    charset = Charset.defaultCharset();
	}

	public EncDecConverter(T t,String password,String charsetName){
	    this.t = t;
	    this.password = password;
	    charset = Charset.forName(charsetName);
	}

	public boolean decrypt(){
		return convert(true);
	}

	public boolean encrypt(){
		return convert(false);
	}

	private boolean convert(boolean processDecrypt){
	    if(t == null || password == null){
	        return false;
	    }

	    Field[] fs = t.getClass().getDeclaredFields();
	    String value = "";
	    try {
		    for(Field f : fs){
		        f.setAccessible(true);
		        if(f.getAnnotation(Encrypted.class) != null){
		        	value = (processDecrypt?decrypt((String)f.get(t),password):encrypt((String)f.get(t),password));
		            f.set(t,removeLineSeparator(value));
		        }
		    }
	    }catch(Throwable e) {
	    	e.printStackTrace();
	    	return false;
	    }
	    return true;
	}

	private String removeLineSeparator(String s) {
		if(s == null) {
			return "";
		}
		return s.replaceAll("[\r]*[\n]*$", "");
	}

    private boolean getKeyAndGenerateIv(String password, byte[] salt, byte[] key_bytes, byte[] iv_bytes) {
        byte[] password_bytes = password.getBytes(charset);
        int length = password_bytes.length + salt.length;
        ByteBuffer byte_buffer = ByteBuffer.allocate(length);
        byte_buffer.put(password_bytes);
        byte_buffer.put(salt);
        byte_buffer.rewind();
        byte[] byte_array = new byte[length];
        byte_buffer.get(byte_array);
        try {
        	System.arraycopy(MessageDigest.getInstance("MD5").digest(byte_array), 0, key_bytes, 0, key_bytes.length);
        }catch (NoSuchAlgorithmException e ) {
        	e.printStackTrace();
        	return false;
        }
        length = password_bytes.length + salt.length + key_bytes.length;
        byte_buffer = ByteBuffer.allocate(length);
        byte_buffer.put(key_bytes);
        byte_buffer.put(password_bytes);
        byte_buffer.put(salt);
        byte_buffer.rewind();
        byte_array = new byte[length];
        byte_buffer.get(byte_array);
        try {
        	System.arraycopy(MessageDigest.getInstance("MD5").digest(byte_array), 0, iv_bytes, 0, iv_bytes.length);
        }catch (NoSuchAlgorithmException e ) {
        	e.printStackTrace();
        	return false;
        }

        return true;
    }

    private String encrypt(String plaintext, String password) throws Throwable{
        // Generate random salt.
        byte[] random_bytes = new byte[8];
        new SecureRandom().nextBytes(random_bytes);

        byte[] key_bytes = new byte[16];
        byte[] iv_bytes = new byte[16];
        getKeyAndGenerateIv(password, random_bytes, key_bytes, iv_bytes);

        SecretKey secret = new SecretKeySpec(key_bytes, "AES");
        IvParameterSpec ivspec = new IvParameterSpec(iv_bytes);
        Cipher cipher = null;
        try {
        	cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        }catch(NoSuchPaddingException e) {
        	throw e;
        }catch(NoSuchAlgorithmException e) {
        	throw e;
        }

        try {
        	cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
        }catch(InvalidKeyException e) {
        	throw e;
        }catch(InvalidAlgorithmParameterException e) {
        	throw e;
        }

        byte[] encrypted_bytes;
        try {
        	encrypted_bytes = cipher.doFinal(plaintext.getBytes(charset));
	    }catch(IllegalBlockSizeException e) {
	    	throw e;
	    }catch(BadPaddingException e) {
	    	throw e;
	    }

        final String header_string = "Salted__";
        byte[] header_bytes = header_string.getBytes(charset);
        int length = header_bytes.length + random_bytes.length + encrypted_bytes.length;
        ByteBuffer byte_buffer = ByteBuffer.allocate(length);
        byte_buffer.put(header_bytes);
        byte_buffer.put(random_bytes);
        byte_buffer.put(encrypted_bytes);
        byte_buffer.rewind();
        byte[] byte_array = new byte[length];
        byte_buffer.get(byte_array);

        return new String(Base64.encodeBase64(byte_array));
        //For Java 8 or later, it can be described as follows.
        //return new String(Base64.getEncoder().encodeToString(byte_array));
    }

    private String decrypt(String payload, String password) throws Throwable{
    	byte[] payload_bytes = Base64.decodeBase64(payload.getBytes(charset));
        //For Java 8 or later, it can be described as follows.
        //byte[] payload_bytes = Base64.getDecoder().decode(payload.getBytes(StandardCharsets.UTF_8));
        byte[] header_bytes = new byte[8];
        byte[] salt_bytes = new byte[8];
        int length = payload_bytes.length;
        ByteBuffer byte_buffer = ByteBuffer.allocate(length);
        byte_buffer.put(payload_bytes);
        byte_buffer.rewind();
        byte_buffer.get(header_bytes);
        byte_buffer.get(salt_bytes);
        length = payload_bytes.length - header_bytes.length - salt_bytes.length;
        byte[] data_bytes = new byte[length];
        byte_buffer.get(data_bytes);

        byte[] key_byte = new byte[16];
        byte[] iv_bytes = new byte[16];
        getKeyAndGenerateIv(password, salt_bytes, key_byte, iv_bytes);

        SecretKey secret = new SecretKeySpec(key_byte, "AES");
        IvParameterSpec ivspec = new IvParameterSpec(iv_bytes);
        Cipher cipher = null;
        try {
        	cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        }catch(NoSuchPaddingException e) {
        	throw e;
        }catch(NoSuchAlgorithmException e) {
        	throw e;
        }

        try {
        	cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
        }catch(InvalidKeyException e) {
        	throw e;
        }catch(InvalidAlgorithmParameterException e) {
        	throw e;
        }

        byte[] decrypted;
        try {
        	decrypted = cipher.doFinal(data_bytes);
        }catch(IllegalBlockSizeException e) {
        	throw e;
        }catch(BadPaddingException e) {
        	throw e;
        }

        return new String(decrypted);
    }
}
5. Create a test class and check the operation.

EncDecTest


import java.io.InputStream;

import javax.xml.bind.JAXB;

public class EncDecTest {
	public static void main(String[] args) {
		EncDecTest t = new EncDecTest();
		t.execute();
	}

	public void execute() {
		InputStream is = this.getClass().getClassLoader().getResourceAsStream("property.xml");
		PropertyBean prop = JAXB.unmarshal(is, PropertyBean.class);
		EncDecConverter<PropertyBean> c = new EncDecConverter<PropertyBean>(prop,"yakisoba","UTF-8");
		if(!c.decrypt()) {
			System.err.println("error.");
			System.exit(1);
		}
		System.out.println(prop.getXxUser()); //Output hogeyama
		System.out.println(prop.getXxPass()); //Output hogeyamano password
		System.out.println(prop.getMiscItem1()); //Output hoge
		System.out.println(prop.getMiscItem2()); //Output fuga
		System.exit(0);
	}
}

If the result of the decrypt method is true In the PropertyBean instance passed as an argument ** The field to be decrypted ** has been decrypted. All you have to do is get it from the bean and use it normally.

As mentioned above, as an argument to the EncDecConverter constructor You need to pass the master password in clear text, As a first step, ** How should the master password be managed and how should I / F be performed? ** ** Will not be mentioned in this article.

Summary

I think there is also a pattern in which OpenSSL encrypted character strings are partially used instead of "whole text". (Aside from whether it is the correct answer) So I tried to summarize such tips. Also, I don't think it's good to use reflection badly, but I use it because it's convenient.

Recommended Posts

Decrypt the entire bean in a properties file with some values encrypted
Create a jar file with the command
Read a string in a PDF file with Java
How to save a file with the specified extension under the directory specified in Java to the list
[Java] Get the file path in the folder with List
When I switched to IntelliJ, I got a lot of differences in the encoding of the properties file.
Put the file in the properties of string in spring xml configuration
Overwrite bean definition in spring xml configuration file with another xml
About the behavior when doing a file map with java
Find the number of days in a month with Kotlin
How to make a jar file with no dependencies in Maven
Read the file under the classpath as a character string with spring
Fitted in Spring Boot using a bean definition file named application.xml
Get the public URL of a private Flickr file in Java