Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the file size after encryption using AES/CBC/PKCS5Padding in java

I tried to use encryption and decryption when sending files using socket. I use the AES/CBC/PKCS5Padding algorithm and first write the IV and then the file to the stream.

The problem is that the loop does not end when the file is receiving.

I think this is due to the file size and the encrypted file seems to be smaller than the original file, While I give the size of the original file to the receiver. If this hypothesis is correct, is there a way to calculate the size of the encrypted file?

File sender

            SecretKey keySpec = new SecretKeySpec(key, "AES");
            byte[] iv = AES.randomNonce(16);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);

            outputStream = socket.getOutputStream();
            outputStream.write(iv);
            outputStream.flush();
            inputStream = context.getContentResolver().openInputStream(message.getUri());
            cipherOutputStream = new CipherOutputStream(outputStream, cipher);

            long size = message.getSize();
            long written = 0;
            byte[] buffer = new byte[8192];
            int count;
            int percent = 0;
            while (!isStopped && (count = inputStream.read(buffer)) > 0) {
                cipherOutputStream.write(buffer, 0, count);
                written += count;
                int p = (int) (((float) written / (float) size) * 100);
                if (percent != p) {
                    percent = p;
                    if (onProgressListener != null) {
                        onProgressListener.onProgress(percent);
                    }
                }
            }
            cipherOutputStream.flush();
            if (onProgressListener != null) {
                onProgressListener.onEnd(null);
            }

File receiver

            inputStream = socket.getInputStream();
            fileOutputStream = new FileOutputStream(file);
            byte[] iv = new byte[16];
            inputStream.read(iv);

            SecretKey keySpec = new SecretKeySpec(key, "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);

            cipherInputStream = new CipherInputStream(inputStream, cipher);

            long size = message.getSize();
            long read = size;
            byte[] buffer = new byte[8192];
            int count;
            int percent = 0;
            while (!isStopped && read > 0 && (count = cipherInputStream.read(buffer, 0, (int) Math.min(buffer.length, read))) != -1) {
                fileOutputStream.write(buffer, 0, count);
                read -= count;
                int p = (int) (((float) read / (float) size) * 100);
                if (percent != p) {
                    percent = p;
                    if (onProgressListener != null) {
                        onProgressListener.onProgress(100 - percent);
                    }
                }
            }
            if (onProgressListener != null) {
                onProgressListener.onEnd(Uri.fromFile(file));
            }
like image 666
Hussein Yaqoobi Avatar asked Dec 22 '22 16:12

Hussein Yaqoobi


2 Answers

The improper code

As commented by PresidentJamesK.Polk you need to close the CipherOutputStream in the sender side by calling the .close() function that calls the doFinal() to finalize the encryption.

The padding size

Although Java says PKCS5Padding actually it is PKCS#7 Padding. PKCS#5 Padding is defined for 8 octets, i.e. block ciphers with 64-bit block size like DES.

PKCS#7 standard is on the rfc2315 (10.3 note 2):

For such algorithms, the method shall be to pad the input at the trailing end with k - (l mod k) octets all having value k - (l mod k), where l is the length of the input.

An octet is one byte and the k is the block size in bytes, and it is 16 for AES.

We need to calculate IV_length + message_size_with_padding

If we assume that you have l bytes to encrypt then the output size is

16 + l + 16 - (l mod 16) therefore at most 16 byte extended due to padding.

A sample Java code

    /**
    * A simple function to calculate the plaintext size after 
    * PKCS#7 padding is applied to the message.
    * 
    *          In Java PKCS#7 = PKCS#5
    * 
    * @param messageSize : the byte size of the plaintext
    * @return message size after the PKCS#7 padding is applied
    */
    public int paddedMessageSize( int messageSize ) {
        
        // first 16 is the prepended IV
        return ( 16 + messageSize + 16 - (messageSize % 16));
    }
    

Notes

CBC mode is vulnerable to padding oracle attacks where applicable. CBC mode provides only confidentiality. If you need integrity and authentication ( you should) either use CBC with HMAC or better use authenticated encryption modes like AES-GCM which doesn't require padding at all, however, you need to store the tag, too. If you fear the nonce reuse issue for AES-GCM then you can use AES-GCM-SIV, which is a nonce misuse resistant scheme. If you are not obligated to use AES you can prefer ChaCha20-Poly1305 which might be easier to use than AES-GCM [1][2].

like image 107
kelalaka Avatar answered Dec 31 '22 14:12

kelalaka


This is an issue of an Android project. When I run this code in Android Studio, Logcat did not show me any exception. But when I run this code in Windows using Eclipse, I got this exception:

java.io.IOException: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

So, based on Kelalaka's answer, I realized that the problem is that cipherInputStream and cipherOutputStream did not close properly. In fact, I first close inputStream and outputStream, and then cipher streams. So I closed the cipher streams first and then the other streams and now the encryption is working properly.

like image 35
Hussein Yaqoobi Avatar answered Dec 31 '22 13:12

Hussein Yaqoobi