El escenario que planteamos en este POST consiste en cifrar datos en cliente con Javascript (utilizamos la librería pidCrypt), transmitirla al servidor a través de un POST y descifrarla en el servidor utilizando Java (en este caso mediante el API Java Security y BouncyCastle). El tipo de cifrado empleado es de clave pública (RSA) y el objetivo final es poder cifrar y almacenar información en el navegador entre sesiones (utilizando, por ejemplo HTML5 Local Storage) y enviarla al servidor para su gestión.
Las claves para cifrar / descifrar
Realmente necesitamos un par de claves: la pública para cifrar y la privada para descifrar. La primera se utilizará en el cliente (JavaScript) y la segunda en la parte servidora (Java). Para crear las claves utilizaremos OpenSSL. Otra opción sería KeyTool, pero la descartamos porque sólo permite generar claves públicas autofirmadas en formato X509, que parece no están soportadas con la librería pidCrypt.
Para crear el par de claves utilizaremos la siguiente sentencia de OpenSSL (generamos un par de claves RSA protegiendo la clave privada con contraseña (-aes128 y -passout pass:) en un fichero en formato PEM, que incluye clave pública y privada, y 2048 bits):
openssl genrsa -aes128 -out clave.pem -passout pass:<PASSWORD> 2048
Para obtener la clave pública a partir del fichero creado ejecutaremos:
openssl rsa -in clave.pem -pubout > clave_pub.pem
Cifrando con Javascript
Como hemos comentado utilizaremos la librería pidCript. En primer lugar, deberemos incluir los siguientes ficheros:
<script src="js/ext/pidCrypt/pidcrypt_c.js"></script> <script src="js/ext/pidCrypt/pidcrypt_util_c.js"></script> <script src="js/ext/pidCrypt/asn1_c.js"></script> <script src="js/ext/pidCrypt/jsbn_c.js"></script> <script src="js/ext/pidCrypt/rng_c.js"></script> <script src="js/ext/pidCrypt/prng4_c.js"></script> <script src="js/ext/pidCrypt/rsa_c.js"></script>
Y, siguiendo el ejemplo de cifrado RSA de pidCrypt, utilizaremos el siguiente código para cifrar:
var pub_key = "MIIBIjANBgkqhkiG9w0<.....>CQIDAQAB";
var pem = pidCryptUtil.decodeBase64(pub_key);
var rsa = new pidCrypt.RSA();
var asn = pidCrypt.ASN1.decode(pidCryptUtil.toByteArray(pem));
var tree = asn.toHexTree();
rsa.setPublicKeyFromASN(tree);
var crypted = rsa.encrypt("TEXT TO CIPHER");
Comentarios:
La clave pública en formato PEM tiene el siguiente formato (abriendo el fichero generado en el punto anterior, clave_pub.pem, en un editor de texto):
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0<....> <...> CQIDAQAB -----END PUBLIC KEY-----
Como podemos ver, la calve la pasaremos al método ‘pidCrypt.ASN1.decode‘ sin las líneas inicial y final y sin ningún tipo de retorno de línea.
Además, deberemos tener en cuenta dos cosas a la hora de descifrar:
* pidCrypt cifra el texto en BASE64, por tanto al descifrar obtendremos «TEXT TO CIPHER» en BASE64.
* El resultado cifrado lo devuelve pidCrypt codificado en HEXADECIMAL.
Descifrando con Java
Para descifrar deberemos realizar dos pasos. En primer lugar debemos obtener el par de claves para trabajar en Java. Para ello debemos obtener un objeto ‘java.security.KeyPair‘ a partir de las claves PEM obtenidas con OpenSSL. Para ello es necesario apoyarnos en clases de BouncyCastle, al no ser un formato soportado por defecto en el API de seguridad de Java:
private KeyPair getKeyPairPEM() throws Exception {
FileReader fileReader = new FileReader("clave.pem");
PEMParser pemParser = new PEMParser(fileReader);
Object pemKeyPair = (Object) pemParser.readObject();
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(<PASSWORD>.toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
KeyPair keyPair = converter.getKeyPair(((PEMEncryptedKeyPair)pemKeyPair).decryptKeyPair(decProv));
pemParser.close();
return keyPair;
}
Utilizaremos la clase ‘org.bouncycastle.openssl.PEMParser‘ para parsear el par de claves del fichero ‘clave.pem‘. El objeto resultante será del tipo ‘org.bouncycastle.openssl.PEMEncryptedKeyPair‘, pues la clave está protegida con password (es posible tener la clave sin contraseña y el método sería distinto). Con el proveedor ‘org.bouncycastle.openssl.PEMDecryptorProvider‘ manejaremos el ‘<PASSWORD>‘ con el que se protegió la clave.
Una vez tenemos el par de claves podemos realizar el descifrado:
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
KeyPair ks = this.getKeyPairPEM();
RSAPrivateKey privKey = (RSAPrivateKey) ks.getPrivate();
byte[] cipherText = Hex.decodeHex("ad9cdc36<...>949e7".toCharArray());
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] plain = cipher.doFinal(cipherText);
String plainText = new String(Base64.decodeBase64(plain));
} catch (Exception ex) {}
Comentarios:
* Al crear el objeto ‘javax.crypto.Cipher‘ utilizamos como algoritmo de cifrado ‘RSA/ECB/PKCS1Padding‘. Java soporta varios de ellos, siendo éste el utilizado por defecto por pidCrypt.
* Como hemos comentado antes el resultado que recibiriamos del cliente (Javascript) estaría codificado en Hexadecimal. Para obtener el array de bytes utilizamos la clase ‘org.apache.commons.codec.binary.Hex‘.
* Finalmente, recordar que el descifrado nos dará el texto en BASE64, por eso, el último paso para obtener el texto original. También utilizamos la librería Apache Commons Codec.