XML Encryption, Criptografía sobre XML
En este tutorial vamos a ver ejemplos de cómo realizar la encriptación y desencriptación de una sección de un documento XML, dejando el resto del documento sin encriptar.
Introducción a XML Encryption
La privacidad o confidencialidad de la información es uno de los requisitos que se deben cubrir en la gran mayoría de las aplicaciones.
Debido al auge de XML como formato de representación (por ejemplo, mensajes entre servicios Web) se hace necesario mecanismos que nos permitan ocultar la información sensible.
XML Encryption es un lenguaje definido por W3C que nos permite especificar que partes de la información deseamos que vaya cifrada y que partes no.
El cifrado/descifrado se puede realizar tanto con claves simétricas cómo asimétricas. Recuerde que los procesos de cifrado/descrifrado básado en claves simétricas tienen muchísimo más rendimiento que los basados en claves asimétricas (pública/privada).
Para este tutorial utilizaremos la implementación de Apache (http://xml.apache.org/security/dist/).
Veamos unos ejemplos autocomentados:
- Encriptación de parte de una información XML.
- Desencriptación de la información anterior.
Documento XML para los ejemplos:
Dada la siguiente información en XML, vamos a encriptar exclusivamente mediante criptografía simétrica la información relaccionada con la tarjeta de crédito.
1 2 3 4 5 6 7 8 9 |
<persona id="468300000"> <nombre>Marvis</nombre> <apellidos>Rondon Marcelo</apellidos> <email>marvis@servidor.com</email> <tarjetaCredito> <numero>83838383</numero> <fechaExpiracion>01/05</fechaExpiracion> </tarjetaCredito> </persona> |
Clase de utilidades usada en los ejemplos de este tutorial
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package com.autentia.examples.xmlencryption; import java.io.File; import java.io.FileOutputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Clase de utilidad. * @author Carlos García Pérez. Autentia. * @see http://www.mobiletest.es */ public class DOMUtils { /** * Serializa un objeto Document en un archivo */ public static void outputDocToFile(Document doc, File file) throws Exception { FileOutputStream f = new FileOutputStream(file); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.transform(new DOMSource(doc), new StreamResult(f)); f.close(); } /** * Lee un Document desde un archivo */ public static Document loadDocumentFromFile(java.io.File file) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = null; factory.setNamespaceAware(true); builder = factory.newDocumentBuilder(); return builder.parse(file); } /** * @return Crea un elemento <tag>value</tag> */ public static Element createNode(Document document, String tag, String value){ Element node = document.createElement(tag); if (value != null){ node.appendChild(document.createTextNode(value)); } return node; } } |
Ejemplo de encriptación de una sección de un documento XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
package com.autentia.examples.xmlencryption; import java.io.File; import java.io.FileOutputStream; import java.security.Key; import javax.xml.parsers.*; import javax.crypto.SecretKey; import javax.crypto.KeyGenerator; import org.apache.xml.security.keys.KeyInfo; 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; /** * Este ejemplo encripta la información relacionada con la tarjeta de crédito de un cliente * @author Carlos García Pérez. Autentia. * @see http://www.mobiletest.es */ public class Encrypter { private static final String SECRET_KEY_FILENAME = "mykey.dat"; private static final String ENCRYPTED_XML_FILENAME = "infoCifrada.xml"; /** * Genera un Document de ejemplo */ private static Document createSampleDom() throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); Element person = document.createElement("persona"); person.setAttribute("id", "468300000"); person.appendChild(DOMUtils.createNode(document, "nombre", "Marvis")); person.appendChild(DOMUtils.createNode(document, "apellidos", "Rondon Marcelo")); person.appendChild(DOMUtils.createNode(document, "email", "marvis@servidor.com")); Element creditCard = document.createElement("tarjetaCredito"); creditCard.appendChild(DOMUtils.createNode(document, "numero", "83838383")); creditCard.appendChild(DOMUtils.createNode(document, "fechaExpiracion", "01/05")); person.appendChild(creditCard); document.appendChild(person); return document; } /** * @return Genera la clave secreta que servirá para encriptar/desencriptar la información * @throws Exception Cualquier incidencia */ private static SecretKey generateAndStoreKeyEncryptionKey() throws Exception { // Generamos la clave usando el algoritmo Triple DES KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede"); // Algoritmo JCE: Triple DES SecretKey secret = keyGenerator.generateKey(); byte[] bytes = secret.getEncoded(); // Guardamos la clave en disco File keyFile = new File(SECRET_KEY_FILENAME); FileOutputStream output = new FileOutputStream(keyFile); output.write(bytes); output.close(); System.out.println("La clave de encriptación está guardada en: " + keyFile.getAbsolutePath()); return secret; } /** * @return Devuelve la clave de encriptación de datos * @throws Exception Cualquier incidencia */ private static SecretKey generateDataEncryptionKey() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); // Algoritmo JCE: Advanced Encryption Standard keyGenerator.init(128); return keyGenerator.generateKey(); } /** * Punto de entrada del ejemplo * @throws Exception Cualquier incidencia */ public static void main(String args[]) throws Exception { // Inicializamos el FrameWork de seguridad de Apache a los valores por defecto org.apache.xml.security.Init.init(); Document document = Encrypter.createSampleDom(); // Obtenemos la clave para encriptar el elemento. Key symmetricKey = Encrypter.generateDataEncryptionKey(); // Obtenemos la clave para encriptar la clave simétrica Key kek = Encrypter.generateAndStoreKeyEncryptionKey(); // Inicializa cifrador para cifrar la clave de cifrado de la información del documento xml XMLCipher keyCipher = XMLCipher.getInstance(XMLCipher.TRIPLEDES_KeyWrap); // URI "http://www.w3.org/2001/04/xmlenc#kw-tripledes"; keyCipher.init(XMLCipher.WRAP_MODE, kek); EncryptedKey encryptedKey = keyCipher.encryptKey(document, symmetricKey); // Ciframos la clave simétrica // Cifrar el contenido del elemento XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.AES_128); // URI "http://www.w3.org/2001/04/xmlenc#aes128-cbc"; xmlCipher.init(XMLCipher.ENCRYPT_MODE, symmetricKey); // Añadimos información sobre la clave de cifrado. KeyInfo EncryptedData encryptedData = xmlCipher.getEncryptedData(); KeyInfo keyInfo = new KeyInfo(document); keyInfo.add(encryptedKey); encryptedData.setKeyInfo(keyInfo); /* * Criframos!! * Reemplazamos en el documento los datos a encriptar por el elemento EncrypteData * con el tercer parámetro a true indica que deseamos encriptar el contenido del elemento * y no el elemento en sí. */ Element node = (Element) document.getElementsByTagName("tarjetaCredito").item(0); xmlCipher.doFinal(document, node, false); // Guarda el Document en un archivo File file = new File(ENCRYPTED_XML_FILENAME); DOMUtils.outputDocToFile(document, file); System.out.println("Los datos han sido encriptados en: " + file.toURL().toString()); } } |
Documento encriptado (infoCifrada.xml)
En el siguiente documento se muestra la información que sería enviada al receptor. En ella se puede observar entre otras cosas:
- La información relacionada con la tarjeta de crédito ha sido sustituida por el elemento
xenc:EncryptedData
- El elemento
xenc:EncryptionMethod
contiene información sobre el método de encriptación usado. - El elemento
KeyInfo
contiene información sobre la clave de encriptación. - El elemento
CipherData
contiene la información encriptada, es decir, la información sobre la tarjeta de crédito. - El elemento
EncryptedKey
contiene la clave de encriptación/desencriptación de la información almacenada enCipherCipherValue
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<persona id="468300000"> <nombre>Marvis</nombre> <apellidos>Rondon Marcelo</apellidos> <email>marvis@servidor.com</email> <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" /> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-tripledes" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" /> <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> gMp/3ZuYVyHn74JDKr3WCLDrf7H+S6wLqGEdRdgqQGw= </xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedKey> </ds:KeyInfo> <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> Wr1njyJlYYOM9lAYqcwGCWkw2L4pUjQD2GGVoU9lVZ0wKqHY8y3l3GY8FY4i5K3G8grIe2xN4u7x 7RtkFiXZgGMeYnQp6oB6ckKp3KFKHVqtucc9AVzOgC7XAw/oe61HRFqe6RRVzXjNMLU5TaV7lJF1 I8NVPQmUSDX7NRtnR68= </xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedData> </persona> |
Ejemplo de desencriptación del archivo (infoCifrada.xml)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
package com.autentia.examples.xmlencryption; import java.io.File; import java.security.Key; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; import org.apache.xml.security.encryption.XMLCipher; import org.apache.xml.security.utils.JavaUtils; import org.apache.xml.security.utils.EncryptionConstants; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Este ejemplo desencripta la información encriptada de un documento XML * @author Carlos García Pérez. Autentia. * @see http://www.mobiletest.es */ public class Decrypter { private static final String SECRET_KEY_FILENAME = "mykey.dat"; private static final String ENCRYPTED_XML_FILENAME = "infoCifrada.xml"; private static final String DESENCRYPTED_XML_FILENAME = "infoDescifrada.xml"; /** * @return La clave de encriptación/encriptación desde un archivo * @throws Exception Cualquier incidencia */ private static SecretKey loadDesencryptionKey() throws Exception { DESedeKeySpec keySpec = new DESedeKeySpec(JavaUtils.getBytesFromFile(SECRET_KEY_FILENAME)); SecretKeyFactory skf = SecretKeyFactory.getInstance("DESede"); SecretKey key = skf.generateSecret(keySpec); return key; } /** * Punto de entrada del ejemplo * @throws Exception Cualquier incidencia */ public static void main(String args[]) throws Exception { // Inicializamos el FrameWork de seguridad de Apache a los valores por defecto org.apache.xml.security.Init.init(); // Obtenemos el documento xml encriptado Document document = DOMUtils.loadDocumentFromFile(new File(ENCRYPTED_XML_FILENAME)); // Accedemos al nodo con la información encriptada. namespace: "http://www.w3.org/2001/04/xmlenc#", localName: "EncryptedData" Element node = (Element) document.getElementsByTagNameNS(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_ENCRYPTEDDATA).item(0); Key kek = Decrypter.loadDesencryptionKey(); // Carga la clave para desencriptar la información // La clave que será usada para desencriptar los datos del xml se obtendrá desde el KeyInfo del EncrypteData usando la EncryptedKey XMLCipher cipher = XMLCipher.getInstance(); cipher.init(XMLCipher.DECRYPT_MODE, null); // Key=null para que use como clave el EncryptedKey cipher.setKEK(kek); // Desencriptamos reemplazando los datos encriptados con su contenido desencriptado cipher.doFinal(document, node); // Guarda el Document en un archivo File file = new File(DESENCRYPTED_XML_FILENAME); DOMUtils.outputDocToFile(document, file); System.out.println("Los datos han sido desencriptados en: " + file.toURL().toString()); } } |
Saludos, Carlos García.