Encriptación AES

Buenas,

En primer lugar comentar lo que quiero. Necesito realizar una aplicación que encripte y desencripte las cadenas de texto recibidas, para ello será necesario utilizar el algoritmo AES. Comentar que el valor de la semilla a utilziar para la encriptación y desencriptación será constante. Los valores encriptados se guardarán en la base de datos. Pues tengo el siguiente código para de pruebas con el algoritmo (DES) que funciona correctamente, pero cuando lo cambio a AES me salta una excepción.

String claveEncriptada = null;
String claveOriginal = "Texto a codificar";
String semilla = "0123456789";

// Generamos una clave secreta.
SecretKeySpec desKey = new SecretKeySpec(new String((semilla.trim()).substring(0, 8)).getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desKey);
byte[] claveEncriptadaBytes = cipher.doFinal( claveOriginal.getBytes() );
claveEncriptada = new BASE64Encoder().encode( claveEncriptadaBytes );

System.out.println(claveEncriptada);

cipher.init(Cipher.DECRYPT_MODE, desKey);

byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(claveEncriptada); // Decrypt
byte[] utf8 = cipher.doFinal(dec); // Decode using utf-8
System.out.println(new String(utf8, "UTF8"));

La exceptción es:

Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 8 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(DashoA13*..)
at javax.crypto.Cipher.b(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
at pruebaencritacion.Main.main(Main.java:23)

Decir que he probado con claves de 128 bytes y 192 bytes y salta el mismo error.

¿A qué puede ser debido?

Muchas Gracias.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

AES

Lee el mensaje de la excepción, ahí está tu respuesta. java.security.InvalidKeyException: Invalid AES key length: 8 bytes.

DES usa llaves de 64 bits y bloques de 64 bits también. AES usa llaves de 128, 192 ó 256 bits, y bloques de 128 bits. No puedes usar una llave DES para cifrar datos con AES porque es muy corta. Y no, no has probado con llaves de 128 bits porque ya habrías podido cifrar datos sin problemas. Si cambias el puro string de la llave no sirve de nada porque después al crear la SecretKeySpec estás tomando un substring con los primeros 8 caracteres solamente, así que su defines la variable   como "0123456789ABCDEF" de todas maneras sólo usas la mitad para la llave.

Imagen de ezamudio

al margen...

Como observaciones al margen... la llave que estás usando espero que sea solamente un ejemplo y que realmente no utilices sólo texto como la llave. Lee acerca de PKCS#5 o PBE (Password-Based Encryption), es un estándar para cifrar datos usando passwords, a partir de los cuales generas llaves, es decir la llave no es directamente el password, sino una digestión del password con otra cosa. Y no uses modo ECB porque es muy débil, a menos que pienses encriptar solamente un bloque de datos; mejor utiliza cipher feedback o cipher block chaining (CFB, CBC). Lee acerca de los modos de cifrado de datos por bloque.

Y finalmente, dices que vas a almacenar tus datos cifrados en una base de datos, con una llave constante. Eso realmente no sirve de nada, sólo estás dando la ilusión de seguridad pero está muy mal implementada. Si necesitas guardar por ejemplo passwords, no uses cifrado de datos, usa mejor una digestión como MD5 o SHA-1 SHA-256 etc. Eso es porque no necesitas nunca realmente descifrar el password de un usuario; cuando el usuario se quiere validar, cifras nuevamente su password y lo comparas con lo que tienes en tu base de datos. Y no debes digerir solamente el password del usuario sino combinarlo con algún dato que varía para cada usuario (su username o uid o email o algo). De ese modo si dos usuarios tienen el mismo password, no se nota en la base de datos porque las digestiones serán distintas porque tienen distinto mail o uid.

Si necesitas guardar datos que sean descifrables (por ejemplo números de tarjeta de crédito) por tu aplicación sin intervención del usuario, utiliza al menos CFB o CBC y que la sal o vector de inicialización sea algo distinto para cada usuario; puede ser generada al azar y guardarse en una tabla aparte que únicamente sea legible por un proceso dedicado que descifra los datos, para que sea más difícil que alguien con acceso limitado a la base de datos pueda descifrar datos que no le corresponde ver; estoy partiendo del principio de que si estás guardando datos cifrados es porque hay gente con acceso a dicha base que no debería poder ver algunas cosas; por eso es importante que no uses la misma llave para todo, porque un atacante solamente tiene que encontrar una llave para leer toda esa información cifrada, y si tu miedo es que ese alguien tiene acceso a la base de datos, creo que es muy probable que ese alguien tenga acceso a la aplicación y por lo tanto ni siquiera hará un ataque de fuerza bruta para atinarle a la llave, sino que buscará la misma en el código de la aplicación, archivos de configuración etc.

Un par de cuestiones

En primer lugar gracias por tu rápida respuesta,

Por otra parte estoy realizando pruebas, por eso hay datos tan fáciles como la semilla. Por otra parte, aunque en el código aparece que sólo utilice los primeros 8 caracteres, despues lo he probado sin utilizar esto y me sale el mismo error.

Te explico un poco más a fondo. La aplicación no va estar en el mismo servidor que la base de datos, los datos en la base de datos deben estar encriptados para que el administrado de la bbdd no los vea. La semilla se guardará en la base de datos encriptada mediante un algortimo asimetrico. La aplicación obtendrá la semilla la desencriptará y realizar la encriptación/desencriptación con AES para realizar el insert/select de la bbdd. Lo que pasa es que quiero ir poco a poco ya que estoy comenzando y ya he leído la teoría de la criptografía, aunque todavía me queda mucho por aprender.

Crees que de esta forma se obtiene una grado bueno de seguridad o no serviría para nada. Por cierto cuando he probado con AES he probado AES/CBC/NoPadding
el código es el siguiente, quitando substring y utilizando una semilla con 128 caracteres (aunque no la he copiado), sigue mostrando el mismo error

String claveEncriptada = null;
String claveOriginal = "Texto a codificar";
String semilla = "01234567890123456789";

// Generamos una clave secreta.
SecretKeySpec desKey = new SecretKeySpec(new String((semilla.trim())).getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, desKey);
byte[] claveEncriptadaBytes = cipher.doFinal( claveOriginal.getBytes() );
claveEncriptada = new BASE64Encoder().encode( claveEncriptadaBytes );

System.out.println(claveEncriptada);

cipher.init(Cipher.DECRYPT_MODE, desKey);

byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(claveEncriptada); // Decrypt
byte[] utf8 = cipher.doFinal(dec); // Decode using utf-8
System.out.println(new String(utf8, "UTF8"));

Imagen de ezamudio

PBE

No sé de dónde estás sacando tu terminología, pero en fin... lo que llamas semilla realmente es la llave. No uses directamente texto como la llave. Utiliza Password-Based Encryption, lee acerca de PCKS#5 para generar una llave a partir de un password, para cifrar datos con un algoritmo simétrico.

La llave que pones en el ejemplo es de 160 bits, que según yo no se maneja en AES; solamente 128, 192 o 256.

Si no vas a usar Padding entonces debes asegurarte que los datos a cifrar tengan una longitud que sea múltiplo de 128 bits (16 bytes), de lo contrario vas a tener problemas para cifrar el último bloque si mide menos de 16 bytes. En tu ejemplo "Texto a codificar" mide 17 bytes de modo que son 2 bloques, pero el segundo bloque mide 1 byte y no definiste Padding en tu instancia de Cipher, de modo que vas a obtener un error si quieres cifrar ese texto.

Por cierto ahora usas CBC pero no veo que le hayas pasado un vector de inicialización a tu Cipher. Si usas CFB o CBC necesitas un vector de inicialización (conocido también como IV por sus siglas en inglés).

En cuanto a la descripción de tu criptosistema, no entiendo para qué usar un algoritmo asimétrico, a menos que la idea es que la aplicación tiene una de las llaves RSA y solamente otra persona tiene la otra, de modo que esa persona puede cifrar la llave AES y guardarla en la base de datos, y solamente la aplicación puede descifrarla (usando la otra llave RSA). Pero al final el problema es muy similar al que enfrentaron las tiendas de música como iTunes muy al principio: si la aplicación tiene la llave para descifrar los datos (o en tu caso, la llave asimétrica para descifrar la llave simétrica), entonces hay que buscar dicha llave en la aplicación. Un atacante encuentra la llave RSA en tu aplicación y con ella puede descifrar la llave AES.

El problema sigue siendo que usas una sola llave AES para cifrar no sé cuántos datos distintos; pueden ser un par de bloques de texto por cada usuario, o varios megabytes por cada usuario. En todo caso, si es poco texto por usuario pero tienes muchos usuarios, se puede montar un ataque de texto conocido para tratar de cifrar la llave. A fin de cuentas lo que tienes que preguntarte es qué tan importantes son los datos que quieres ocultar, de quién los quieres ocultar y qué tan dispuestos están a obtenerlos estas personas de quienes los estás ocultando. Si yo tengo acceso parcial a un sistema así, puedo usar el sistema para que cifre datos que yo conozco y los puedo ver cifrados en la base de datos; eso más el saber que solamente se usa una llave para cifrar toda la info de la base de datos, me puede ayudar a encontrar dicha llave, buscando coincidencias en los textos.

Similar a los ingleses cuando lograron descifrar el algoritmo de las Enigma alemanas: asumes que todas las comunicaciones cifradas que interceptas tienen ciertos bloques de texto en común, como un "a quien corresponda" o "estimado blabla" y que terminan con algo similar como "sin más por el momento"; con eso puedes hacer criptanálisis para detectar texto en común en varios bloques distintos. Por eso es importante usar CBC o CFB y es muy importante que cada conjunto de datos que vas a cifrar utilice un IV distinto. Si usas la misma llave y el mismo IV para todos los datos que vas a cifrar, la verdad no tiene mucho caso porque toda esa información está protegida por una sola llave, y una vez que alguien logra encontrar esa llave de una u otra forma, tiene acceso a todos los datos que se supone no debería poder ver.

solaris y AES

Hola,

tengo una aplicación en .net que genera un archivo cifrado en AES con key y IV (el archivo se llama clave.cnx) y debo desencriptarlo en Solaris 10.

Intenté utilizar el 'openssl enc' pero no logro desencriptarlo. Los únicos datos que tengo son el archivo encriptado, la key y el IV. Alguna sugerencia?. Gracias.

Imagen de ezamudio

formato

openssl utiliza su propio formato para cifrar archivos y espera ese mismo formato para descifrarlos (es un formato abierto obviamente). Sólo puedes descifrar con openssl archivos que tengan dicho formato. Qué formato tiene el archivo que genera tu app en .net? si no es compatible, deberás hacer tu propio programa para descifrarlo. Si tienes el código en C#, puedes hacer un programa simple en Mono y compilarlo en Linux (no sé si haya Mono para solaris), o hacerlo en Java, finalmente los datos deben ser compatibles a nivel byte.

Solaris y AES

Entiendo, la duda que me queda es si las diferentes opciones de cifrado que me ofrece el openssl enc no deberían ser estándar?
por ejemplo las que me ofrece relacuionadas con el AES son:

Cipher Types
-aes-128-cbc -aes-128-cfb -aes-128-ecb
-aes-128-ofb -aes-192-cbc -aes-192-cfb
-aes-192-ecb -aes-192-ofb -aes-256-cbc
-aes-256-cfb -aes-256-ecb -aes-256-ofb
-aes128 -aes192 -aes256

te paso otro dato, la forma en que trato de desencriptar (probé todas las variantes e cifrado de AES y no me anduvo):

openssl enc -aes128 -in archivo.aes -out archivo.salida -d -k "Sistemas" -iv 4167756173207929

Mis preguntas son:
- no son estandar las variantes del AES que me ofrece el openssl?
- hay algún otro parámetro que no estoy usando en la desencriptación y quizás deba tener en cuenta?

El archivo encriptado que viene de .net fue hecho con la librería: Imports System.Security.Cryptography

Using myRijndael = Rijndael.Create()
myRijndael.Key = clsAES.GetKey()
myRijndael.IV = clsAES.GetIV()
cadena_conexion = "[BASE] " & txtInstancia.Text.ToUpper() & "|[USER] " & txtUsuario.Text.ToUpper() & "|[PASS]" & txtPass.Text
cryptedCnx = clsAES.Encrypt(cadena_conexion, myRijndael.Key, myRijndael.IV)
End Using

Saludos y gracias.

Imagen de ezamudio

sí son

si son estándar las variantes de cifrado. El problema es el formato del archivo que openssl espera.

Ahora entiendo. alguna

Ahora entiendo.

alguna sugerencia para poder utilizar AES de ambos lados (windows y Solaris) ?.

Gracias.

Imagen de ezamudio

openssl

Usa openssl en ambos lados. O implementa un programa en Java que descifre el archivo que genera el programa en .net