El misterio de keytool y los certificados Java

24 abril, 2014

En una entrada anterior comenté que las bibliotecas criptográficas, si bien están diseñadas para ocultar mediante abstracción su funcionamiento interno, a veces nos pueden acarrear sorpresas si no sabemos exactamente qué está pasando en su interior. Algo de lo más normal, ya que precisamente intentan ocultar todo. Pero diseñar una caja negra es algo que debe hacerse muy bien, ya que se tiene que evitar que su comportamiento tenga elementos inesperados o sin sentido para el observador externo. En caso contrario, la abstracción «gotea».

Si se me permite explicar una batallita más, otro aspecto un tanto misterioso con el que me he encontrado trabajando también en el desarrollo de aplicaciones criptográficas en Java basadas en cifrado de clave pública está en la generación de certificados digitales. Los certificados digitales están muy bien explicados con detalle en otros recursos, por lo que no me alargaré, pero si quisiera definirlos en una sola frase, diría que es la manera de asociar una identidad a una clave pública, conceptualmente, de manera similar a como un DNI asocia una foto a un nombre.

Dado que el uso de certificados es el sistema estándar para interoperar entre bibliotecas y aplicaciones que necesitan intercambiar claves públicas, poder generar certificados es muy útil. Si buscamos atentamente entre la multitud de clases criptográficas que ofrece Java, podremos encontrar clases asociadas a certificados. Por ejemplo «X509Certificate», que representa certificados según el estándar X.509, es el más usado (utilizado para conexiones seguras en un navegador). Sin embargo, nuestro gozo en un pozo. Si bien es posible cargar certificados ya existentes desde fichero o un almacén criptográfico seguro (keystore), por más que uno busque por la API, no habrá manera de encontrar cómo generar uno desde cero, a partir de una clave pública generada en nuestro propio código. ¡Mala suerte! Parece que si se desea crear un certificado, es necesario usar bibliotecas externas, como BouncyCastle, una muy popular. Durante un tiempo acepté este hecho como algo inevitable.

Un certificado digital para nuestro blog

Sin embargo, la caja negra posee una pequeña rendija por donde gotean algunos detalles. Esta rendija se encuentra en la relación que existe entre las classes Java y una herramienta llamada keytool.

Esta herramienta es un pequeño programa auxiliar distribuido entre los binarios del entorno de desarrollo de Java que sirve para gestionar claves criptográficas. Así pues, una funcionalidad muy útil de keytool es la de poder generar fácilmente pares de claves para algoritmos de clave pública. Siempre que keytool genera un par de claves, automáticamente guarda la clave pública dentro un certificado digital autofirmado. Si bien un certificado autofirmado, generado por uno mismo y no por una autoridad de confianza, no sería el que se emplearía en un entorno real (seria como un DNI impreso por nosotros mismos) éste es más que suficiente para poder hacer algunas pruebas en nuestros programas. Solo debemos hacer lo siguiente:

[code language=»bash»]keytool -genkey -alias miAlias -keyalg RSA -keystore almacenClaves.jks -keysize 2048[/code]

¡Y voilà! Ya tenemos una nuevas claves y su certificado almacenados de manera segura en el fichero «almacenClaves.jks».

Una particularidad especial de keytool es que, aún tratándose de un binario en código nativo, todas sus funciones dependen de ejecutar bytecode de métodos de las clases Java definidas en sus bibliotecas criptográficas, mediante JNI. Por lo tanto, en cierto modo, sirve tanto como herramienta auxiliar de gestión de claves, como de pequeña demostración de las capacidades criptográficas del lenguaje. El binario por sí solo es incapaz de hacer nada. Si se ejecutara sin el entorno Java, se «quejaría» de que no encuentra clases (i.e. ClassNotFoundException). En ese sentido, su rol se asemeja un poco al de otra «navaja suiza criptográfica», OpenSSL, pero más limitado en sus funcionalidades y ligado exclusivamente al entorno Java.

Sin embargo, al conocer esta particularidad aparece el gran misterio. Keytool es capaz de generar certificados digitales desde cero, pero sin embargo, se basa exclusivamente en la invocación de las bibliotecas criptográficas de Java en su entorno de desarrollo por defecto. Y esta API no ofrece ningún mecanismo a primera vista. Entonces ¿cómo lo hace?

Si bien me costó un poco descubrirlo, el caso es que Java dispone de un conjunto de APIs ocultas, que permiten llevar a cabo operaciones no documentadas. Si buscamos la carpeta con los ficheros «.jar» que contienen las bibliotecas básicas del runtime Java, concretamente  el fichero «rt.jar», veremos que hay muchas más clases que las que aparecen en la API. Concretamente, entre ellas podemos encontrar la clase «sun.security.x509.X509CertImpl», que es la implementación interna usada para generar certificados digitales. Igualmente, encontramos muchas otras auxiliares que le dan soporte.

Vaya, vaya…

A quién se le ocurre ocultar funcionalidades tan importantes… Sin saber como funciona internamente keytool, jamás se me hubiera ocurrido mirar y hubiera dado por hecho que Java no permite generar certificados por sí mismo. Eso sí, el uso de una API oculta es un «hack» en toda regla, y un tanto arriesgado, ya que los desarrolladores no garantizan su continuidad entre versiones. Pero mientras exista una keytool con las mismas funcionalidades actuales, habrá algún método para generar certificados, oculto entre las clases de Java. A fin de cuentas, si ni siquiera Oracle respeta su propia ocultación de datos, no voy a ser yo el que deje de ahorrarse el uso de bibliotecas externas para aplicaciones simples.

Y, por si a alguien le fuese útil, aquí os dejo un código de ejemplo para generar un certificado con estas clases ocultas.

[code language=»java»]
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Date;
import sun.security.x509.*;

public class CertIssuer {

//Medida de las claves
public static final int KEY_LEN = 2048;

//Fecha de expiración
private static final int EXPIRATION = 365;

//Algoritmo de firma a usar
private static final String ALGORITHM = «SHA1withRSA»;

/** Genera un certificado autofirmado
*
* @param subName Nombre del propietario.
* @param pubKey Clave pública del propietario.
* @param issName Nombre del emisor..
* @param issKey Clave privada del emisor, para firmar el certificado.
* @return Certificado X.509.
* @throws Exception Error.
*/
public X509Certificate generateCertificate(String subName, PublicKey pubKey, String issName, PrivateKey issKey) throws Exception {

//Todo certificado tiene un número de serie único
BigInteger sn = new BigInteger(64, new SecureRandom());

//Calcular fecha de expiración
Date from = new Date();
Date to = new Date(from.getTime() + EXPIRATION * 86400000l);
CertificateValidity interval = new CertificateValidity(from, to);

//Se generan las identidades de propietario y emisor
X500Name owner = new X500Name(subName);
X500Name issuer = new X500Name(issName);

//Se genera la información del certificado
X509CertInfo info = new X509CertInfo();
info.set(X509CertInfo.VALIDITY, interval);
info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
info.set(X509CertInfo.ISSUER, new CertificateIssuerName(issuer));
info.set(X509CertInfo.KEY, new CertificateX509Key(pubKey));
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
AlgorithmId algo = new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid);
info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));

//Se firma el certificado
X509CertImpl cert = new X509CertImpl(info);
cert.sign(issKey, ALGORITHM);

//Se actualiza el campo del algoritmo y se vuelve a firmar
algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
info.set(CertificateAlgorithmId.NAME + «.» + CertificateAlgorithmId.ALGORITHM, algo);
cert = new X509CertImpl(info);
cert.sign(issKey, ALGORITHM);
return cert;
}

/** Se genera un certificado autofirmado a partir de un par de claves al azar.
*/
public static void main (String[] args) throws Exception {

CertIssuer certIssue = new CertIssuer();

KeyPairGenerator keyGen = KeyPairGenerator.getInstance(«RSA»);
keyGen.initialize(KEY_LEN);
KeyPair kPair = keyGen.genKeyPair();

String subject = «CN=Blog Informatica,O=UOC,OU=EIMT,L=BCN,ST=Cataluña,C=ES»;
X509Certificate cert = certIssue.generateCertificate(subject, kPair.getPublic(), subject, kPair.getPrivate());

FileOutputStream certFile = new FileOutputStream(«certificado.crt»);
certFile.write(cert.getEncoded());
certFile.close();
}
}
[/code]

(Visited 254 times, 1 visits today)
Autor / Autora
Joan Arnedo Moreno
Comentarios
Nair22 agosto, 2014 a las 3:05 pm

Hola, este artículo está muy bueno, felicidades!!!
Pero me quedó una duda, ¿cómo hacer para que la aplicación que desarrollé y que va a tener mi cliente sea capaz de decodificar o utilizar el certificado que se generó con el código que proponen?
Saludos

Responder
Gabriel10 octubre, 2014 a las 8:31 pm

Hola. El artículo es en cierta medida interesante pero creo que la idea de «abstracción» no es ocultar el funcionamiento, sino propiciar el POLIMORFISMO. Por otro lado no entiendo a que se refiere con que «Oracle no respeta su ocultación de datos», suena mas bien confuso o amarillista. Considero que lo que hizo no es un misterio, mucho menos un «hack», sino mas bien decompilar un .class y reutilizar código hecho por alguien mas tal vez violando una licencia de copyright. Esta clase de notas sería esperable de algún estudiante haciendo experimentos, pero creo que en su caso que es «profesor» debería tener cuidado con lo que escribe. Atte.

Responder
Rommel Mercado29 diciembre, 2014 a las 8:50 pm

Saludos, en primer lugar agradecer por este aporte es de gran ayuda, ahora mi consulta es como se puede firmar documentos xml con la llave privada desde keytool o de otra manera, o se utiliza arhivos con extension .pem, bueno espero tus comentarios.
gracias

Responder
Jesus2 abril, 2015 a las 8:50 am

Saludos.
Sí que es verdad que es dfícil conseguir implementar un programa que genere certificados sin utilizar la ventana de comandos con keytool. Ahora bien, tu aportación me ha resultado magnífica no sólo para comprender la estructura de un certificado, sino para trabajar sobre ello. Ahora bien, me gustaría preguntarte sobre la figura de la Autoridad de certificación. Estoy intentando crear un programa que las gestione, cree y edite para una empresa. Y me parece que la clase que nos muestras se parece bastante a una. Cualquier información que conozcas será preciosa para mí. Muchas gracias por compartir lo que sabes.

Responder
Pedro2 diciembre, 2016 a las 11:34 am

Muchas gracias por este artículo, me ha sido de gran utilidad.

Gracias!

Responder
German Gonzalez3 abril, 2018 a las 5:57 pm

Buen día, excelente artículo. una duda, se puede crear una keystore con las huellas digitales de otra llave?

Responder
Certificado Digital Online11 agosto, 2023 a las 7:01 pm

Gran trabajo. Me encanta este tema y sobre todo la forma en que lo has explicado. es realmente impresionante Gracias por compartir esta información.

https://gestiones24.es/

Responder
Certificado Digital Online16 agosto, 2023 a las 1:27 pm

Gracias por esta increíble publicación, has entregado un muy buen contenido.

https://gestiones24.es/

Responder
Deja un comentario