0
votes

I'm encrypting Strings and writing them into a text file. In order to decrypt the content, I'm reading from that file and print the decrypted data. When I tested my code with one string value, it encrypted and decrypted perfectly fine; however, when I added more strings to encrypt, the encryption worked fine but the decryption gave me this exception "javax.crypto.BadPaddingException: Given final block not properly padded "

This is my code. Please help!

// these are initialized in main
SecretKey key = KeyGenerator.getInstance("DES").generateKey();
            AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
            ecipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            dcipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
            dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
// catches ..

// it will take a string and the file that will have the encrypted strings
private static void encrypt(String s, OutputStream os) throws IllegalBlockSizeException, BadPaddingException {
        try {
            byte[] buf = s.getBytes();
            byte[] b = ecipher.doFinal(buf);
            os.write(b);
// this is to write a new line after writing each encrypted value and avoid overwriting
            os.write(System.getProperty("line.separator").getBytes()); 
            os.flush();
            os.close();
        }
    catch (IOException e) {
        System.out.println("I/O Error:" + e.getMessage());
    }
}
// this will take the file that has all of the encryptions
private static void decrypt(InputStream is) throws IllegalBlockSizeException, BadPaddingException {
    try {
        byte[] buf = new byte[is.available()];
        is.read(buf);
        byte[] decrypted = dcipher.doFinal(buf);  // THE CAUSE OF THE PROBLEM!!!!
        System.out.println(new String (decrypted));
        is.close();
    }
    catch (IOException e) {
        System.out.println("I/O Error:" + e.getMessage());
    }
2

2 Answers

1
votes

When encrypting, you're writing the encrypted data to your output file then adding a newline, but when decrypting, you seem to be reading the entire contents of the file and decrypting it, this will include the newline character which it will attempt to decrypt as if it were part of the ciphertext, causing your padding exception. You're also attempting to decrypt all of the separately written strings using a single decrypt call, whereas they need to be decrypted individually.

I would suggest converting the encrypted data to Base64 before writing it to the output file and appending the newline. When decrypting, read a line, convert from Base64 back to a byte[] and decrypt, then repeat for each line in the input.

0
votes

Appending a new line after the encrypted bytes is superfluous and should be removed.

The actual cause of the problem is the usage of InputStream.available() to get the size of the data. This method can return any value ranging from 0 to the actual number of bytes in the input stream, so it is not suitable to determine the size of input data. In your example, it returns some value and you read the amount of bytes equal to it but with the very high probability it will not be the all of encrypted data and the cipher will not be able to decode it properly.

In general, there is no way to determine the size of the input stream, so you will have to use the cycle to read the stream. The decryption code should be based on the following paradigm:

private static byte[] decrypt( Cipher dcipher, InputStream in, int BUFFER_SIZE ) throws Exception {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    byte[] inputBuffer = new byte[ BUFFER_SIZE ]; // 4K or 8K are pretty good buffer size values 
    int r = in.read( inputBuffer );
    while ( r >= 0 ) {
        byte[] outputUpdate = dcipher.update( inputBuffer, 0, r );
        out.write( outputUpdate );
        r = in.read( inputBuffer );
    }
    byte[] outputFinalUpdate = dcipher.doFinal();
    out.write( outputFinalUpdate );
    return out.toByteArray();
}
  • When you do not know the size, you must read from the InputStream in cycle.
  • You should never ignoer the return value of InputStream.read(byte[]).
  • The ByteArrayOutputStream can be used as the simplest way to get the resizable byte array if you want to get the decrypted data all at once.

You can also first read the whole encrypted data using the code similar to the cycle above but without intermediate Cipher.update() and then decode it in one pass using the single Cipher.doFinal() call.

private static byte[] decrypt( Cipher dcipher, InputStream in, int BUFFER_SIZE ) throws Exception {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    byte[] inputBuffer = new byte[ BUFFER_SIZE ]; // 4K or 8K are pretty good buffer size values
    int r = in.read( inputBuffer );
    while ( r >= 0 ) {
        out.write( inputBuffer, 0, r );
        r = in.read( inputBuffer );
    }
    return dcipher.doFinal( out.toByteArray() );
}