[JAVA] Décrypter le bean entier dans un fichier de propriétés avec certaines valeurs cryptées

Statut

・ ** Nous développons avec JavaVM (6 ou version ultérieure) **, ce qui est loin d'être le dernier. -Dans le fichier de propriétés, ** certaines valeurs d'éléments telles que "mot de passe" sont chiffrées avec OpenSSL (Base64) **. -Les éléments cibles à déchiffrer peuvent augmenter ou diminuer **. -Je n'aime pas déchiffrer les éléments à déchiffrer un par un **.

Objectif

Je ne veux pas m'embêter avec le décryptage. Je veux faire fonctionner un bean comme s'il était défini depuis le début en texte brut. En d'autres termes, si vous faites Dawn avec Bean, vous voulez l'utiliser avec Pan. (vocabulaire)

Articles que j'ai utilisés comme référence

Obtenir un chiffrement compatible OpenSSL avec Java / PHP

Version Java

6 ou version ultérieure. Cet article est conforme à 6 pour le moment.

Bibliothèque utilisée

Puisque nous utilisons Base64, nous utiliserons ApacheCommonsCodec.

Personnages (choses à préparer)

Dossier de propriété

Tout d'abord, je veux convertir avec JAXB, c'est donc un fichier de propriétés au format XML.

Annotation chiffrée

Annotations personnalisées pour les beans.

PropertyBean classe

Un bean qui contient les valeurs du fichier de propriétés.

Classe EncDecConverter

Comme le titre l'indique, c'est une classe qui fera de vous une casserole si vous le faites avec Dawn.

Ce sont toutes les ressources utilisées par Java. Séparément, préparez le mot de passe principal utilisé pour le cryptage / décryptage dans votre cerveau. La méthode de cryptage est le "mode CBC AES à clé 128 bits".

Entraine toi

1. 1. Créez un fichier de propriétés.

La cible de déchiffrement est «xxUser» et «xxPass». Placez-le dans un répertoire qui a un chemin de classe.

property.xml


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
	<!--Cible de décryptage "xxUser"-->
	<!--C'est une chaîne de caractères dans laquelle le texte brut "hogeyama" est chiffré avec le mot de passe "yakisoba".-->
	<xxUser>U2FsdGVkX1/k+6dPcXrci4AbyQ0TNtytubkVFCxzcF4=</xxUser>
	<!--Cible de décryptage "xxPass"-->
	<!--Il s'agit d'une chaîne de caractères dans laquelle le texte brut "mot de passe hogeyamano" est crypté avec le mot de passe "yakisoba".-->
	<xxPass>U2FsdGVkX19XLVe01kx2ahoKVKnSXLhBQ2aiRrdUlbjgtKu1IXD3EuYDSADab5vA</xxPass>
	<!--Texte brut-->
	<miscItem1>hoge</miscItem1>
	<!--Texte brut-->
	<miscItem2>fuga</miscItem2>
</root>

Créez la chaîne de caractères chiffrée réelle avec la commande openssl (ci-dessous) ou l '[URL de référence] ci-dessus (https://qiita.com/kazuhidet/items/509bee2c3a109ff6ea61).

bash


$echo [texte brut]| openssl enc -e -aes-128-cbc -base64 -k [mot de passe principal]
2. Créez une annotation chiffrée.

Annotation chiffrée


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. Créez un PropertyBean.

À ce stade, ajoutez une annotation chiffrée au champ qui contient la valeur chiffrée.

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. La classe EncDecConverter est la suivante.

Classe EncDecConverter


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));
        //Pour Java 8 ou version ultérieure, il peut être décrit comme suit.
        //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));
        //Pour Java 8 ou version ultérieure, il peut être décrit comme suit.
        //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. Créez une classe de test et vérifiez l'opération.

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()); //Sortie hogeyama
		System.out.println(prop.getXxPass()); //Sortie du mot de passe hogeyamano
		System.out.println(prop.getMiscItem1()); //Sortie hoge
		System.out.println(prop.getMiscItem2()); //Sortie fuga
		System.exit(0);
	}
}

Si le résultat de la méthode de déchiffrement est vrai Dans l'instance PropertyBean passée en argument ** Le champ à déchiffrer ** a été déchiffré. Tout ce que vous avez à faire est de le récupérer dans le haricot et de l'utiliser normalement.

Comme mentionné ci-dessus, en tant qu'argument du constructeur EncDecConverter Vous devez transmettre le mot de passe principal en texte brut, Dans un premier temps, ** Comment gérer le mot de passe principal et comment effectuer l'I / F? ** ** Ne sera pas mentionné dans cet article.

Résumé

Je pense qu'il existe également un modèle dans lequel les chaînes de caractères cryptées OpenSSL sont partiellement utilisées au lieu de "texte entier". (Mis à part si c'est la bonne réponse) J'ai donc essayé de résumer ces conseils. De plus, je ne pense pas qu'il soit bon d'utiliser mal la réflexion, mais je l'utilise parce que c'est pratique.

Recommended Posts

Décrypter le bean entier dans un fichier de propriétés avec certaines valeurs cryptées
Créez un fichier jar avec la commande
Lire une chaîne dans un fichier PDF avec Java
Comment enregistrer des fichiers avec l'extension spécifiée sous le répertoire spécifié en Java dans la liste
Lorsque je suis passé à IntelliJ, il y avait une grande différence dans l'encodage du fichier de propriétés.
Mettez le fichier dans les propriétés de string avec la configuration spring xml
Remplacez la définition du bean dans le fichier de configuration spring xml par un autre xml
À propos du comportement lors de la création d'un mappage de fichiers avec Java
Trouvez le nombre de jours dans un mois avec Kotlin
Comment créer un fichier jar sans dépendances dans Maven
Lisez le fichier sous le chemin de classe sous forme de chaîne de caractères avec ressort
Intégré dans Spring Boot à l'aide d'un fichier de définition de bean nommé application.xml
Obtenez l'URL publique du fichier privé de Flickr en Java