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:
- Firma digitalmente un documento XML
- 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 elementovalue */ 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:
- 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.
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…\\\»;
}
}
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 + \\\»\\\
\\\
\\\»;
}
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;
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
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
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.
Muy buen ejemplo. ¿podríais definir como hacer para firmar una parte de un documento, no un documento XML completo (ReferenceUri=»»)?
Gracias
buenas tengo problemas con las librerias de apache me podrian dar el url para descargar gracias..
Como podria hacer esta firma , pero en php para facturacion electronica en php, es para sunat ?
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.
Como puedo hacer el xml en formato UBL 2.0 de Oasis ?
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…
Hola, necesito que la firma me quede en un tag especifico del XML y no en la raiz.
Ejemplo:
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());
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!
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
¿Alguien ya firmo en .NET?
el algoritmo que cambio debería tener en caso de un UBL 2.1
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
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
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?
Alguien ha conseguido firmar un xml con formato para efactura.
La rutina de Mario funciona bien, pero efactura no lo reconoce.
Y las dependencias_
Hola Carlos,
Puedes pasarnos la URL del repositorio de Git con el proyecto ?