Firma electrónica XAdES-BES: Soluciones comunes

Luego de más de un año de ser publicada la primera parte de esta serie de artículos sobre la firma electrónica XAdES-BES de los comprobantes tributarios electrónicos del Ecuador, con una segunda parte donde se detalla el proceso de ingeniería inversa que fue utilizado, y tras numerosas implementaciones exitosas en varias empresas, a continuación se presenta un listado de problemas comunes encontrados y la manera en que fueron solucionados.

Soluciones a problemas con firma electrónica XAdES-BES
Photo by looseid

No se puede enviar la información por el servicio web del SRI

Este error por lo general se debe a que no se ha codificado previamente el comprobante electrónico a Base 64. Otra causa suele ser el código de autorización, el cual es un código de 49 dígitos con la siguiente estructura:

  • Ocho (8) dígitos para la fecha de emisión, en formato DDMMYYYY, es decir dos dígitos para el día, dos dígitos para el mes y cuatro dígitos para el año.
  • Dos (2) dígitos para el código del tipo de comprobante:
    • 01 factura,
    • 04 nota de crédito,
    • 05 nota de débito,
    • 06 guía de remisión,
    • 07 retención.
  • Trece (13) dígitos para el RUC, tomando en cuenta las validaciones y los problemas que tienen estos códigos, los cuales ya fueron hablados en otro artículo al respecto del RUC.
  • Un (1) dígito para el tipo de ambiente:
    • 1 es pruebas
    • 2 es producción.
  • Tres (3) dígitos para el establecimiento (por defecto 001).
  • Tres (3) dígitos para el punto de emisión (por defecto 001).
  • Nueve (9) dígitos para el secuencial.
  • Ocho (8) dígitos para el código único.
  • Un dígito para el tipo de emisión, donde 1 es normal.
  • Un dígito para el dígito verificador, el cual se lo calcula con el módulo 11 de los anteriores 48 dígitos.

La implementación de un generador de este código de autorización se lo puede encontrar en el repositorio de GitHub, en la implementación de ejemplo que se hizo para el algoritmo de firmado.

El hash de los nodos y/o la firma electrónica XAdES-BES no coincide con lo generado por el facturador del SRI

Este problema por lo general surge por mal formato de la estructura y/o los caracteres del archivo XML del comprobante:

  • En Windows, la mayoría de editores de texto agregan dos caracteres en cada salto de línea: CR (Carriage Return o retorno de carro, expresado también como \r) y LF (Line Feed o salto de línea, expresado también como \n). Esto puede hacer fallar al cálculo de los hash y de la firma electrónica XAdES-BES, por lo que primero se deberían quitar del texto los caracteres CR.
  • Se debe revisar que la codificación de caracteres sea correcta, por lo general UTF-8.
  • No se admite que el XML tenga tags con auto cierre, es decir en lugar de <tag /> se debería poner <tag></tag>.
  • Hay que revisar con mucho cuidado los espacios antes, y después de los tags, así como los saltos de línea. Por ejemplo en la forma canónica no se deben quitar los saltos de línea, solo hay que agregar los namespaces. También hay que revisar los espacios entre los atributos del tag.
  • Se debe tener estricto respeto de mayúsculas y minúsculas, por ejemplo si el atributo se llama PropertyID, daría error poner PropertyId, con la “d” final minúscula.

La firma electrónica XAdES-BES generada no es correcta

Los archivos .P12 pueden tener dentro varios certificados y varias llaves privadas, las cuales a veces no son leídas por las librerías utilizadas. En algunos casos se deben extraer estos datos, en especial la clave privada, para realizar la firma en sí. Esto se debe a que dentro del archivo P12 pueden haber dos llaves privadas: una para verificación y otra para realizar el firmado. Esto a veces confunde a las implementaciones que leen el archivo P12 y solo cargan la clave privada de verificación. Se producen fallas silenciosas al cargar la clave privada incorrecta para el firmado. De igual manera, se debe tener cuidado en tomar el certificado correcto para el firmado, que pueden ser cuatro.

Dependiendo de la tecnología que se escojan para la implementación, es más o menos fácil implementar la firma electrónica XAdES-BES. Esto se debe a la estructura del certificado digital que genera el Banco Central, el cual contiene dos llaves privadas. Tal situación puede confundir a las librerías de firma electrónica XAdES-BES, ya que toman la primera llave privada cuando deberían tomar la segunda. Por ejemplo, en PHP no hay manera con las librerías estándar de tomar la segunda llave privada, ya que por defecto toma solamente la primera y no da opción de escoger. Lo que se ha tenido que realizar en estos casos es extraer la segunda llave privada en un archivo aparte a través de la herramienta OpenSSL, para luego realizar la firma electrónica XAdES-BES con esta llave privada extraída.

Extracción manual de la llave privada válida

Una vez instalado OpenSSL se pueden extraer las llaves privadas de un certificado del Banco Central con el siguiente comando:

openssl pkcs12 -in juanito_perez.p12 -nocerts -out llaves_privadas_juanito_perez.pem

Al abrir el archivo generado .PEM con cualquier editor de texto se encontrará con dos llaves privadas: la una dice Decryption Key y la otra dice Signing Key. El truco es extraer esta segunda llave privada con el comentario “Signing Key” a un archivo aparte (incluyendo los renglones del principio y final de la llave, con el texto BEGIN ENCRYPTED PRIVATE KEY y END ENCRYPTED PRIVATE KEY), para luego realizar la firma electrónica con este archivo en lugar de usar todo el certificado .P12 y así evitar el uso de la llave privada con comentario Decryption Key en la obtención de la firma electrónica XAdES-BES.

Conclusión

La implementación de la firma electrónica XAdES-BES es un proceso que se lo puede realizar en cualquier lenguaje de programación. Este desarrollo requiere de mucho cuidado para poder realizarlo bien. Se debe evaluar con mucho cuidado si vale la pena realizar esta implementación, ya que en la actualidad ya existen librerías en Java de comprobada efectividad que realizan esta tarea muy bien, y que han sido probadas por varios años en sistemas en producción. Las ventajas de una implementación en otro lenguaje serán:

  • El control de todo el sistema dentro de una misma tecnología;
  • El manejo de casos especiales que no soportan las librerías ya implementadas; y,
  • De paso también el aprendizaje que se llega a adquirir, que no es poca cosa.

Sin embargo, en la gran mayoría de los casos reales, estas ventajas son superadas por las desventajas. Todo sistema nuevo trae un riesgo: errores no detectados que se filtran a producción. Es por ello que se debe evaluar con mucho cuidado la conveniencia de realizar esta implementación. En caso de juzgar conveniente su realización, es recomendable contar con un plan de pruebas para el control de calidad.

Firma electrónica XAdES-BES: Soluciones comunes
5/5, de 1 voto

2 comentarios sobre “Firma electrónica XAdES-BES: Soluciones comunes

  1. Excelente colaboracion. La encontre de casualidad. Tengo una sola pregunta. Para calcular en PHP etsi:certdigest ds:digestvalue he realizado lo siguiente: genere con openssl un archivo con formado de codificacion .der partiendo de la clave_publica.pem y posteriormente calcule con sha1 y base64_encode. Esta es la forma correcta de calcularlo? Quisiera saber ya que todavia no me funciona la autorizacion del SRI y sigo teniendo el error 39 FIRMA INVALIDA.

    1. Hola. La forma de calcular el ds:digestvalue está bien, pero debes tener cuidado ya que en PHP la función openssl_pkcs12_read no reconoce todos los certificados del archivo .P12 sino solo el primero. Lo que tocaría hacer es extraer el certificado correcto para el firmado con comandos externos, revísale con todo el contenido de la variable que te devuelve openssl_pkcs12_read, en el elemento extracerts del arreglo:

      $certificado_p12 = file_get_contents(“nombre_del_propietario_del_certificado.p12”);
      if (openssl_pkcs12_read($certificado_p12, $pkcs12, “MiClaveDelCertificado”)) {
      $certificado = $pkcs12[“extracerts”][0];
      $certificado = str_replace(‘—–BEGIN CERTIFICATE—–‘, ”, $certificado);
      $certificado = str_replace(‘—–END CERTIFICATE—–‘, ”, $certificado);
      $certificado = str_replace(“\n”, “”, $certificado);
      $certificado = str_split($certificado, 76);
      $certificado = implode(“\n”, $certificado);
      $certificado_b64 = str_replace(“\n”, “”, $certificado);
      $hash_certificado_der = base64_encode(hash(“sha1”, base64_decode($certificado_b64), true));
      echo $hash_certificado_der;
      }

Deja un comentario