Encriptación asimétrica en java hecho tan sencillo que te hará llorar

Los últimos días me he estado enfrentando a la encriptación asimétrica y lo cierto es que puede ser un quebradero de cabeza tener en cuenta el formato de las claves, el charset en el que se procesan, el algoritmo que usan… En este tutorial voy a intentar simplificar al máximo todo el proceso.

Generar las claves

Mi librerería (si me puedo permitir llamarlo así) te permite omitir este paso y usar unas claves temporales. Sin embargo, ten en cuenta que si usas esta opción, cada instancia de mi clase servirá para desencriptar tan solo lo que se haya encriptado con la propia, y si relanzas el programa o creas otra instancia, no podrá desencriptar los datos que hayas guardado anteriormente. Esto, por lo general, no es lo adecuado a nuestras necesidades ya que no permite persistir los datos encriptados.

Para generar las claves vamos a usar el siguiente comando de open ssl, que nos generará en nuestra carpeta de usuario una clave privada de 3072 bits en el estándar PKCS#8, con la que podremos encriptar secuencias de hasta 373 carácteres (si pretendes encriptar una cantidad ilimitada y no bloques independientes deberías considerar la criptografía híbrida).

También puedes optar por una clave de 4096 bits para un límite de 501 carácteres (y una protección incluso excesiva ante fuerza bruta).

openssl genpkey -out privada.pem -algorithm RSA -pkeyopt rsa_keygen_bits:3072

A partir de esta clave podemos generar la pública:

openssl rsa -in privada.pem -out publica.pem -pubout -outform PEM

De ahora en adelante voy a asumir que tu código implementa una forma de leer estos archivos, obtener su contenido y guardarlo en unas variables de tipo String a las que me referiré más adelante como publicKeyStr y privateKeyStr.

No tienes que preocuparte ni de las cabeceras del archivo ni de los retornos de carro, puedes pasarla por parámetro tal cual la leas del .pem, que ya se encarga mi clase de todo 👌🏻.

Aclaro que estoy asumiendo que estás configurando un servidor. Por supuesto, si tienes un cliente Java que va a emitir datos encriptados, tan solo necesitaremos tener la clave pública del servidor, y por lo tanto podremos usar esta clase tan solo para encriptar.

Preparar la clase

Crea una clase RSAUtils en tu paquete de utilidades, puedes copiar la mía y usar los métodos públicos que contiene y cuyo uso voy a explicar. Te dejo la clase completa para descargar al final del tutorial.

Como verás, la clase no usa ninguna librería externa por lo que puedes incluirlo en cualquier proyecto sin calentarte la cabeza.

¡Listo! Ya podemos usar la clase para encriptar y desencriptar.

Encriptar y desencriptar con claves autogeneradas

Si quieres utilizar las claves temporales, puedes usar el constructor de la clase que requiere un parámetro int; la longitud de la clave. Como hemos visto más arriba, se recomienda una longitud de 3072.

¿Sabías que… ?

Cualquier longitud igual o superior a 512 bits se va a considerar válida, sin embargo, con la capacidad computacional actual esta longitud comienza a ser vulnerable.

RSAUtils rsa = new RSAUtils(3072);

Ahora puedes ir directamente al paso Uso de los métodos.

Encriptar y desencriptar con tus claves

Sencillísimo también, si has seguido el primer paso, cuento con que tengas tus claves guardadas en una variable, pues bien, lo único que cambia respecto a la forma anterior es el constructor de RSAUtils que vamos a usar. En este caso, el que te pide dos parámetros de tipo String, siendo estos la clave pública y la privada.

RSAUtils rsa = new RSAUtils(publicKeyStr, privateKeyStr);

La otra diferencia es que deberás contemplar que este constructor lance una InvalidKeySpecException en el caso de que alguno de tus String con las claves no contenga una clave válida en el formato correcto. Sin embargo, debería serlo si has seguido el primer paso correctamente.

Ahora puedes ir directamente al paso Uso de los métodos.

Encriptar tan solo conociendo la clave pública

Si eres un cliente y el servidor solo te ofrece su clave pública, puedes usar este constructor:

RSAUtils rsa = new RSAUtils(publicKeyStr);

Por lo tanto, el método .decrypt() quedará inutilizable, pero no hay ningún problema con esto ya que imagino que lo que pretendes es enviarle el texto plano encriptado al servidor, que se encargará de usar su hartamente protegida clave privada para saber qué le quieres decir.

Y lo mejor de todo es que si el servidor no es tuyo y implementa su propia forma de desencriptar, ¡no tienes que preocuparte! El texto que le vas a enviar está ya codificado correctamente para ser completamente compatible 😉.

Uso de los métodos

Pues bien, con este objeto recién instanciado ya puedes encriptar y desencriptar con las manos atadas a la espalda. Puedes pasarle texto plano al método .encrypt(), que te devolverá un String perfectamente legible.

Pero, ¿qué me devuelve exactamente?
El texto resultante es una representación del valor hexadecimal codificado en base 64, de modo que es fácilmente legible, compatible con cualquier texto y lo más importante, al haberse interpretado como ISO_8859_1 no se ha perdido información y se puede recuperar su estado hexadecimal posteriormente.

Para desencriptar este texto, habrás adivinado que es tan simple como pasárselo al método .decrypt(), que te devolverá el texto original. De modo que el código completo se nos quedaría así:

try {
    RSAUtils rsa = new RSAUtils(publicKeyStr, privateKeyStr);
    String encryptedText = rsa.encrypt("Prueba 123");
    String decryptedText = rsa.decrypt(encryptedText);
    System.out.println(decryptedText); //Imprime: Prueba 123
} catch (InvalidKeySpecException e) {
     //Revisa tus claves
} catch (IllegalBlockSizeException e) {
     //String demasiado largo para la clave aportada
}

La clase

La clase completa la podéis encontrar en https://github.com/Clopma/EasyRSA

Espero que te ahorre tiempo de desarrollo, que eso siempre es bienvenido. Estaré encantado de escuchar cualquier duda o sugerencia.

¡Un saludo y hasta otra!

2 comentarios

  1. DEV
    2 de junio, 2021
    Responder

    Excelente, solo que tengo un problema, utilizo Longitud de la llave: 4096
    Transformación RSA: RSA / ECB / OAEPWithSHA-256AndMGF1Padding
    Codificación RSA: UTF-8 y al desencriptar recibo el siguiente error: Illegal base64 character 20 java

    • clopma
      4 de junio, 2021
      Responder

      ¿Buenas, has probado con mi RSAUtils? Esta clase maneja la conversión a Base64 de forma transparente.

Deja un comentario

Tu dirección de correo electrónico no será publicada.