package com.fidufosyga.model;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Key;
import javax.crypto.SecretKey;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.utils.EncryptionConstants;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.OutputKeys;
import org.apache.xml.security.utils.JavaUtils;


public class Crypto {
	
	static   {
		org.apache.xml.security.Init.init();
     }
	
	private static Document parseFile(String fileName) throws Exception{

		javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(true);
		javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
		Document document = db.parse(fileName);
		
		return document;
	}
	
	private static SecretKey GenerateKeyEncryptionKey() throws Exception{
			String jceAlgorithmName = "DESede";
			KeyGenerator keyGenerator = KeyGenerator.getInstance(jceAlgorithmName);
			SecretKey keyEncryptKey = keyGenerator.generateKey();
			return keyEncryptKey;
 	}
	
	private static void storeKeyFile(Key keyEncryptKey, String path) throws IOException{
	    byte[] keyBytes = keyEncryptKey.getEncoded();
	    File keyEncryptKeyFile = new File(path + "keyEncryptKey");
	    FileOutputStream outStream = new FileOutputStream(keyEncryptKeyFile);
	    outStream.write(keyBytes);
	    outStream.close();
	
	    //System.out.println("Key encryption key stored in: " + keyEncryptKeyFile.toURI().toString());
	}

	private static SecretKey GenerateSymmetricKey() throws Exception{
		String jceAlgorithmName = "AES";
		KeyGenerator keyGenerator = KeyGenerator.getInstance(jceAlgorithmName);
		keyGenerator.init(128);
		
		return keyGenerator.generateKey();
	}
	
	private static void writeEncryptedDocToFile(Document doc, String fileName) throws Exception{
		File encryptionFile = new File(fileName);
		FileOutputStream outStream = new FileOutputStream(encryptionFile);

		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer();
		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
		DOMSource source = new DOMSource(doc);
		StreamResult result = new StreamResult(outStream);
		transformer.transform(source, result);
		
		outStream.close();  
		
		//System.out.println("Encrypted XML document written to: " + encryptionFile.toURI().toString());

    }
	
	private static void writeDecryptedDocToFile(Document doc, String fileName) throws Exception{
		File encryptionFile = new File(fileName);
		FileOutputStream outStream = new FileOutputStream(encryptionFile);

		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer();
		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
		DOMSource source = new DOMSource(doc);
		StreamResult result = new StreamResult(outStream);
		transformer.transform(source, result);
		
		outStream.close();  
		
        //System.out.println("Decrypted data written to: " + encryptionFile.toURI().toString());
    }
	
	public static void Encrypt(String source, String target, String element, String path)throws Exception{
		// Parsea fichero xml en elmento tipo Document
		Document document = parseFile(source);
	    // generate symmetric key
	    Key symmetricKey = GenerateSymmetricKey();
        // Get a key to be used for encrypting the symmetric key
	    Key keyEncryptKey = GenerateKeyEncryptionKey();
        // Write the key to a file
	    storeKeyFile(keyEncryptKey, path);

		// Inicializa cifrador para cifrar la clave de cifrado de // la informacion del documento xml
		XMLCipher keyCipher = XMLCipher.getInstance(XMLCipher.TRIPLEDES_KeyWrap);
		keyCipher.init(XMLCipher.WRAP_MODE, keyEncryptKey);

		// Cifra la clave simetrica
		EncryptedKey encryptedKey = keyCipher.encryptKey(document, symmetricKey);
		// Especifica elemento del fichero xml a cifrar
		Element rootElement = document.getDocumentElement();
		Element elementToEncrypt = rootElement;
		// Inicializa cifrador para cifrar el elemento xml
		XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.AES_128);
		xmlCipher.init(XMLCipher.ENCRYPT_MODE, symmetricKey);
		// Anade informacion de la clave de cifrado
		EncryptedData encryptedDataElement = xmlCipher.getEncryptedData();
		KeyInfo keyInfo = new KeyInfo(document);
		keyInfo.add(encryptedKey);
		encryptedDataElement.setKeyInfo(keyInfo);
		// Cifra
		boolean encryptContentsOnly = true;
		xmlCipher.doFinal(document, elementToEncrypt);
		// Escribe el resultado en el fichero destino
		writeEncryptedDocToFile(document, target);
	}
	
	private static Document loadEncryptedFile(String fileName)throws Exception{
		File encryptedFile = new File(fileName);
		javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(true);
		javax.xml.parsers.DocumentBuilder builder = dbf.newDocumentBuilder();
		Document document = builder.parse(encryptedFile);
        //System.out.println("Encryption document loaded from: " + encryptedFile.toURI().toString());
		
		return document;
	}
	
    private static SecretKey loadKeyEncryptionKey(String path) throws Exception{
    	String fileName = path + "keyEncryptKey";
    	String jceAlgorithmName = "DESede";
    	File kekFile = new File(fileName);

    	DESedeKeySpec keySpec = new DESedeKeySpec(JavaUtils.getBytesFromFile(fileName));
    	SecretKeyFactory skf = SecretKeyFactory.getInstance(jceAlgorithmName);
    	SecretKey key = skf.generateSecret(keySpec);
    	
    	//System.out.println("Key encryption key loaded from: " + kekFile.toURI().toString());
    	return key;
    }
	
	
	public static void Decrypt(String source, String target, String path)throws Exception{
		// Carga el fichero xml origen en elemento tipo Document
		System.out.println("Dencrypted XML document: " + source.toString());
		Document document = loadEncryptedFile(source);
		String namespaceURI = EncryptionConstants.EncryptionSpecNS;
        String localName = EncryptionConstants._TAG_ENCRYPTEDDATA;
        Element encryptedDataElement = (Element)document.getElementsByTagNameNS(namespaceURI, localName).item(0);
		//Carga la clave de cifrado de la clave que cifro la informacion del documento xml
		Key keyEncryptKey = loadKeyEncryptionKey(path);
		// Inicializa cifrador
		XMLCipher xmlCipher = XMLCipher.getInstance();
		xmlCipher.init(XMLCipher.DECRYPT_MODE, null);
		xmlCipher.setKEK(keyEncryptKey);
		// Descifra
		try{
		xmlCipher.doFinal(document, encryptedDataElement);
		}
		catch (Exception e) {
			System.out.println("Error: " + e.getMessage());
		}
		
		// Escribe el resultado en un fichero
		writeDecryptedDocToFile(document, target);
	}
}
