sábado, 24 de abril de 2010

Validar una clave Privada con su Certificado en JAVA

Hola de nuevo, como que esto de escribir en el blog ya me me esta gustando, bueno, solo como complemento de la entrada anterior donde encriptamos un archivo con la llave privada queria escribor como validar un archivo con un certificado.

Para esto lo que se tiene que hacer es crear un objeto X509Certificat a partir un byte[] o un InputStream que contiene el archivo.cer

File archivoCer = new File("c:/certificado.cer");
InputStream fis = new FileInputStream(archivoCer);// Se maneja la excepcion
X509Certificate cert = X509Certificate.getInstance(fis);

A continuacion se crea un objeto Signature pasandole e algoritmo de encripcion, en nuestro caso es el MD5withRSA, este es el mismo objeto que utilizamos para firmar nuestro archivo, pero esta vez lo inicializaremos para verificar nuestro archivo firmado con el certificado que tenemos

Signature firma = Signature.getInstance("MD5withRSA");
firma.initVerify(cert.getPublicKey());

A continuacion le cargamos el archivo o texto sin firmar

firma.update(textoAFirmar.getBytes("UTF-8"));

Y por ultimo verificamos el archivo firmado, donde textoFirmado en un byte[] que obtuvimos despues de firmar un archivo

firma.verify(textoFirmado)

Bueno, espero que les halla servido, yo tuve que utilizar esto para verificar que el certifiado fuera valido con respecto al la clave privada que estaba ingresando, bueno les pego el codigo de toda la clase para que lo entiendan mejor:

package com.infosoft.rasengan.util;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;

import javax.security.cert.CertificateException;
import javax.security.cert.X509Certificate;

import org.apache.commons.ssl.PKCS8Key;
import org.apache.commons.ssl.ProbablyNotPKCS8Exception;

public class CertificateValidator {

public final static int SUCCESS = 0;
public final static int ERROR_PASSWORD = 1;
public final static int ERROR_PRIVATE_KEY = 2;
public final static int ERROR_PUBLIC_KEY = 3;
public final static int ERROR_PUBLIC_PRIVATE_KEY = 4;

private byte[] clavePrivada;
private byte[] clavePublica;
private String password;

public CertificateValidator(InputStream archivoClavePrivada,
InputStream archivoClavePublica, String password) {
this.clavePrivada = FormatUtil.getBytes(archivoClavePrivada);
this.clavePublica = FormatUtil.getBytes(archivoClavePublica);
this.password = password;
}

/**
* Valida el certificado digital, la clave privada, asi como su relacion
*
* @return - 0 si todo esta correcto
* - 1 si el password de la clave privada no es correcto
* - 2 si la clave privada no es un archivo que corresponda al estandar PKCS8
* - 3 si el certificado no es un archivo que corresponda al estandar PKCS10
* - 4 si el certificado no corresponde a la clave privada
*/

public int validate() {
String textoAFirmar = "||2.0|FDF|28125|2007-09-12T12:47:31|11160|2007|ingreso|Pago en una sola exhibicion|"
+ "TERMINOS CONTADO ESP|3674.13|4225.25|IMM9304016Z4|Ingram Micro Mexico S.A. de C.V.|Laguna de Terminos|249|"
+ "Anahuac|Miguel Hidalgo|Distrito Federal|Mexico|11320|Av. 16 de Septiembre|225|San MartinXochinahuac|"
+ "Azcapotzalco|Distrito Federal|Mexico|02140|CAOG8406274R0|CHAVEZ OCHOA GABRIEL|HDA. DE CORLOME NO. 51|"
+ "COL. FLORESTA COYOACAN|DELG. TLALPAN|MEXICO, D.F. MX 14310|MX|3.00|"
+ "TONER NEGRO P/LASERJET SUPL 2420 (6,000 PAG )|1189.04|3567.12|1.00|COMISION TARJETA DECREDITO|"
+ "107.01|107.01|IVA|15.00|551.12|551.12||";
try {
PKCS8Key pkcs8 = new PKCS8Key(clavePrivada, password.toCharArray());
PrivateKey pk = pkcs8.getPrivateKey();
Signature firma = Signature.getInstance("MD5withRSA");
firma.initSign(pk);
firma.update(textoAFirmar.getBytes("UTF-8"));
byte[] firmado = firma.sign();
X509Certificate cert = X509Certificate.getInstance(clavePublica);
cert.checkValidity();
firma.initVerify(cert.getPublicKey());
firma.update(textoAFirmar.getBytes("UTF-8"));
if (firma.verify(firmado)) {
return SUCCESS;
} else {
return ERROR_PUBLIC_PRIVATE_KEY;
}
} catch (ProbablyNotPKCS8Exception e) {
return ERROR_PRIVATE_KEY;
} catch (UnsupportedEncodingException e) {
return ERROR_PRIVATE_KEY;
} catch (SignatureException e) {
return ERROR_PRIVATE_KEY;
} catch (GeneralSecurityException e) {
return ERROR_PASSWORD;
} catch (CertificateException e) {
return ERROR_PUBLIC_KEY;
}
}

}

Saludos

jueves, 18 de febrero de 2010

Como obtener el sello digital con JAVA

Hola el objetivo de esta entrada es explicar cómo crear un sello digital, para la facturación electrónica.

Escribo esto porque tuve muchos problemas para poder crearlo, mi primer problema fue el poder leer la llave privada que te da el SAT, ya que esta viene ecriptada con un password y encontré poca información de como leerla en java.

Bueno, basta de quejas, empecemos!

Lo primero que hay que hacer es generar la cadena original, la creación de esta cadena esta especificada en el anexo 20 de la Resolución Miscelánea Fiscal inciso D. Esta es una cadena formada con los datos de la factura, estos datos están separados por el carácter pipe ”|”, y se inicia y finaliza esta cadena con un doble pipe “||”.
Aquí le s dejo un ejemplo de la cadena Original.

||2.0|AB|32|2010-02-18T12:30:03|434|2006|ingreso|EFECTIVO|3000.00|45029.85|02934INFOSOFT|Infosoft & Xgress|arenas|98|Coyoacan|DF|México|54832|4503495WXGF4|México|2|Impresoras|1500.00|3000.00|2|PC|2000.00|4000.00|IVA|15.00|2900.85|2900.85||

Ya que tenemos la cadena original lo siguiente es pasarla por un algoritmo de digestión como es el MD5, y el resultado pasarlo por un algoritmo de encriptación RSA, estos dos pasos se pueden hacer en uno solo con el api de java. Para esto lo primero que necesitamos es la clave privada (archivo.key) que nos proporcionó el SAT junto con su password. Esta clave esta codificada según el estándar PKCS8 en formato DER. Además esta clave esta encriptada con un password.
Para poder des enriptarla utilizaremos la librería commons-ssl de apache. De esa libraría utilizaremos el objeto PKCS8Key de la siguiente manera:

PKCS8Key pkcs8 = new PKCS8Key(clavePrivada, password.toCharArray());

Donde clavePrivada es un arreglo de bytes que contiene la clave privada.
Para poder obtener la clave privada solo se la pedimos a este objeto:

java.security.PrivateKey pk = pkcs8.getPrivateKey();

Lo siguiente que hay que hacer es obtener el objeto que codificara en MD5 y RSA nuestra cadena, e inicializarlo con la clave privada este es java.security.Signature.

Signature firma = Signature.getInstance("MD5withRSA");
firma.initSign(pk);

donde pk es la clave privada que obtuvimos.
Al objeto Signature se le pasa la cadena original convertida en bytes con formato UFT-8
firma.update(cadenaOriginal.getBytes("UTF-8"));
y obtenemos el arreglo de bytes con la cadena original encriptada.

byte[] cadenaFirmada = firma.sign();

A continuación se codifica en base 64 la cadena para poder obtener caracteres imprimibles con el objeto sun.misc.BASE64Encoder.

BASE64Encoder b64 = new BASE64Encoder();
String selloDigital = b64.encode(firma.sign());

Y con esto obtenemos una cadena como esta que es el sello digital

UdjknEGo/r0v7QrAhvL+aFKkl6Jk6b4pNvRYI0ymWrW19k4DjzygbtTnAnB0HNmafifTNzxB+/wE
JsJKGWjbnSAO61gy6JTLSvrdS+KPKpQtxYH8/7Ib55J8T4PuIL0a6qmAwpMFrHToSaAO0NXG3W9t
homWp8b+UdbJ2qL/ABI=

Les dejo el código completo para que lo revisen.


package com.infosoft.rasengan.cfd;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.Signature;

import org.apache.commons.ssl.PKCS8Key;
import org.jdom.Document;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.output.Format.TextMode;

import sun.misc.BASE64Encoder;

import com.infosoft.rasengan.cfd.bean.Comprobante;
/**
* Clase que genera el comprobante fiscal digital asi como el sello digital
* @author Xgress
*
*/
public class GeneradorCFD {

private Comprobante comprobante;

/**
* Constructor que inicializa el objeto con un Comprobante, genera el
* selloDigital y se lo agrega al objeto Comprobante
*
* @param comprobante
* Objeto que contiene todos los datos de un Comprobante Fiscal
* Digital
* @param archivoClavePrivada
* InputStream que contiene el archivo .key de la llave privada
* @param password
* String con el password de la llave privada
* @throws GeneralSecurityException
* Excepcion arrogada cuando el password no es el correcto
*/
public GeneradorCFD(Comprobante comprobante,
InputStream archivoClavePrivada, String password)
throws GeneralSecurityException {
this.comprobante = comprobante;
this.comprobante
.setSello(getSelloDigital(archivoClavePrivada, password));
}

/**
* Metodo que genera el sello digital a partir de la cadena original y la
* clave privada siguiendo los siguientes pasos: 1)Se le aplica la funcion
* hash de digestion MD5 a la cadena original y se aplica el algoritmo de
* encripcion RSA con la clave privada. 2) Se codifica el resultado a Base64
* teniendo como resultado una cadena imprimible.
*
* @param archivoClavePrivada
* InputStream que contiene la clave privada(archivo .key)
* @param password
* String con el password de la clave privada
* @return String con el sello digital
* @throws GeneralSecurityException
* Excepcion arrogada cuando el password no es el correcto
*/
public String getSelloDigital(InputStream archivoClavePrivada,
String password) throws GeneralSecurityException {
String cadenaOriginal = comprobante.getCadenaOriginal();
System.out.println(cadenaOriginal);
byte[] clavePrivada = getBytes(archivoClavePrivada);
PKCS8Key pkcs8 = new PKCS8Key(clavePrivada, password.toCharArray());
PrivateKey pk = pkcs8.getPrivateKey();
Signature firma = Signature.getInstance("MD5withRSA");
firma.initSign(pk);
String selloDigital = null;
try {
firma.update(cadenaOriginal.getBytes("UTF-8"));
BASE64Encoder b64 = new BASE64Encoder();
selloDigital = b64.encode(firma.sign());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(selloDigital);
return selloDigital;
}

/**
* Genera el xml extensible del comprobante fiscal digital
* @return String con el xml
*/
public String getCFD() {
Format format = Format.getPrettyFormat();
format.setEncoding("utf-8");
format.setTextMode(TextMode.NORMALIZE);
XMLOutputter xmlOutputer = new XMLOutputter(format);
String res = xmlOutputer
.outputString(new Document(comprobante.getXML()));
try {
res = new String(res.getBytes("UTF-8"));//TODO: probar bien
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return res;
}
/**
* Metodo que convierte un input stream con la llave privada a un array de bytes
* @param is InputSteam con la clave privada
* @return Arreglo de bytes con la clave privada
*/
private byte[] getBytes(InputStream is) {
int totalBytes = 714;
byte[] buffer = null;
try {
buffer = new byte[totalBytes];
is.read(buffer, 0, totalBytes);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}

}