XML Signature – Firma Digital sobre XML

24
74726

XML Signature – Firma Digital sobre XML

En este tutorial vamos a ver ejemplos de cómo firmar digitalmente y validar un documento.

Introducción a XML Signature

Con las firmas digitales conseguimos:

  • Integridad: Si la información firmada es modificada, la validación de la firma fallará.
  • No repudiación: La entidad que firmo la información no puede luego decir que no lo hizo.
  • Autentificación: Sabemos qué entidad es la que realizó la firma, pues lo podemos validar con su clave pública.

Además podemos conseguir confidencialidad (cifrado) con el par de claves pública/privada, pues lo que se encripta con la clave privada sólo puede ser descifrado con la clave pública.

Para este tutorial utilizaremos la implementación de Apache (http://xml.apache.org/security/dist/).

Veamos unos ejemplos auto comentados:

  1. Firma digitalmente un documento XML
  2. Validación de un documento firmado digitalmente

Generamos una clave privada y su correspondiente certificado

La herramienta keytool crea pares de claves públicas y privadas, certificados auto firmados, y gestiona almacenes de claves.
Creamos una clave pública/privada:

keytool -genkey -alias mi_cert_ejemplo -keyalg DSA -dname "CN=Usuario, O=Autentia, C=ES"
                    -keypass abc1234 -storepass abc12345 -keystore myKeyStore.jks -validity 365
      

Clase de utilidades usada en los ejemplos de este tutorial

package com.autentia.examples.xmlsignature;

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 para el ejemplo
 * @author Carlos García. 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 value
	 */
	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;
	}  
	
	/**
	 * @return Devuelve un Document a firmar
	 * @throws Exception Cualquier incidencia
	 */
	public static Document createSampleDocument() 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",	 "Pepito"));
        person.appendChild(DOMUtils.createNode(document, "apellidos", "Pérez Luna"));
        person.appendChild(DOMUtils.createNode(document, "email",	 "pepito.perez@servidor.com"));

        document.appendChild(person);

        return document;
	}	
}

Ejemplo. Firmamos digitalmente el documento XML

package com.autentia.examples.xmlsignature;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;


import org.w3c.dom.*;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;


/**
 * Firma un documento XML
 * @author Carlos García. Autentia.
 * @see http://www.mobiletest.es
 */
public class CreateSignature {

	private static final String KEYSTORE_TYPE		  = "JKS";
	private static final String KEYSTORE_FILE		  = "myKeyStore.jks";
	private static final String KEYSTORE_PASSWORD	  = "abc12345";
	private static final String PRIVATE_KEY_PASSWORD  = "abc1234";
	private static final String PRIVATE_KEY_ALIAS	  = "mi_cert_ejemplo";



	/**
	 * Punto de entrada al ejemplo
	 */
	public static void main(String args[]) throws Exception {
		org.apache.xml.security.Init.init();

		Document doc = DOMUtils.createSampleDocument();
		
		Constants.setSignatureSpecNSprefix("");	// Sino, pone por defecto como prefijo: "ns"


		// Cargamos el almacen de claves
		KeyStore ks  = KeyStore.getInstance(KEYSTORE_TYPE);
		ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PASSWORD.toCharArray());

		// Obtenemos la clave privada, pues la necesitaremos para encriptar.
		PrivateKey privateKey = (PrivateKey) ks.getKey(PRIVATE_KEY_ALIAS, PRIVATE_KEY_PASSWORD.toCharArray());
		File	signatureFile = new File("signature.xml");		
		String	baseURI       = signatureFile.toURL().toString();	// BaseURI para las URL Relativas.
		
		// Instanciamos un objeto XMLSignature desde el Document. El algoritmo de firma será DSA
		XMLSignature xmlSignature = new XMLSignature(doc, baseURI, XMLSignature.ALGO_ID_SIGNATURE_DSA);

		 
		// Añadimos el nodo de la firma a la raiz antes de firmar.
		// Observe que ambos elementos pueden ser mezclados en una forma con referencias separadas
		doc.getDocumentElement().appendChild(xmlSignature.getElement());

		// Creamos el objeto que mapea: Document/Reference
		Transforms transforms = new Transforms(doc);
		transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
		
		// Añadimos lo anterior Documento / Referencia
		// ALGO_ID_DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1";
		xmlSignature.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);

		// Añadimos el KeyInfo del certificado cuya clave privada usamos
		X509Certificate cert = (X509Certificate) ks.getCertificate(PRIVATE_KEY_ALIAS);
		xmlSignature.addKeyInfo(cert);
		xmlSignature.addKeyInfo(cert.getPublicKey());
		
		
		// Realizamos la firma
		xmlSignature.sign(privateKey);
		
		// Guardamos archivo de firma en disco
		DOMUtils.outputDocToFile(doc, signatureFile);
	}
}

Documento firmado digitalmente (signature.xml).

En el siguiente documento se muestra el documento firmado digitalmente, en donde se puede observar:

  1. La información relacionada con la tarjeta de crédito ha sido sustituida por el elemento xenc:EncryptedData

	Pepito
	Pérez Luna
	pepito.perez@servidor.com
	
		
			
			
			
				
					
				
				
				
					Oyyx+K28+cp7kuUgcnANtTBdUwg=
				
			
		
		
			ZVzRud7G4mEZsDnBavbnZoFUmm5J2OBDkQ+IooDLn95ndGYdrq6uPQ==
		
		
			
				
					MIICmDCCAlYCBEfrim8wCwYHKoZIzjgEAwUAMDIxCzAJBgNVBAYTAkVTMREwDwYDVQQKEwhBdXRl
					bnRpYTEQMA4GA1UEAxMHVXN1YXJpbzAeFw0wODAzMjcxMTUyMTVaFw0wOTAzMjcxMTUyMTVaMDIx
					CzAJBgNVBAYTAkVTMREwDwYDVQQKEwhBdXRlbnRpYTEQMA4GA1UEAxMHVXN1YXJpbzCCAbcwggEs
					BgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEmaUVdQCJR+1k9
					jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD
					9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGB
					APfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYT
					t88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4VrlnwaS
					i2ZegHtVJWQBTDv+z0kqA4GEAAKBgDUPDwxDZFXMrZha74VNmgyFslLM01wKw17nbt9UFTJALk71
					iPpozeZMP2u0SoYst2nbxkCs1hziiuaNJnykzcjVf3+PmL3sQES8SxwJBRUME2UTA2006WD3xNy9
					iZ9yibcWQimB8eKIJyBBxSk5TueAzvTA8HN2+Rvgh8RMaOzhMAsGByqGSM44BAMFAAMvADAsAhQH
					4+nQZdFvlvsfyOfq1t02h9MJEgIUEvYDfxeygKCmrIlA0sQLtaCs0Qo=
				
			
			
				
					

/X9TgR11EilS30qcLuzk5/YRt1I870QAwx4/gLZRJmlFXUAiUftZPY1Y+r/F9bow9subVWzXgTuA HTRv8mZgt2uZUKWkn5/oBHsQIsJPu6nX/rfGG/g7V+fGqKYVDwT7g/bTxR7DAjVUE1oWkTL2dfOu K2HXKu/yIgMZndFIAcc=

l2BQjxUjC8yykrmCouuEC/BYHPU= 9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3 zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKL Zl6Ae1UlZAFMO/7PSSo= NQ8PDENkVcytmFrvhU2aDIWyUszTXArDXudu31QVMkAuTvWI+mjN5kw/a7RKhiy3advGQKzWHOKK 5o0mfKTNyNV/f4+YvexARLxLHAkFFQwTZRMDbTTpYPfE3L2Jn3KJtxZCKYHx4ognIEHFKTlO54DO 9MDwc3b5G+CHxExo7OE=

Ejemplo. Validamos el documento XML digitalmente firmado (signature.xml)

En el siguiente ejemplo vamos a validar el documento que ha sido firmado en el ejemplo anterior.

package com.autentia.examples.xmlsignature;


import java.io.File;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.w3c.dom.*;

import javax.xml.parsers.*;

/**
 * Verifica un archivo firmado
 * @author Carlos García. Autentia.
 * @see http://www.mobiletest.es
 */
public class VerifySignature {
	
	/**
	 * Punto de inicio
	 */
	public static void main(String args[]) throws Exception {
		org.apache.xml.security.Init.init();

		String signatureFileName = "signature.xml";

		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

		dbf.setNamespaceAware(true);
		dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE);
		
		File			f	= new File(signatureFileName);
		DocumentBuilder db	= dbf.newDocumentBuilder();
		Document	 doc	= db.parse(new java.io.FileInputStream(f));
		Element		 sigElement = (Element) doc.getElementsByTagName("Signature").item(0);
		XMLSignature signature  = new XMLSignature(sigElement, f.toURL().toString());

		KeyInfo keyInfo = signature.getKeyInfo();
		if (keyInfo != null) {
			X509Certificate cert = keyInfo.getX509Certificate();
			if (cert != null) {
				// Validamos la firma usando un certificado X509
				if (signature.checkSignatureValue(cert)){
					System.out.println("Válido según el certificado");	
				} else {
					System.out.println("Inválido según el certificado");	
				}
			} else {
				// No encontramos un Certificado intentamos validar por la cláve pública
				PublicKey pk = keyInfo.getPublicKey();
				if (pk != null) {
					// Validamos usando la clave pública
					if (signature.checkSignatureValue(pk)){
						System.out.println("Válido según la clave pública");	
					} else {
						System.out.println("Inválido según la clave pública");	
					}
				} else {
					System.out.println("No podemos validar, tampoco hay clave pública");
				}
			}
		} else {
			System.out.println("No ha sido posible encontrar el KeyInfo");
		}
	}

}

Como prueba, modifique el documento y observará que el proceso de validación falla debido a que no se cumple la propiedad de integración de la información.

Saludos, Carlos García.

24 COMENTARIOS

  1. Hola Yolanda, te dejo a continuación un ejemplo en C# para validar un XML contra un XSD, espero aun te sirva Saludos desde Morelia, México.

    private void btnValidar_Click(object sender, EventArgs e)
    {
    Resultado = true;
    Accion = VALIDAR;
    txtResultado.Text = \\\»\\\»;
    txtComentarios.Text = \\\»\\\»;
    try
    {

    XmlTextReader xmlR = new XmlTextReader(txtXML.Text);
    XmlValidatingReader xsdR = new XmlValidatingReader(xmlR);
    if (chkXSD.Checked)
    xsdR.Schemas.Add(null, txtXSD.Text);
    xsdR.ValidationType = chkXSD.Checked ? ValidationType.Schema : ValidationType.None;
    xsdR.ValidationEventHandler += new ValidationEventHandler(AdminEventoValidacion);

    while (xsdR.Read())
    {
    Cursor.Current = Cursors.WaitCursor;
    Application.DoEvents();
    }
    Cursor.Current = Cursors.Default;
    xsdR.Close();
    }
    catch (UnauthorizedAccessException ex)
    {
    txtComentarios.Text = ex.Message;
    }
    catch (Exception ex)
    {
    txtComentarios.Text = ex.Message;
    }
    finally
    {
    if (txtComentarios.Text.Trim() == \\\»\\\»)
    {
    if (chkXSD.Checked)
    txtResultado.Text = Resultado ? \\\»Formato de la declaración cumple las especificaciones del esquema\\\» : \\\»Archivo XML incorrecto con respecto al esquema XSD\\\»;
    else
    txtResultado.Text = Resultado ? \\\»La estructura del archivo es correcta\\\» : \\\»Archivo XML mal estructurado\\\»;
    txtComentarios.Text = txtResultado.Text;
    }
    else
    txtResultado.Text = \\\»Esperando validación…\\\»;
    }
    }

  2. El método asociado al evento de validacion, segun sea con respecto al XSD (ValidationType.Schema), o bien para validar la estructura del XML (ValidationType.None)

    private void AdminEventoValidacion(object sender, ValidationEventArgs args)
    {
    Resultado = false;
    txtComentarios.Text += args.Message + \\\»\\\
    \\\
    \\\»;
    }

  3. Hola me podrian enviar o indicar donde puedo bajar los jars para hacer uso de las librerias de firma de xml para utilzar las clases hablo especificamente de las siguientes
    import org.w3c.dom.*;
    import org.apache.xml.security.signature.XMLSignature;
    import org.apache.xml.security.transforms.Transforms;
    import org.apache.xml.security.utils.Constants;

  4. Estimado Carlos
    Estoy investigando este tema y necesito exponerlo para continuar con mis estudios de post-grado y me gustaría tener un demo de tu aplicación, solo que funcione con algún ejemplo que pueda ejecutar en linea. Sé cómo opera el mecanismo de encriptación pero necesito exponerlo a un grupo de compañeros de estudio ¿es posible? respeto toda autoría y mantendría el contacto para seguir esta linea de trabajo en el desarrollo de web semántica.

    Gracias de antemano.
    Adrian Silva

  5. Carlos el tutorial esta muy bueno muy detallado. Necesito extender las funcionalidades de un prototipo de una aplicación para firmar digitalmente archivos XML. Usted podría decirme cuales son las librerías mas utilizada por Jaba para firmar XML. Me seria de gran ayuda su información.
    saludos

  6. Me ha parecido muy interesante y muy util, pero tengo una duda, ¿como puedo validar que el certificado X509 que viaja en el XML está autorizado por una Autoridad certificadora válida que yo tenga almacenada en mi equipo?
    Gracias.

  7. Hola quisiera saber por que la firma es Inválida según la clave pública cuando la valides de la firma ya no es valida, cuando expiro el certificado, existe alguna forma de verificar integridad si la fecha de expiración esta vencida.

  8. hola a todos… tengo un problema con org.apache.xml.security.Init.init(); me salta la excepcion, probé con la librerias xmlSec 1.0.4 , 1.3.0 y 1.5.0 y con todas me bota el mismo error… por favor su ayuda… gracias…

    • Completa con est, el tag es SOAP-ENV:Header:

      Element element = null;
      element = doc.getDocumentElement();
      element.normalize();
      element.getElementsByTagName(«SOAP-ENV:Header»).item(0)
      .appendChild(xmlSignature.getElement());

  9. Hola, necesito ayuda super urgente. Necesito incorporar tags KEYINFO, como se podría incorporar estos tags en el xml firmado? Muchas gracias por el aporte!

  10. Hay alguna manera de implementar
    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);
    }

    en PHP

    • Para UBL 2.0, 2.1, en Invoice debes solamente ubicar el nodo del UBLExtensions mediante los nodos hijos del documento, por ejemplo, en mi caso tengo esta estructura:

      En el java, considerando que el objeto «doc» represemta el xml, accedo al nodo de la firma de la siguiente forma:

      doc.getDocumentElement().getChildNodes().item(3).getChildNodes().item(3).getChildNodes().item(1).appendChild(xmlSignature.getElement());

      Veras que se imprime la firma en ese nodo.

      Saludos

  11. consulta un documento XML firmado se puede importar a otro XML?, si es positivo como se realiza? yo importo pero pierdo la validez de la firma, de la sección del primer documento

  12. Hola Mario,

    Gran ayuda tu codigo para conseguir firmar con Java un xml.

    Estoy tratando de generar un trozo de código que sea capaz de firmar un xml y subirlo directamente a efactura, pero el xml firmado no contine la estructura de firma que efactura espera.

    Me gustaría poder intercambiar contigo ideas y poder colaborar para resolver este problema
    Es posible?

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad