La arquitectura de criptografía de Java permite a los desarrolladores crear una instancia de una clase como un cifrado, o un generador pseudoaleatorio, al usar llamadas como las siguientes:
En el caso de Android, no recomendamos especificar el proveedor. En general, cualquier llamada a las API de la extensión de criptografía de Java (JCE) en la que se especifique un proveedor solo debe realizarse si este se incluye en la aplicación, o si esta última puede manejar una posible ProviderNotFoundException.
Desafortunadamente, muchas aplicaciones dependen del proveedor “Crypto”, ahora eliminado, para un antipatrón de derivación de claves.
Este proveedor solo proporcionaba una implementación del algoritmo SHA1PRNG para instancias de SecureRandom. El problema es que este algoritmo no ofrece solidez en términos de criptografía. Para los lectores interesados en los pormenores, en la sección 8.1 de
On statistical distance based testing of pseudo random sequences and experiments with PHP and Debian OpenSSL (acerca de las pruebas basadas en distancias estadísticas de secuencias pseudoaleatorias y experimentos con PHP y Debian OpenSSL), escrita por Yongge Want y Tony Nicol, se afirma que la secuencia “aleatoria”, considerada como un formato binario, se ve restringida a la devolución de ceros, y que esta restricción se acentúa según la inicialización.
Como resultado,
en Android N dejaremos sin efecto el algoritmo SHA1PRNG y el proveedor Crypto. Anteriormente, habíamos solucionado los inconvenientes del uso de SecureRandom para la derivación de claves unos años atrás, en
Using Cryptography to Store Credentials Safely (uso de la criptografía para el almacenamiento seguro de credenciales). Sin embargo, debido al uso continuo que se da a esta clase, la repasaremos aquí.
Una aplicación común, aunque incorrecta, de este proveedor se destinaba a derivar claves para cifrado con una contraseña como elemento de inicialización. La implementación de SHA1PRNG suponía un error que lo hacía funcionar de manera determinista si se llamaba a setSeed() antes de obtener una salida. Este error se empleaba para derivar una clave proporcionando una contraseña como elemento de inicialización y luego usando los bytes de salida “aleatorios” de la clave (“aleatorios” en esta oración significa “predecibles e inseguros en términos criptográficos”). Esta clave se podía usar posteriormente para cifrar y descifrar datos.
En la sección siguiente, se explica la manera correcta de derivar claves y de descifrar datos cifrados con una clave insegura. También se ofrece un
ejemplo completo, en el que se incluye una clase auxiliar para usar la funcionalidad de SHA1PRNG en desuso, con el propósito exclusivo de descifrar datos que de lo contrario no estarían disponibles.
Las claves pueden derivarse de la siguiente manera:
- Si lees una clave AES de un disco, almacena la clave real y evita las complicaciones. Puedes obtener una interfaz SecretKey para el uso de claves AES a partir de los bytes de la siguiente manera:
SecretKey key = new SecretKeySpec(keyBytes, "AES");
- Si usas una contraseña para derivar una clave, sigue el excelente tutorial de Nikolay Elenkov considerando la advertencia de que una buena regla de oro supone que el tamaño del valor salt debe ser igual al de la salida de la clave. El aspecto resultante será el siguiente:
/* User types in their password: */
String password = "password";
/* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
as the output (256 / 8 = 32)
byte[] salt; // Should be of saltLength
byte[] salt = new byte[saltLength];
/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
iterationCount, keyLength);
/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
SecretKeyFactory keyFactory = SecretKeyFactory
SecretKey key = new SecretKeySpec(keyBytes, "AES");
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
Eso es todo. No necesitarás nada más.
Para facilitar la transición de datos, abarcamos el caso de desarrolladores que cifran datos con una clave insegura, derivada de una contraseña en cada ocasión. Puedes usar la clase auxiliar InsecureSHA1PRNGKeyDerivator en
la aplicación de ejemplo para derivar la clave.
private static SecretKey deriveKeyInsecurely(String password, int
keySizeInBytes) {
byte[] passwordBytes = password.getBytes(StandardCharsets.US_ASCII);
return new SecretKeySpec(
InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(
}
passwordBytes, keySizeInBytes),
"AES");
Luego, puedes volver a cifrar tus datos con una clave derivada de manera segura, conforme a la explicación anterior, y seguir adelante sin problemas.
Nota 1: Como medida temporal para que las aplicaciones continúen funcionando, decidimos crear de todos modos la instancia para las aplicaciones orientadas a la versión 23 del SDK, para Marshmallow, o a versiones anteriores. No confíes en la presencia del proveedor Crypto en el Android SDK; nuestro plan es eliminarlo por completo en el futuro.
Nota 2: Debido a que en muchas secciones del sistema se considera que existe un algoritmo SHA1PRNG, cuando se solicita una instancia de este y no se especifica el proveedor devolvemos una instancia de la clase OpenSSLRandom, la cual representa una fuente sólida de números aleatorios derivados de OpenSSL.