Java – Falta caracteres finales al cifrar utilizando blowfish

Estoy usando un código Java que encripta el contenido de un archivo de texto usando Blowfish. Cuando vuelvo a convertir el archivo cifrado (es decir, descifrarlo) a la cadena le falta un carácter del final. ¿Alguna idea de por qué? Soy muy nuevo en Java y he estado jugando con esto durante horas sin suerte.

El archivo war_and_peace.txt solo contiene la cadena “Esto es un texto”. decrypted.txt contiene “Esto es algo de texto” (sin t al final). Aquí está el código java:

public static void encrypt(String key, InputStream is, OutputStream os) throws Throwable { encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os); } public static void decrypt(String key, InputStream is, OutputStream os) throws Throwable { encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os); } private static byte[] getBytes(String toGet) { try { byte[] retVal = new byte[toGet.length()]; for (int i = 0; i < toGet.length(); i++) { char anychar = toGet.charAt(i); retVal[i] = (byte)anychar; } return retVal; }catch(Exception e) { String errorMsg = "ERROR: getBytes :" + e; return null; } } public static void encryptOrDecrypt(String key, int mode, InputStream is, OutputStream os) throws Throwable { String iv = "12345678"; byte[] IVBytes = getBytes(iv); IvParameterSpec IV = new IvParameterSpec(IVBytes); byte[] KeyData = key.getBytes(); SecretKeySpec blowKey = new SecretKeySpec(KeyData, "Blowfish"); //Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding"); Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding"); if (mode == Cipher.ENCRYPT_MODE) { cipher.init(Cipher.ENCRYPT_MODE, blowKey, IV); CipherInputStream cis = new CipherInputStream(is, cipher); doCopy(cis, os); } else if (mode == Cipher.DECRYPT_MODE) { cipher.init(Cipher.DECRYPT_MODE, blowKey, IV); CipherOutputStream cos = new CipherOutputStream(os, cipher); doCopy(is, cos); } } public static void doCopy(InputStream is, OutputStream os) throws IOException { byte[] bytes = new byte[4096]; //byte[] bytes = new byte[64]; int numBytes; while ((numBytes = is.read(bytes)) != -1) { os.write(bytes, 0, numBytes); } os.flush(); os.close(); is.close(); } public static void main(String[] args) { //Encrypt the reports try { String key = "squirrel123"; FileInputStream fis = new FileInputStream("war_and_peace.txt"); FileOutputStream fos = new FileOutputStream("encrypted.txt"); encrypt(key, fis, fos); FileInputStream fis2 = new FileInputStream("encrypted.txt"); FileOutputStream fos2 = new FileOutputStream("decrypted.txt"); decrypt(key, fis2, fos2); } catch (Throwable e) { e.printStackTrace(); } } 

`

Hay un par de cosas que no son óptimas aquí.

Pero primero resolvamos tu problema. El motivo por el que falta la última parte de su entrada es el relleno que especifica: ¡ninguno! Sin especificar un relleno, el Cipher solo puede operar en bloques de longitud completa (8 bytes para Blowfish). El exceso de entrada que sea inferior a un bloque de largo se descartará de manera silenciosa, y faltará el texto. En detalle: “Este es un texto” tiene 17 bytes de longitud, por lo que se descifran dos bloques completos y se desecha el último byte 17, “t”.

Siempre use un relleno en combinación con cifrados de bloques simétricos, PKCS5Padding está bien.

A continuación, cuando getBytes() con Cipher , no necesita implementar sus propios getBytes() , ya que String#getBytes ya está haciendo el trabajo por usted. Solo asegúrese de operar con la misma encoding de caracteres cuando obtenga los bytes y cuando reconstruya una String partir de bytes, es una fuente común de errores.

Debes echar un vistazo a los documentos de JCE , te ayudarán a evitar algunos de los errores comunes.

Por ejemplo, el uso directo de teclas de cadena no es necesario para la criptografía simétrica, ya que no contienen suficiente entropía, lo que facilitaría la fuerza bruta de esa clave. La JCE le brinda la clase KeyGenerator y siempre debe usarla a menos que sepa exactamente lo que está haciendo. Genera una clave aleatoria segura del tamaño apropiado para usted, pero además, y eso es algo que la gente tiende a olvidar, también se asegurará de que no cree una clave débil. Por ejemplo, existen claves débiles conocidas para Blowfish que deben evitarse en el uso práctico.

Finalmente, no debe usar un IV determinista cuando realice el cifrado CBC. Hay algunos ataques recientes que hacen posible explotar esto, lo que resulta en la recuperación total del mensaje, y eso obviamente no es bueno. El IV siempre debe elegirse al azar (utilizando un SecureRandom ) para hacerlo impredecible. Cipher hace por usted de manera predeterminada, simplemente puede obtener el IV utilizado después del cifrado con Cipher#getIV .

En otra nota, menos relevante para la seguridad: debe cerrar las secuencias en un bloque final para asegurarse de que estén cerradas a toda costa; de lo contrario, se quedará con un identificador de archivo abierto en caso de una excepción.

Aquí hay una versión actualizada de su código que toma en cuenta todos estos aspectos (tuvo que usar cadenas en lugar de archivos en main , pero simplemente puede reemplazarlo con lo que tenía allí):

 private static final String ALGORITHM = "Blowfish/CBC/PKCS5Padding"; /* now returns the IV that was used */ private static byte[] encrypt(SecretKey key, InputStream is, OutputStream os) { try { Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, key); CipherInputStream cis = new CipherInputStream(is, cipher); doCopy(cis, os); return cipher.getIV(); } catch (Exception ex) { throw new RuntimeException(ex); } } private static void decrypt(SecretKey key, byte[] iv, InputStream is, OutputStream os) { try { Cipher cipher = Cipher.getInstance(ALGORITHM); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); CipherInputStream cis = new CipherInputStream(is, cipher); doCopy(cis, os); } catch (Exception ex) { throw new RuntimeException(ex); } } private static void doCopy(InputStream is, OutputStream os) throws IOException { try { byte[] bytes = new byte[4096]; int numBytes; while ((numBytes = is.read(bytes)) != -1) { os.write(bytes, 0, numBytes); } } finally { is.close(); os.close(); } } public static void main(String[] args) { try { String plain = "I am very secret. Help!"; KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish"); SecretKey key = keyGen.generateKey(); byte[] iv; InputStream in = new ByteArrayInputStream(plain.getBytes("UTF-8")); ByteArrayOutputStream out = new ByteArrayOutputStream(); iv = encrypt(key, in, out); in = new ByteArrayInputStream(out.toByteArray()); out = new ByteArrayOutputStream(); decrypt(key, iv, in, out); String result = new String(out.toByteArray(), "UTF-8"); System.out.println(result); System.out.println(plain.equals(result)); // => true } catch (Exception e) { e.printStackTrace(); } } 

Tienes tu CipherInputStream y CipherOutputStream mezclados. Para encriptar, se lee de una stream de entrada simple y se escribe en un CipherOutputStream . Para descifrar … tienes la idea.

EDITAR:

Lo que está sucediendo es que ha especificado NOPADDING y está intentando cifrar utilizando un CipherInputStream. Los primeros 16 bytes forman dos bloques completos válidos y, por lo tanto, se cifran correctamente. Entonces solo queda 1 byte, y cuando la clase CipherInputStream recibe la indicación de fin de archivo, realiza un Cipher.doFinal() en el objeto de cifrado y recibe una excepción IllegalBlockSizeException. Esta excepción se traga y la lectura devuelve -1 que indica el final del archivo. Sin embargo, si usa PKCS5PADDING, todo debería funcionar.

EDIT 2:

El relieve es correcto, ya que el problema real es simplemente que es complicado y propenso a errores al usar las clases CipherStream con la opción NOPADDING. De hecho, estas clases declaran explícitamente que se tragan en silencio todas las excepciones de Seguridad lanzadas por la instancia de Cipher subyacente, por lo que quizás no sean una buena opción para los principiantes.

Las claves son binarias y String no es un contenedor para datos binarios. Utilice un byte [].

Cuando tuve este problema tuve que llamar a doFinal en el cifrado:

http://docs.oracle.com/javase/1.4.2/docs/api/javax/crypto/Cipher.html#doFinal ()

    Intereting Posts