Firma electrónica de la factura electrónica ecuatoriana

En el Ecuador el uso de facturas electrónicas (y en general de comprobantes electrónicos) es obligatorio a cada vez más tipos de empresas. Su implementación requiere que tengan firma electrónica ecuatoriana en estándar XAdES-BES (acrónimo de XML Advanced Electronic Signatures – Basic Electronic Signature), sin embargo no especifican con suficiente detalle la forma de implementar tal formato de firma, viéndose las empresas obligadas a utilizar un applet de Java que realice la acción como caja negra. A nivel técnico, esto quería decir que, no importa el lenguaje de programación utilizado, sea Python, .Net, PHP, u otro, era necesario salir de ese entorno para llamar a la funcionalidad en Java, con las molestias y bajas de rendimiento del caso.

A continuación se explicará la implementación de la firma electrónica XAdES BES que acepta el Servicio de Rentas Internas del Ecuador -SRI-. Como prerrequisito se necesita el certificado digital .P12 que lo vende el Banco Central del Ecuador. También es útil la herramienta gratuita del SRI para la facturación electrónica, con la cual se pueden comparar los resultados del firmado.

También te puede interesar la parte dos del artículo donde se implementa la firma digital en JavaScript y se explica el proceso de ingeniería inversa con el cual se obtuvo estos resultados. Adicional, en la parte tres del artículo se listan algunos problemas comunes que surgen en las implementaciones de este algoritmo, se detallan sus causas y se presentan posibles soluciones.

Estructura de la firma electrónica XAdES BES

La estructura de una factura electrónica con firma electrónica en formato XAdES BES  válida es la siguiente:

Estructura de la factura electrónica con firma electrónica
Estructura de la factura electrónica con su firma electrónica

El nodo ds:Signature contiene toda la firma generada y tiene dos namespaces:

  1. xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
  2. xmlns:etsi="http://uri.etsi.org/01903/v1.3.2#"

A continuación se detallan las cuatro partes de la firma electrónica XAdES.

ds:SignedInfo

Guarda el método de canonicalización (c14n), el algoritmo de firmado (SHA1) y tres hash que referencian a los siguientes nodos:

  1. SignedProperties dentro del nodo ds:Object (detallado más abajo en este artículo), que tiene los datos adicionales para implementar el formato XAdES BES.
  2. ds:KeyInfo, con la información del certificado firmante.
  3. comprobante, con la información de la factura electrónica.

Para obtener estos hash es necesario aplicar tanto el método de canonicalización como el algoritmo de firmado previamente mencionados. En la práctica, la canonicalización consiste solamente en agregar los dos namespaces antes indicados al nodo que se va a firmar, respetando todos los saltos de línea, espacios y caracteres especiales . Por ejemplo:

Original:

<ds:KeyInfo Id="Certificate976903">
    ...
</ds:KeyInfo>

Para obtener el hash:

<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:etsi="http://uri.etsi.org/01903/v1.3.2#" Id="Certificate976903">
    ...
</ds:KeyInfo>

La excepción sería el hash del nodo comprobante, el cual no se canonicaliza previamente.

Como se mencionó, el algoritmo utilizado para el hash es SHA1, el cual por cierto está siendo reemplazado a nivel mundial por SHA256 debido a las vulnerabilidades descubiertas en su algoritmo. Esperemos que el SRI mejore la seguridad en este sentido.

Para su inclusión en el archivo XML de la factura electrónica firmada, el hash debe ser codificado en base64.

ds:SignatureValue

Es la firma digital codificada en base64 del nodo ds:SignedInfo, previamente canonicalizado.

Actualización al 3 de enero del 2018: Esta parte de la firma, aunque parece relativamente simple (solo se obtendría la firma digital del nodo indicado, no es así?), puede llegar a ser un poco problemática, tal y como se lo detalla en la parte tres del artículo.

ds:KeyInfo

Guarda la información del certificado digital con el que se realiza la firma electrónica.

Tiene dos nodos: ds:X509Datads:KeyValue.

  • El nodo ds:X509Data tiene un solo nodo hijo, ds:X509Certificate, el cual guarda el certificado en formato PEM (acrónimo de Privacy Enhanced Mail, que es la versión codificada en base64 del certificado digital y que originalmente se creó para enviar los certificados binarios vía correo electrónico, de ahí su nombre), sin las cabeceras de inicio y final, y en renglones de 76 caracteres.
  • El nodo ds:KeyValue tiene un nodo hijo, ds:RSAKeyValue, y dos nodos nietos, ds:Modulusds:Exponent. El primero guarda el módulo en base64, y el segundo guarda el exponente, el cual es 65537 codificado en base64, que resulta AQAB. Por cierto, el número 65537 es un número primo de Fermat, exactamente el F4, que es usado ampliamente en algoritmos de encriptación por su bajo peso de Hamming, lo cual es eficiente en cálculos realizados por máquinas binarias.

ds:Object

Almacena información adicional para implementar el estándar XAdES BES. Sin esta sección no serían firmas XAdES sino firmas electrónicas XML clásicas.

Este nodo tiene un solo nodo hijo, etsi:QualifyingProperties, y un solo nodo nieto, etsi:SignedProperties. Este último tiene dos nodos hijos, etsi:SignedSignaturePropertiesetsi:SignedDataObjectProperties.

  • El nodo etsi:SignedSignatureProperties tiene dos nodos hijos, etsi:SigningTimeetsi:SigningCertificate. El primero guarda fecha y hora  de generación en formato ISO 8601 en hora local (YYYY-MM-DDThh:mm:ssTZD, donde TZD es la zona horaria en formato “-hh:mm”), y el segundo tiene la siguiente estructura:
    • etsi:Cert
      • etsi:CertDigest
        • ds:DigestMethod: El mismo algoritmo SHA1 usado anteriormente.
        • ds:DigestValue: el hash del certificado en formato DER (Distinguished Encoding Rules), codificado en base64.
      • etsi:IssuerSerial
        • ds:X509IssuerName: guarda el valor constante (por ahora) de "CN=AC BANCO CENTRAL DEL ECUADOR,L=QUITO,OU=ENTIDAD DE CERTIFICACION DE INFORMACION-ECIBCE,O=BANCO CENTRAL DEL ECUADOR,C=EC". Sería recomendable sacar este valor del mismo certificado digital, para mantener compatibilidad futura.
        • ds:X509SerialNumber: el número serial del certificado digital, en formato decimal, no hexadecimal.
  • El nodo etsi:SignedDataObjectProperties tiene la siguiente estructura:
    • etsi:DataObjectFormat
      • etsi:Description: guarda el valor constante de "contenido comprobante".
      • etsi:MimeType: guarda el valor constante de "text/xml".

Números aleatorios en la estructura de la firma electrónica

Existen ocho números aleatorios enteros entre 1 y 100 mil, que se colocan a lo largo de la estructura de la firma electrónica:

  1. Certificate_number
  2. Signature_number
  3. SignedProperties_number
  4. SignedInfo_number
  5. SignedPropertiesID_number
  6. Reference_ID_number
  7. SignatureValue_number
  8. Object_number

Estos números aleatorios se colocan en atributos de varios nodos, de la siguiente forma:

  1. En ds:KeyInfo, su atributo Id se compone de la siguiente manera: "Certificate" & Certificate_number.
  2. En el segundo nodo hijo ds:Reference del nodo ds:SignedInfo, el atributo URI se compone de la siguiente manera: "#Certificate" & Certificate_number.
  3. En etsi:SignedProperties, su atributo Id se compone de la siguiente manera: "Signature" & Signature_number & "-SignedProperties" & SignedProperties_number.
  4. En el primer nodo hijo ds:Reference del nodo ds:SignedInfo, el atributo URI se compone de la siguiente manera: "#Signature" & Signature_number & "-SignedProperties" & SignedProperties_number.
  5. En ds:Signature, su atributo Id se compone de la siguiente manera: "Signature" & Signature_number.
  6. En ds:Object, su atributo Id se compone de la siguiente manera: "Signature" & Signature_number & "-Object" & Object_number.
  7. En etsi:QualifyingProperties, su atributo Target se compone de la siguiente manera: "#Signature" & Signature_number.
  8. En ds:SignedInfo, su atributo Id se compone de la siguiente manera: "Signature-SignedInfo" & SignedInfo_number.
  9. En el primer nodo hijo ds:Reference del nodo ds:SignedInfo, el atributo Id se compone de la siguiente manera: "SignedPropertiesID" & SignedPropertiesID_number.
  10. En etsi:DataObjectFormat, su atributo ObjectReference se compone de la siguiente manera: "#Reference-ID-" & Reference_ID_number.
  11. En el tercer nodo hijo ds:Reference del nodo ds:SignedInfo, el atributo Id se compone de la siguiente manera: "Reference-ID-" & Reference_ID_number.
  12. En ds:SignatureValue, su atributo Id se compone de la siguiente manera: "SignatureValue" & SignatureValue_number.

Implementación en JavaScript

Para la implementación de la firma electrónica en JavaScript se utilizó la librería Forge tanto para la obtención de los hash SHA1 así como para la extracción de datos del certificado digital, y el firmado en sí. También se utilizó la librería moment.js para la obtención de la fecha y hora de generación.

Para probar la firma electrónica se tiene dos opciones:

  1. Firmar primero con el facturador electrónico gratuito del SRI, luego colocar los ocho números aleatorios presentes en la firma electrónica generada, y con esos números ejecutar la versión JavaScript del generador de firmas. Deben resultar en los mismos valores.
  2. Generar la firma electrónica con la versión JavaScript, codificar todo el contenido del XML resultante en base64, y enviar el texto vía SOAP a los Servicios Web del SRI.

El código fuente se lo puede acceder en GitHub.

Actualización al 25 de octubre de 2016:

Se creó una prueba de concepto donde la firma electrónica se genera 100% local en el navegador, sin recursos de servidor ni enviando información a ninguna parte. Se la puede acceder en https://jsfiddle.net/jybaro/xmvov1c3/

Esta versión, a más de utilizar las librerías Forge y Moment mencionadas antes, también llama a la librería buffer, la cual permite manipular información binaria en el navegador.

Actualización al 28 de diciembre de 2016:

Se ha elaborado un artículo complementario donde se explican detalles sobre la implementación en JavaScript:

Actualizado al 3 de enero de 2018:

Se ha elaborado una tercera parte del artículo con un listado de soluciones a problemas comunes en la implementación de la firma electrónica:

Firma electrónica de la factura electrónica ecuatoriana
5/5, de 3 votos

También te puede interesar...

32 comentarios sobre “Firma electrónica de la factura electrónica ecuatoriana

  1. https://jsfiddle.net/jybaro/xmvov1c3/
    Lo he probado con algunas certificados pero no funciona simpre me devuelve
    mensaje = “FIRMA INVALIDA”
    informacionAdicional = “La firma es invalida [Firma inválida (firma y/o certificados alterados)]”
    Quisiera saber si en el nodo ds:SignatureValue, es solamente la firma digital codificada en base64 del nodo ds:SignedInfo, o tiene que ir algun otro valor

    1. Hola,
      Corregido. Había un error en JSFiddle al obtener la firma digital del nodo SignedInfo, donde se estaba reutilizando una variable que ya estaba instanciada antes. Está probado en el servicio Web del SRI y ya no sale el error de “firma inválida”. La corrección está subida al mismo JSFiddle.
      Tal y como se detalla en el artículo, la firma es solo de ese nodo, previa canonicalización.
      Gracias por el aviso!

  2. Estimado he probado el código en jsfiddle y me sigue apareciendo error en firma, realizando la depuración observo que el bloque SignatureValue y DigestValue del comprobante, no coinciden.
    Saludos,

    1. Los nodos SignatureValue y DigestValue no tienen que ver puesto que, como se detalla en el artículo, el primero es la firma del nodo SignedInfo, mientras que el segundo es el hash del certificado.

      Para realizar la depuración sugiero revisar la segunda parte del artículo, cuyo enlace consta al final.

  3. Estimado Mike o Edgar Valarezo,

    Estuve probando el script y me sale el mismo error que publico mike sobre la firma invalida. Alguno de ustedes pudo dar con el error ?

    Gracias

    1. Hola, lo he probado nuevamente con el webservice del SRI y no me da el error de firma inválida que mencionas, que debo confesar antes sí daba y que con los ajustes del caso ya fue superado hace unos meses. El error que ahora me da es ” ERROR EN DIFERENCIAS“, lo cual quiere decir que se logró verificar la firma del comprobante electrónico y que ahora manda errores ya del contenido de la factura. Con todo intentaré conseguir una segunda firma electrónica para realizar más pruebas, las colaboraciones son bienvenidas.

      Sugiero que te revises la segunda parte del artículo cuyo enlace se lo puede encontrar al final. También aconsejaría tener cuidado con la codificación de caracteres.

    1. Hola, favor visita el artículo sobre XaDES-BES, en la parte final hay una actualización de errores comunes en la implementación, espero te pueda servir. Saludos.

  4. Estimado en la libreria xades-bes-sri me sale 2 errores puntuales que por favor me podrian guiar.

    1.- saveFile_noui esta funcion no se encuentra
    2.- TypeError: Argument 1 of File.constructor can’t be converted to a sequence.

    Por favor su ayuda

    1. Revísale por favor el código que está publicado en la continuación del artículo.

  5. Muy buenas, primero que todo excelente aporte, he estado probando el javascript y me sale sigue saliendo el error de Firma Invalida, ya he probado en todos los enlaces, agradeceria mucho la ayuda.

    1. Hola, por favor revisa la actualización del artículo sobre XaDES-BES, al final, donde se listan algunos errores comunes en la implementación, saludos.

  6. Hola, escribo estas líneas para felicitarte por el artículo y contarte que lo acabo de implentar la firma en visual .net todo nativo, me ha tomado como dos semanas pero sin ésta ayuda créeme que hasta ahora no vería la luz.
    Solo ésta acotación::
    Donde pones: ds:X509SerialNumber: el número serial del certificado digital, podrías aclarar que hay que ponerlo como decimal y no hexadecimal (eso me di cuenta viendo el código).

    Gracias

    1. No existirá manera en que puedas compartirmelo? Quisiera poder implementarlo en PHP y no se por donde arrancar, seria de gran ayuda verlo desarrollado en .net. Gracias!

    2. Hola, gracias por comentar, te respondí por interno. Está compartida la implementación en JavaScript a fin de ser una prueba de concepto del algoritmo, y que sirva de referencia para ser desarrollado en otros lenguajes. En PHP de pronto te recomendaría revisar una librería que implemente XML digital signature, una búsqueda rápida en Google te puede dar algunos ejemplos.

  7. Hola SR. Edgar Valarezo puede escribirme a mi correo nu????em@hotmail.com o agregarme a facebook masterzor LEMR quiero hacerle unas preguntas o Whatsapp +593098???0379 por favor

    1. Estimado mil disculpas, nuestro tiempo es muy escaso y solo respondemos llamadas y whatsapp bajo contrato de soporte. Si gustas puedes dejar tus dudas en este espacio de comentarios para que las respuestas sirvan al resto, o también puedes contratar nuestros servicios de soporte. Saludos.

  8. Hay alguna manera de hacer que esos archivos los cargue automaticamente (sin necesidad del formulario) estoy tratando de adaptarlo a una implementación que tengo (lo unico que me falta es firmar el documento) pero me está costando hacerlo en PHP.

    1. Hola, sí es posible hacerlo en PHP de manera automática con este algoritmo, tenemos experiencia en lograrlo con PHP, si gustas te podemos mandar una cotización de nuestros servicios. Saludos.

  9. O conoces una manera de pasarle los archivos que no sea a través de un input en html? osea que yo tenga fijo la ruta del P12 y la factura XML (la cual genero en el sistema) se las pase ambas y así se genere el archivo firmado, tendrás algo en mente? saludos.. PD. Soy el del comentario anterior.

    1. La única manera de pasarle los archivos sin el input HTML y que lea directamente del disco es cargando los archivos en el servidor.

  10. Estoy probando este código y me parece un muy buen artículo, pero estoy teniendo problemas para validar la firma con los WS del SRI, creo que el mi error esta en la forma en que leo el XML de la factura, debido a que al comparar con otro firmador (colocando las mismas constantes) el valor del SHA1 de la factura que va en SignedInfo varía, he probado borrand espacios entre tags, quitanto saltos de líneas, etc y sigo obteniendo valores diferentes para el SHA1 de la factura? muchas gracias, espero me puedas ayudar con esto

    1. Hola, verifícale la forma canónica del XML de la factura antes de firmarle, y también ten cuidado con la codificación de caracteres, tal vez alguna tilde o eñe te esté dando problemas. Te puede ayudar la segunda parte del artículo, donde extiendo la manera de probar el algoritmo. Saludos.

  11. Hola, tengo implementada la factura electronica en PHP y usando el SOAP nativo de PHP le envio el XML generado al Web Service del SRI de recepcion y me sale RECIBIDA pero me indica error en la estructura del XML, corro el XML en el programa SOAPUI y me lo recibe perfectamente, al ejecutar el WS de autorizacion me dice error en la firma. Necesito el soporte tecnico que me puedas brindar para solucionar mi problema, me puedes contactar al siguiente numero 098???2600 o al 098???6578 para convenir el precio, lo necesito urgente.

  12. Hola, muchas gracias por la explicación, parece ser que es la única que detalla como funciona la firma electrónica. Tengo unas preguntas espero que me puedas ayudar.
    1) Trato de ejecutar el webservice de pruebas de autorización con el xml firmado por el jsfiddle pero me dice que la firma es invalida.
    2) En donde puedo encontrar mas informacion? De donde sacaste la informacion que expones aqui? El supuesto manual del sri no indica como implementar XAdES_bes.
    3) Si trato de autorizar un comprobante previamente no autorizado ese comprobante ya queda registrado para ellos como no autorizado o si puedo probar varias veces con el mismo comprobante? Gracias!

    1. Hola, qué bueno que en algo ayuda el artículo. Sobre tus consultas:
      1) Mira la parte final de la continuación del artículo, donde se listan algunos errores comunes. Recuerda que un espacio de más, un enter o una minúscula que era mayúscula hará que toda la firma sea distinta. También ten en cuenta que el archivo .p12 puede tener más de una firma privada, y puede que se esté firmando con la firma equivocada.
      2) Como tu lo dices, no hay documentación sobre el tema. Lo que yo hice fue ingeniería inversa del facturador del SRI, mira en el segundo artículo más detalles sobre esto.
      3) En pruebas sí se podía mandar el mismo código varias veces.

  13. Me parece un excelente artículo, el único que explica realmente el detalle de la firma, como lo debería de exponer el SRi en su manual. Yo solía tener el problema de la firma invalida cuando el xml tenía un carácter especial, es decir al momento de leer xml este tenia errores, un( &) por por ejemplo o otro caracter ~ que hacia aparecer el error, en mi caso el problema era porque al guardar el fichero en el sistema no se tomaban encuenta las respectivas validaciones y codificaciones del mismo y al leerlo ya esté venía mal.

Deja un comentario