Using password-based encryption on a file in Java

后端 未结 2 1810
一向
一向 2020-12-15 13:58

I\'m trying to encrypt the contents of one file into another file using a passphrase in Java. The file is getting read to a byte array, encrypted to another byte array, and

相关标签:
2条回答
  • 2020-12-15 14:35

    You are using the Cipher.ENCRYPT_MODE for both, decrypting and encrypting. You should use Cipher.DECRYPT_MODE for decrypting the file.

    That has been fixed, but your boolean is wrong. It should be true for encrypt and false for decrypt. I would strongly recommend against using false/true as function arguments and always use enum like Cipher.ENCRYPT... moving on

    Then you are encrypting to .encrypted file, but trying to decrypt the original plain text file.

    Then you are not applying padding to encryption. I am surprised this actually has to be done manually, but padding is explained here. The padding scheme PKCS5 appeared to be implicitly used here.

    This is full working code, writing encrypted file to test.txt.encrypted, and decrypted file to test.txt.decrypted.txt. Adding padding in encryption and removing it in decryption is explained in the comments.

    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();
            }
    
        }
    }
    
    0 讨论(0)
  • 2020-12-15 14:54

    These are some improvements to the @Esailija 's answer given some new features in Java.

    By using the CipherInputStream and CipherOutputStream classes, the length and complexity of the code is greatly reduced.

    I also use char[] instead of String for the password.

    You can use System.console().readPassword("input password: ") to get the password as a char[] so that it is never a String.

    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;
    }
    
    0 讨论(0)
提交回复
热议问题