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));
}
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].
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With