Uso de cifrado basado en contraseña en un archivo en Java

Estoy tratando de cifrar el contenido de un archivo en otro usando una frase de contraseña en Java. El archivo se lee en una matriz de bytes, se cifra en otra matriz de bytes y, a continuación, se escribe en el nuevo archivo. Desafortunadamente, cuando bash revertir el cifrado, el archivo de salida se desencripta como basura.

Sospecho firmemente que el problema tiene que ver con generar una clave idéntica cada vez que se usa la misma frase de contraseña. Escribí un método de prueba que vuelca la clave en un archivo cada vez que se genera uno. La clave se registra directamente y en forma codificada. El primero es idéntico cada vez, pero el segundo siempre es diferente por alguna razón.

Honestamente, no sé mucho sobre los métodos de encriptación, especialmente en Java. Solo necesito que los datos sean moderadamente seguros, y el cifrado no tiene que soportar un ataque de nadie con tiempo y habilidades importantes. Gracias de antemano a cualquiera que tenga consejos sobre esto.

Edit: Esailija tuvo la amabilidad de señalar que siempre estaba configurando el cifrado con ENCRYPT_MODE. Corrigí el problema usando un argumento booleano, pero ahora obtengo la siguiente excepción:

javax.crypto.IllegalBlockSizeException: La longitud de entrada debe ser múltiple de 8 al descifrar con cifrado rellenado

Eso me suena como si la frase de contraseña no se usara correctamente. Tenía la impresión de que “PBEWithMD5AndDES” lo convertiría en un código de 16 bytes, que sin duda es un múltiplo de 8. Me pregunto por qué la clave se genera y se usa bien para el modo de cifrado, pero luego se queja cuando lo intenta. Para descifrar en las mismas condiciones exactas.

import java.various.stuff; /**Utility class to encrypt and decrypt files**/ public class FileEncryptor { //Arbitrarily selected 8-byte salt sequence: private static final byte[] salt = { (byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7, (byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17 }; private static Cipher makeCipher(String pass, Boolean decryptMode) throws GeneralSecurityException{ //Use a KeyFactory to derive the corresponding key from the passphrase: PBEKeySpec keySpec = new PBEKeySpec(pass.toCharArray()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(keySpec); //Create parameters from the salt and an arbitrary number of iterations: PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 42); /*Dump the key to a file for testing: */ FileEncryptor.keyToFile(key); //Set up the cipher: Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); //Set the cipher mode to decryption or encryption: if(decryptMode){ cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec); } else { cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec); } return cipher; } /**Encrypts one file to a second file using a key derived from a passphrase:**/ public static void encryptFile(String fileName, String pass) throws IOException, GeneralSecurityException{ byte[] decData; byte[] encData; File inFile = new File(fileName); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, false); //Read in the file: FileInputStream inStream = new FileInputStream(inFile); decData = new byte[(int)inFile.length()]; inStream.read(decData); inStream.close(); //Encrypt the file data: encData = cipher.doFinal(decData); //Write the encrypted data to a new file: FileOutputStream outStream = new FileOutputStream(new File(fileName + ".encrypted")); outStream.write(encData); outStream.close(); } /**Decrypts one file to a second file using a key derived from a passphrase:**/ public static void decryptFile(String fileName, String pass) throws GeneralSecurityException, IOException{ byte[] encData; byte[] decData; File inFile = new File(fileName); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, true); //Read in the file: FileInputStream inStream = new FileInputStream(inFile); encData = new byte[(int)inFile.length()]; inStream.read(encData); inStream.close(); //Decrypt the file data: decData = cipher.doFinal(encData); //Write the decrypted data to a new file: FileOutputStream target = new FileOutputStream(new File(fileName + ".decrypted.txt")); target.write(decData); target.close(); } /**Record the key to a text file for testing:**/ private static void keyToFile(SecretKey key){ try { File keyFile = new File("C:\\keyfile.txt"); FileWriter keyStream = new FileWriter(keyFile); String encodedKey = "\n" + "Encoded version of key: " + key.getEncoded().toString(); keyStream.write(key.toString()); keyStream.write(encodedKey); keyStream.close(); } catch (IOException e) { System.err.println("Failure writing key to file"); e.printStackTrace(); } } } 

Está utilizando Cipher.ENCRYPT_MODE para descifrar y cifrar. Debe usar Cipher.DECRYPT_MODE para descifrar el archivo.

Eso ha sido arreglado, pero tu booleano está equivocado. Debe ser cierto para cifrar y falso para descifrar. Recomiendo encarecidamente no usar false/true como argumentos de función y siempre uso enumeración como Cipher.ENCRYPT . Cipher.ENCRYPT … yendo a

Entonces está cifrando en un archivo .encrypted, pero tratando de descifrar el archivo de texto plano original.

Entonces no estás aplicando el relleno al cifrado. Me sorprende que esto tenga que hacerse de forma manual, pero aquí se explica el relleno . El esquema de relleno PKCS5 parecía ser utilizado implícitamente aquí.

Este es un código de trabajo completo, escribiendo el archivo cifrado en test.txt.encrypted y el archivo descifrado en test.txt.decrypted.txt . La adición de relleno en el cifrado y su eliminación en el descifrado se explica en los comentarios.

 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; public class FileEncryptor { public static void main( String[] args ) { try { encryptFile( "C:\\test.txt", "password" ); decryptFile( "C:\\test.txt", "password" ); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (GeneralSecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //Arbitrarily selected 8-byte salt sequence: private static final byte[] salt = { (byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7, (byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17 }; private static Cipher makeCipher(String pass, Boolean decryptMode) throws GeneralSecurityException{ //Use a KeyFactory to derive the corresponding key from the passphrase: PBEKeySpec keySpec = new PBEKeySpec(pass.toCharArray()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(keySpec); //Create parameters from the salt and an arbitrary number of iterations: PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 42); /*Dump the key to a file for testing: */ FileEncryptor.keyToFile(key); //Set up the cipher: Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); //Set the cipher mode to decryption or encryption: if(decryptMode){ cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec); } else { cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec); } return cipher; } /**Encrypts one file to a second file using a key derived from a passphrase:**/ public static void encryptFile(String fileName, String pass) throws IOException, GeneralSecurityException{ byte[] decData; byte[] encData; File inFile = new File(fileName); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, true); //Read in the file: FileInputStream inStream = new FileInputStream(inFile); int blockSize = 8; //Figure out how many bytes are padded int paddedCount = blockSize - ((int)inFile.length() % blockSize ); //Figure out full size including padding int padded = (int)inFile.length() + paddedCount; decData = new byte[padded]; inStream.read(decData); inStream.close(); //Write out padding bytes as per PKCS5 algorithm for( int i = (int)inFile.length(); i < padded; ++i ) { decData[i] = (byte)paddedCount; } //Encrypt the file data: encData = cipher.doFinal(decData); //Write the encrypted data to a new file: FileOutputStream outStream = new FileOutputStream(new File(fileName + ".encrypted")); outStream.write(encData); outStream.close(); } /**Decrypts one file to a second file using a key derived from a passphrase:**/ public static void decryptFile(String fileName, String pass) throws GeneralSecurityException, IOException{ byte[] encData; byte[] decData; File inFile = new File(fileName+ ".encrypted"); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, false); //Read in the file: FileInputStream inStream = new FileInputStream(inFile ); encData = new byte[(int)inFile.length()]; inStream.read(encData); inStream.close(); //Decrypt the file data: decData = cipher.doFinal(encData); //Figure out how much padding to remove int padCount = (int)decData[decData.length - 1]; //Naive check, will fail if plaintext file actually contained //this at the end //For robust check, check that padCount bytes at the end have same value if( padCount >= 1 && padCount <= 8 ) { decData = Arrays.copyOfRange( decData , 0, decData.length - padCount); } //Write the decrypted data to a new file: FileOutputStream target = new FileOutputStream(new File(fileName + ".decrypted.txt")); target.write(decData); target.close(); } /**Record the key to a text file for testing:**/ private static void keyToFile(SecretKey key){ try { File keyFile = new File("C:\\keyfile.txt"); FileWriter keyStream = new FileWriter(keyFile); String encodedKey = "\n" + "Encoded version of key: " + key.getEncoded().toString(); keyStream.write(key.toString()); keyStream.write(encodedKey); keyStream.close(); } catch (IOException e) { System.err.println("Failure writing key to file"); e.printStackTrace(); } } } 

Estas son algunas mejoras en la respuesta de @Esailija dadas algunas características nuevas en Java.

Al usar las clases CipherInputStream y CipherOutputStream, la longitud y la complejidad del código se reducen considerablemente.

También uso char [] en lugar de String para la contraseña.

Puede usar System.console (). ReadPassword (“input password:”) para obtener la contraseña como char [] para que nunca sea una cadena.

 public static void encryptFile(String inFileName, String outFileName, char[] pass) throws IOException, GeneralSecurityException { Cipher cipher = PasswordProtectFile.makeCipher(pass, true); try (CipherOutputStream cipherOutputStream = new CipherOutputStream(new FileOutputStream(outFileName), cipher); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inFileName))) { int i; while ((i = bis.read()) != -1) { cipherOutputStream.write(i); } } } public static void decryptFile(String inFileName, String outFileName, char[] pass) throws GeneralSecurityException, IOException { Cipher cipher = PasswordProtectFile.makeCipher(pass, false); try (CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(inFileName), cipher); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFileName))) { int i; while ((i = cipherInputStream.read()) != -1) { bos.write(i); } } } private static Cipher makeCipher(char[] pass, Boolean decryptMode) throws GeneralSecurityException { // Use a KeyFactory to derive the corresponding key from the passphrase: PBEKeySpec keySpec = new PBEKeySpec(pass); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(keySpec); // Create parameters from the salt and an arbitrary number of iterations: PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 43); // Set up the cipher: Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); // Set the cipher mode to decryption or encryption: if (decryptMode) { cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec); } else { cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec); } return cipher; }