← Blog
DTE

Cómo firmar un XML DTE: firma electrónica XMLDSig del SII

Cómo firmar un XML DTE en Chile: las dos firmas del documento (TED y firma XMLDSig con certificado), el modo Detached, la codificación ISO-8859-1 y los errores comunes.

Equipo Emitir6 min de lectura

Firmar un DTE es donde más developers se traban, y no por la criptografía en sí: se traban porque un DTE no tiene una firma, tiene dos, y cada una sigue reglas distintas. Este artículo separa esas dos firmas, explica qué es XMLDSig, cómo se aplica con tu certificado digital y por qué la codificación ISO-8859-1 puede romperte la firma aunque el documento se vea perfecto.

Un DTE tiene dos firmas, no una

Antes de escribir una línea de código, hay que tener clara la distinción que el instructivo del SII da por sentada:

  1. La firma del TED (el FRMT). El Timbre Electrónico (TED) lleva su propia firma, el FRMT. Se genera con la llave privada RSA que viene dentro del CAF y usa el algoritmo SHA1withRSA. Esta firma no sigue el estándar XMLDSig: por las restricciones de espacio del código de barras PDF417 de la representación impresa, el SII definió un esquema propio en su instructivo. Prueba que el documento está dentro de un rango de folios autorizado.
  2. La firma del DTE (y la del envío). El documento completo, y luego el sobre de envío, se firman con el estándar XML Signature (XMLDSig) de la W3C, usando tu certificado digital de emisor en modo Detached. Esta firma asegura integridad (el XML no fue alterado) e identidad (quién firmó).

Son dos mundos: el TED se firma con una llave que te da el SII en el CAF; el DTE se firma con un certificado que compras tú. Confundirlas es el primer error. El TED lo tratamos en detalle en qué es el TED, el timbre electrónico de un DTE; acá nos concentramos en la firma XMLDSig del DTE.

Qué es XMLDSig

XMLDSig (XML Signature) es el estándar de la W3C para firmar documentos XML. En vez de firmar un blob opaco, define una estructura Signature que contiene:

  • Una o más referencias (Reference) al contenido firmado, identificado por su atributo de id.
  • Por cada referencia, su digest (un hash del contenido ya canonicalizado).
  • Un SignatureValue: el digest del bloque de referencias, firmado con la llave privada del certificado.
  • El KeyInfo, que incluye el certificado público para que el verificador (el SII) reconstruya y valide todo.

La idea: el SII recalcula los digests sobre tu XML, verifica que coincidan (integridad) y comprueba la firma contra el certificado (identidad).

El certificado digital

La firma XMLDSig se aplica con tu certificado digital de firma electrónica, emitido por una Autoridad Certificadora acreditada ante el SII (e-certchile, e-Sign, Acepta). Normalmente lo recibes como archivo .p12 / .pfx (formato PKCS#12), que empaqueta la llave privada y el certificado, protegido por una contraseña. La vigencia suele ser de uno a tres años.

Este certificado es independiente del CAF. El CAF te da el rango de folios [D,H], la llave pública RSA (RSAPK) y la llave privada para el TED. El certificado te lo da la AC y sirve para la firma XMLDSig.

Cómo se firma el DTE: el flujo

A grandes rasgos, el flujo es siempre el mismo, sin importar el lenguaje. En pseudocódigo:

# 1. Cargar el certificado desde el .p12/.pfx
llave_privada, certificado = cargar_pkcs12("emisor.p12", password)

# 2. Serializar el DTE en ISO-8859-1, con un salto de línea al final de cada tag
xml = serializar(dte, encoding="ISO-8859-1")

# 3. Canonicalizar el nodo a firmar para calcular el digest:
#    - eliminar fines de línea
#    - eliminar blancos y tabulaciones ENTRE tags
#    - resolver la referencia a NameSpaces
#    sin tocar el contenido de los elementos terminales
canon = canonicalizar(nodo_dte)
digest = base64(sha1(canon))   # según el algoritmo declarado en la Reference

# 4. Construir el bloque <Signature> (Detached) con esa Reference + su digest
signed_info = construir_signed_info(reference, digest)

# 5. Firmar el SignedInfo canonicalizado con la llave privada
signature_value = base64(firmar_rsa(canonicalizar(signed_info), llave_privada))

# 6. Insertar <SignatureValue> y <KeyInfo> (con el certificado) en el bloque Signature
dte_firmado = insertar_signature(xml, signature_value, certificado)

Es pseudocódigo ilustrativo, no una API concreta: la mayoría de los stacks (Java, .NET, Python, Node) traen soporte de XMLDSig o librerías de terceros que hacen los pasos 3 a 6, pero cada una expone nombres distintos. Lo que no cambia entre librerías son las reglas de codificación del SII.

El orden y las reglas que sí importan

Estas son las reglas del instructivo que rompen la verificación cuando no se respetan:

  • Codificación ISO-8859-1. Todo el XML va en ISO-8859-1, no UTF-8.
  • Saltos de línea por tag. Se inserta un salto de línea al final de cada tag al serializar.
  • Reglas del digest. Para calcular el digest se eliminan los fines de línea, los blancos y tabulaciones entre tags y se resuelve la referencia a NameSpaces; el digest se calcula sobre el string resultante. No se modifica el contenido de los elementos terminales.
  • Modo Detached. El bloque Signature va separado y referencia al contenido por su id.

Para comparar: el FRMT del TED se firma sobre el string del bloque DD eliminando los caracteres que hay entre el tag de cierre de un elemento y el de inicio del siguiente, sin tocar el contenido de los elementos terminales. Misma filosofía de "normaliza el espaciado, no el contenido", distinto estándar.

Errores comunes de firma

  • Firmar en UTF-8. El digest se calcula sobre bytes distintos a los que espera el SII y la firma no valida. Debe ser ISO-8859-1.
  • Caracteres especiales sin escapar. Hay que usar las entidades XML predefinidas (&&amp;, <&lt;, etc.) y respetar ISO-8859-1. Si no, el documento se cae en el paso de Schema con un error de tipo "Invalid Character", antes de que el SII mire tu firma. Lo desarrollamos en DTE rechazado por Invalid Character e ISO-8859-1.
  • Canonicalización propia que altera el contenido. Eliminar blancos dentro de un elemento terminal, o reformatear valores, cambia el digest. Solo se normaliza el espaciado entre tags.
  • Confundir las dos firmas. Intentar firmar el TED con el certificado, o el DTE con la llave RSA del CAF, no funciona: cada firma tiene su llave.
  • Reindentar el XML después de firmar. Cualquier "pretty print" posterior a la firma reescribe los espacios y el digest deja de coincidir. Se firma al final, y no se vuelve a tocar.

Recuerda el orden de validación del SII: 1) Schema, 2) Firma Digital, 3) Timbre Electrónico. Un fallo en un paso detiene los siguientes. Si tu envío rebota en firma, suele ser codificación o canonicalización; si rebota antes, es schema. Las causas más frecuentes están reunidas en por qué el SII rechaza un DTE.

Verifica antes de enviar

Antes de mandar el envío al SII, vale la pena revisar la estructura y la codificación del XML localmente. Puedes usar el validador de XML DTE para chequear el documento sin subirlo a ningún servidor.

Firmar bien el DTE es trabajoso justamente porque hay dos firmas, dos llaves y un conjunto de reglas de codificación que no perdonan. Emitir firma el XML del DTE con XMLDSig y genera el TED por ti desde el backend, respetando la codificación ISO-8859-1 y la canonicalización que pide el SII, para que no tengas que pelear con el digest a mano. Estamos en etapa de acceso temprano: súmate a la lista de espera.

Preguntas frecuentes

¿Cuántas firmas tiene un DTE y en qué se diferencian?+

Un DTE tiene dos firmas distintas. La primera es el FRMT, la firma del Timbre Electrónico (TED): se calcula sobre el bloque DD con la llave privada RSA que entrega el CAF y usa SHA1withRSA. No sigue el estándar XMLDSig por las restricciones de espacio del código PDF417; se rige por el instructivo del SII. La segunda es la firma del propio DTE y la del envío, que sí sigue el estándar XML Signature (XMLDSig) de la W3C y se aplica con el certificado digital del emisor. La primera prueba el folio autorizado; la segunda prueba integridad e identidad del firmante.

¿Qué es XMLDSig y por qué lo usa el SII?+

XMLDSig (XML Signature) es el estándar de firma digital sobre XML definido por la W3C. El SII lo usa para firmar el DTE y el sobre de envío porque permite asegurar la integridad del documento (que no se alteró) y la identidad del firmante (el emisor), apoyándose en su certificado digital. Es un estándar abierto e interoperable, a diferencia del FRMT del TED, que es un esquema propio acotado por el espacio del PDF417.

¿En qué modo se firma el DTE: Enveloped o Detached?+

La firma del DTE y del envío se aplica en modo Detached (separado), según el instructivo del SII. El bloque Signature queda como un elemento separado dentro del XML y referencia al contenido firmado por su atributo de identificación. Esto es distinto de una firma Enveloped, donde el nodo Signature se anida dentro del propio elemento firmado.

¿Qué certificado digital necesito para firmar el DTE?+

Necesitas un certificado digital de firma electrónica emitido por una Autoridad Certificadora acreditada ante el SII, como e-certchile, e-Sign o Acepta. Suele entregarse como archivo .p12 o .pfx (formato PKCS#12), que contiene la llave privada y el certificado. La vigencia típica es de uno a tres años. Ese certificado es el que firma el DTE con XMLDSig; no se confunde con la llave RSA del CAF, que firma el TED.

¿Por qué la codificación ISO-8859-1 afecta a la firma?+

Porque la firma y el timbre se calculan sobre los bytes exactos del XML. El instructivo del SII exige que el documento vaya en ISO-8859-1, con saltos de línea al final de cada tag, y que para calcular el digest se eliminen los fines de línea, los blancos y tabulaciones entre tags y la referencia a NameSpaces. Si codificas en UTF-8 o cambias esas reglas de espaciado, el digest cambia y la verificación de la firma o del timbre falla, aunque el contenido visible sea el mismo.

¿En qué orden valida el SII el envío?+

El SII valida en tres pasos y en este orden: primero el Schema (estructura del XML contra los esquemas oficiales), luego la Firma Digital (XMLDSig con el certificado del emisor) y por último el Timbre Electrónico (el TED y su FRMT). Por eso un error de schema, como un carácter inválido sin escapar, detiene la validación antes de que el SII llegue siquiera a revisar tu firma.

Emite DTE del SII desde tu backend

Emitir es la API de facturación electrónica para Chile. En construcción.

Unirme a la lista de espera