Timbraje de Documentos Tributarios Eléctronicos con Java (Sólo para Chile)
Introducción.
Este tutorial tiene como objetivo enseñar cómo timbrar un documento electrónico, como una factura, guía de despacho o nota de crédito, mediante el uso de una firma digital. Para realizar este proceso, se necesita un archivo XML con los datos necesarios, y este tutorial asume que ya tienes un XML preparado. Si no lo tienes, se sugiere generarlo previamente. El ejemplo está basado en Java, aunque también es posible implementarlo en otros lenguajes como C#, Python o Ruby.
Asumiendo que vamos a trabajar con este documento XML
<?xml version="1.0" encoding="ISO-8859-1"?> <DTE version="1.0"> <Documento ID="F27T33"> <Encabezado> <IdDoc> <TipoDTE>33</TipoDTE> <Folio>27</Folio> <FchEmis>2003-09-08</FchEmis> </IdDoc> <Emisor> <RUTEmisor>97975000-5</RUTEmisor> <RznSoc>RUT DE PRUEBA</RznSoc> <GiroEmis>Insumos de Computacion</GiroEmis> <Acteco>31341</Acteco> <CdgSIISucur>1234</CdgSIISucur> <DirOrigen>Teatinos 120, Piso 4</DirOrigen> <CmnaOrigen>Santiago</CmnaOrigen> <CiudadOrigen>Santiago</CiudadOrigen> </Emisor> <Receptor> <RUTRecep>8414240-9</RUTRecep> <RznSocRecep>JORGE GONZALEZ LTDA</RznSocRecep> <GiroRecep>COMPUTACION</GiroRecep> <DirRecep>SAN DIEGO 2222</DirRecep> <CmnaRecep>LA FLORIDA</CmnaRecep> <CiudadRecep>SANTIAGO</CiudadRecep> </Receptor> <Totales> <MntNeto>426226</MntNeto> <TasaIVA>18</TasaIVA> <IVA>76720</IVA> <MntTotal>502946</MntTotal> </Totales> </Encabezado> <Detalle> <NroLinDet>1</NroLinDet> <CdgItem> <TpoCodigo>INT1</TpoCodigo> <VlrCodigo>011</VlrCodigo> </CdgItem> <NmbItem>Cajon AFECTO</NmbItem> <DscItem/> <QtyItem>139</QtyItem> <PrcItem>1807</PrcItem> <MontoItem>251173</MontoItem> </Detalle> <Detalle> <NroLinDet>2</NroLinDet> <CdgItem> <TpoCodigo>INT1</TpoCodigo> <VlrCodigo>022</VlrCodigo> </CdgItem> <NmbItem>Relleno AFECTO</NmbItem> <DscItem/> <QtyItem>59</QtyItem> <PrcItem>2967</PrcItem> <MontoItem>175053</MontoItem> </Detalle> <Referencia> <NroLinRef>1</NroLinRef> <TpoDocRef>SET</TpoDocRef> <FolioRef>1</FolioRef> <FchRef>2003-08-01</FchRef> <CodRef>1</CodRef> <RazonRef>Caso 4256-1</RazonRef> </Referencia> </Documento> </DTE>
Mensaje a Firmar
<DD><RE>97975000-5</RE><TD>33</TD><F>27</F><FE>2003-09-08</FE> <RR>8414240-9</RR><RSR>JORGE GONZALEZ LTDA</RSR><MNT>502946</M NT><IT1>Cajon AFECTO</IT1><CAF version="1.0"><DA><RE>97975000- 5</RE><RS>RUT DE PRUEBA</RS><TD>33</TD><RNG><D>1</D><H>200</H> </RNG><FA>2003-09-04</FA><RSAPK><M>0a4O6Kbx8Qj3K4iWSP4w7KneZYe J+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw==< /M><E>Aw==</E></RSAPK><IDK>100</IDK></DA><FRMA algoritmo="SHA1 withRSA">g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAa vCALKByHzdYAfZ3LhGTXCai5zNxOo4lDQ==</FRMA></CAF><TSTED>2003-09 -08T12:28:31</TSTED></DD>
Clave RSA Privada obtenida del CAF
-----BEGIN RSA PRIVATE KEY----- MIIBOwIBAAJBANGuDuim8fEI9yuIlkj+MOyp3mWHifoP6a4oWLSBKJSrd3MpEsZd czvL0l7t/e0IU5rF+0gRLnU1Mfvtsw1wYWcCAQMCQQCLyV9FxKFLW09yWw7bVCCd xpRDr7FRX/EexZB4VhsNxm/vtJfDZyYle0Lfy42LlcsXxPm1w6Q6NnjuW+AeBy67 AiEA7iMi5q5xjswqq+49RP55o//jqdZL/pC9rdnUKxsNRMMCIQDhaHdIctErN2hC IP9knS3+9zra4R+5jSXOvI+3xVhWjQIhAJ7CF0R0S7SIHHKe04NUURf/7RvkMqm1 08k74sdnXi3XAiEAlkWk2vc2HM+a1sCqQxNz/098ketqe7NuidMKeoOQObMCIQCk FAMS9IcPcMjk7zI2r/4EEW63PSXyN7MFAX7TYe25mw== -----END RSA PRIVATE KEY-----
El resultado obtenido de la firma RSA-SHA1, utilizado la llave privada sobre el mensaje es:
pqjXHHQLJmyFPMRvxScN7tYHvIsty0pqL2LLYaG43jMmnfiZfllLA0wb32lP+HBJ /tf8nziSeorvjlx410ZImw==
Dependencias de Maven
Para este ejemplo, se utilizan dos dependencias principales que deben añadirse al archivo pom.xml
de tu proyecto Maven:
BouncyCastle: Es una biblioteca que ofrece una gama completa de herramientas criptográficas, incluyendo soporte para la firma de documentos digitales. En este caso, se utiliza para generar una firma digital con el algoritmo RSA.
Log4j: Aunque no es directamente utilizado en el ejemplo de código, Log4j es necesario porque es una dependencia requerida por BouncyCastle. Log4j ayuda a gestionar los logs del sistema, lo cual es útil para monitorear y depurar la aplicación, especialmente cuando se maneja criptografía y firma digital.
Ejemplo de archivo pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ejemplo</groupId>
<artifactId>EjemploTimbre</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
<exec.mainClass>com.ejemplo.ejemplotimbre.EjemploTimbre</exec.mainClass>
</properties>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
</dependencies>
</project>
Ejemplo de Código
A continuación, se muestra un ejemplo de cómo implementar el timbre electrónico utilizando BouncyCastle para la firma digital de un mensaje XML. Este código usa la clave privada para generar una firma sobre el mensaje XML y luego la codifica en Base64.
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
*/
package com.ejemplo.ejemplotimbre;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
*
* @author esteban
*/
public class EjemploTimbre {
public static void main (String[] args ) throws FileNotFoundException, IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
Security.addProvider(new BouncyCastleProvider());
KeyFactory factory = KeyFactory.getInstance("RSA", "BC");
try {
String mensaje = "<DD><RE>97975000-5</RE><TD>33</TD><F>27</F><FE>2003-09-08</FE>";
mensaje = mensaje +"<RR>8414240-9</RR><RSR>JORGE GONZALEZ LTDA</RSR><MNT>502946</M";
mensaje = mensaje +"NT><IT1>Cajon AFECTO</IT1><CAF version=\"1.0\"><DA><RE>97975000-";
mensaje = mensaje+"5</RE><RS>RUT DE PRUEBA</RS><TD>33</TD><RNG><D>1</D><H>200</H>";
mensaje = mensaje+"</RNG><FA>2003-09-04</FA><RSAPK><M>0a4O6Kbx8Qj3K4iWSP4w7KneZYe";
mensaje = mensaje+"J+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw==<";
mensaje = mensaje+"/M><E>Aw==</E></RSAPK><IDK>100</IDK></DA><FRMA algoritmo=\"SHA1";
mensaje = mensaje+"withRSA\">g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAa";
mensaje = mensaje+"vCALKByHzdYAfZ3LhGTXCai5zNxOo4lDQ==</FRMA></CAF><TSTED>2003-09";
mensaje = mensaje+"-08T12:28:31</TSTED></DD>";
String claversa = """
-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANGuDuim8fEI9yuIlkj+MOyp3mWHifoP6a4oWLSBKJSrd3MpEsZd
czvL0l7t/e0IU5rF+0gRLnU1Mfvtsw1wYWcCAQMCQQCLyV9FxKFLW09yWw7bVCCd
xpRDr7FRX/EexZB4VhsNxm/vtJfDZyYle0Lfy42LlcsXxPm1w6Q6NnjuW+AeBy67
AiEA7iMi5q5xjswqq+49RP55o//jqdZL/pC9rdnUKxsNRMMCIQDhaHdIctErN2hC
IP9knS3+9zra4R+5jSXOvI+3xVhWjQIhAJ7CF0R0S7SIHHKe04NUURf/7RvkMqm1
08k74sdnXi3XAiEAlkWk2vc2HM+a1sCqQxNz/098ketqe7NuidMKeoOQObMCIQCk
FAMS9IcPcMjk7zI2r/4EEW63PSXyN7MFAX7TYe25mw==
-----END RSA PRIVATE KEY-----
""";
/* QUITO LOS ENCABEZADOS Y PIES DE LA CLAVE RSA */
String claveLimpia = claversa
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
.replace("-----END RSA PRIVATE KEY-----", "")
.replaceAll("\\s+", ""); // Elimina todos los espacios en blanco
byte[] claveBytes = Base64.getDecoder().decode(claveLimpia);
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(claveBytes);
PrivateKey priv = factory.generatePrivate(privKeySpec);
System.out.println("Clave privada generada correctamente.");
/* CONVIERTO EL MENSAJE EN ARREGELO DE BYTES */
byte[] data = mensaje.getBytes("ISO-8859-1");
/* COMPUTO LA FIRMA CON BOUNCY CASTLE EN FUNCION DE LA CLAVE PRIVADA Y EL MENSAJE */
Signature signature = Signature.getInstance("SHA1withRSA", "BC");
signature.initSign(priv);
signature.update(data);
System.out.println("Firmando el mensaje...");
/* EL RESULTADO DE LA FIRMA LA LLEVO A UN ARREGLO DE BYTES */
byte[]resultado = signature.sign();
/* ENCODEO EL RESULTADO DE LA FIRMA DEL TIMBRE EN BASE64 */
System.out.println("Firma generada en Base64: ");
String encoded = Base64.getEncoder().encodeToString(resultado);
System.out.println(encoded);
} catch (InvalidKeySpecException e) {
System.err.println("Error al generar la clave privada: " + e.getMessage());
}
}
}
En el ejemplo de código proporcionado, el proceso para generar una firma digital es el siguiente:
- Generación de la Clave Privada: El código recibe una clave privada RSA en formato Base64. Esta clave se procesa para eliminar los encabezados y pies de la clave, y luego se convierte en un arreglo de bytes que se utiliza para generar la clave privada en el formato adecuado.
- Firma del Mensaje: El mensaje XML que contiene los datos a firmar se convierte en un arreglo de bytes. Con la clave privada RSA y el mensaje, se calcula la firma digital utilizando el algoritmo SHA1withRSA a través de BouncyCastle.
- Resultado de la Firma: Una vez calculada la firma, esta se codifica en Base64, para que pueda ser fácilmente integrada o transmitida dentro de un documento o mensaje XML.
Salida Esperada
Al ejecutar el código, el mensaje firmado será impreso en la consola como un string codificado en Base64. Esta firma puede ser luego incorporada en el XML como parte del timbre electrónico.
El documento con el timbre realizado debería quedar así
<?xml version="1.0" encoding="ISO-8859-1"?> <DTE version="1.0"> <Documento ID="F27T33"> <Encabezado> <IdDoc> <TipoDTE>33</TipoDTE> <Folio>27</Folio> <FchEmis>2003-09-08</FchEmis> </IdDoc> <Emisor> <RUTEmisor>97975000-5</RUTEmisor> <RznSoc>RUT DE PRUEBA</RznSoc> <GiroEmis>Insumos de Computacion</GiroEmis> <Acteco>31341</Acteco> <CdgSIISucur>1234</CdgSIISucur> <DirOrigen>Teatinos 120, Piso 4</DirOrigen> <CmnaOrigen>Santiago</CmnaOrigen> <CiudadOrigen>Santiago</CiudadOrigen> </Emisor> <Receptor> <RUTRecep>8414240-9</RUTRecep> <RznSocRecep>JORGE GONZALEZ LTDA</RznSocRecep> <GiroRecep>COMPUTACION</GiroRecep> <DirRecep>SAN DIEGO 2222</DirRecep> <CmnaRecep>LA FLORIDA</CmnaRecep> <CiudadRecep>SANTIAGO</CiudadRecep> </Receptor> <Totales> <MntNeto>426226</MntNeto> <TasaIVA>18</TasaIVA> <IVA>76720</IVA> <MntTotal>502946</MntTotal> </Totales> </Encabezado> <Detalle> <NroLinDet>1</NroLinDet> <CdgItem> <TpoCodigo>INT1</TpoCodigo> <VlrCodigo>011</VlrCodigo> </CdgItem> <NmbItem>Cajon AFECTO</NmbItem> <DscItem/> <QtyItem>139</QtyItem> <PrcItem>1807</PrcItem> <MontoItem>251173</MontoItem> </Detalle> <Detalle> <NroLinDet>2</NroLinDet> <CdgItem> <TpoCodigo>INT1</TpoCodigo> <VlrCodigo>022</VlrCodigo> </CdgItem> <NmbItem>Relleno AFECTO</NmbItem> <DscItem/> <QtyItem>59</QtyItem> <PrcItem>2967</PrcItem> <MontoItem>175053</MontoItem> </Detalle> <Referencia> <NroLinRef>1</NroLinRef> <TpoDocRef>SET</TpoDocRef> <FolioRef>1</FolioRef> <FchRef>2003-08-01</FchRef> <CodRef>1</CodRef> <RazonRef>Caso 4256-1</RazonRef> </Referencia> <TED version="1.0"> <DD> <RE>97975000-5</RE> <TD>33</TD> <F>27</F> <FE>2003-09-08</FE> <RR>8414240-9</RR> <RSR>JORGE GONZALEZ LTDA</RSR> <MNT>502946</MNT> <IT1>Cajon AFECTO</IT1> <CAF version="1.0"> <DA> <RE>97975000-5</RE> <RS>RUT DE PRUEBA</RS> <TD>33</TD> <RNG> <D>1</D> <H>200</H> </RNG> <FA>2003-09-04</FA> <RSAPK> <M>0a4O6Kbx8Qj3K4iWSP4w7KneZYeJ+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw ==</M> <E>Aw==</E> </RSAPK> <IDK>100</IDK> </DA> <FRMA algoritmo="SHA1withRSA">g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAavCALKByHzdYAfZ3LhGT XCai5zNxOo4lDQ==</FRMA> </CAF> <TSTED>2003-09-08T12:28:31</TSTED> </DD> <FRMT algoritmo="SHA1withRSA">pqjXHHQLJmyFPMRvxScN7tYHvIsty0pqL2LLYaG43jMmnfiZfllLA0wb32lP+HBJ /tf8nziSeorvjlx410ZImw==</FRMT> </TED> <TmstFirma>2003-09-08T12:28:31</TmstFirma> </Documento> </DTE>
Conclusión Este tutorial mostró los pasos básicos para timbrar un DTE mediante firma digital en Java. Si bien el ejemplo se centró en el algoritmo RSA-SHA1, puedes adaptarlo para usar otros métodos criptográficos según las necesidades de tu implementación. Si tienes preguntas o necesitas soporte adicional, no dudes en comentar.
Comentarios
Publicar un comentario