-** 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 **.
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)
Achieve OpenSSL compatible encryption with Java / PHP
6 or later. This article conforms to 6 for the time being.
Since Base64 is used, use ApacheCommonsCodec.
First of all, I want to convert with JAXB, so it is a property file in XML format.
Custom annotation for beans.
A bean that contains the values in the properties file.
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".
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]
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 {}
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;
}
}
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);
}
}
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.
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