Crear sello de CFDI con java

El sello de un CFDI es en realidad la firma electrónica de algunos campos en conjunto(cadena original) del CFDI. La firma digital brinda varios servicios de seguridad:

  • No repudio. La entidad que emite el CFDI no puede rechazar que ella/él lo emitió puesto que en teoría sólo ella/él conoce la llave privada y nadie mas.
  • Autenticación. Al verificar la firma de un CFDI sabemos quién lo emitio ya que se utiliza la correspondiente llave pública del emisor. En Criptografía de llave pública,
    siempre existen un par de llaves, la pública y privada, las cuales sirven para cifrar y descifrar el mensaje.
  • Integridad. Al firmar el CFDI se realiza un subproceso que genera una cadena única, producto del uso de funciones hash. Si se altera el documento posteriormente o al menos los
    campos que se uilizan para generar la cadena original, la cadena única generada debe cambiar completamente y el resultado de la firma será diferente a la original.

Hay dos procesos principales en la generación del sello:

  • Obtener una cadena única que identifica a la cadena original y por consecuencia al CFDI. Esto se realiza utilizando un algoritmo que usa funciones hash como SHA256 para CFDI 3.3 (SHA1 para CFDI 3.2)
  • Cifrar con la llave privada la cadena única generada en el paso anterior. Para este paso se utiliza el algoritmo RSA.

En código java se ve de la siguiente manera:

...
//archivoLlavePrivada: es el archivo de la llave privada
//keyWord es el password de la llave privada
PKCS8Key pkcs8 = new PKCS8Key(Base64.getDecoder().decode(archivoLlavePrivada), keyWord.toCharArray());
KeyFactory privateKeyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec pkcs8Encoded = new PKCS8EncodedKeySpec(pkcs8.getDecryptedBytes());
PrivateKey privateKey = privateKeyFactory.generatePrivate(pkcs8Encoded);
signature = Signature.getInstance("SHA256withRSA");
signature.update(cadenaOriginal);
String firma = new String(Base64.getEncoder().encode(signature.sign()));

logger.debug("Firma digital del CFDI:" + firma);
...

El archivo que contiene la llave privada se puede obtener así:

...
File file = new File(path); /path: es la ruta del archivo de llave privada
FileInputStream fileInputStream = new FileInputStream(file);
byte[] fileBytes = new byte[fileInputStream.available()];
fileInputStream.read(fileBytes);
fileInputStream.close();
               
String fileString = new String(Base64.getEncoder().encode(fileBytes));
               
return fileString;
...

Notas:

  • Certificado digital. Este es una estructura de datos que entre otras cosas contiene la llave pública que posteriormente será ocupada para verificar la firma.
  • Realmente el documento oficial es el XML, puesto que contiene los atributos para generar la cadena original, generar el sello y verificar el CFDI. El PDF no importa ya que no es un documento que se utilice para la verificación del sello.