Amphoru Amphoru - 29 days ago 25
Java Question

Decrypting of AES/CBC/PKCS5Padding Error: Given final block not properly padded

I'm getting Given final block not properly padded error while decrypting AES/CBC/PKCS5Padding cipher on large encrypted file.

I think this issue is caused by adding wrong initialization vector in cipher.init() method.

I can't read whole file at runtime, so i need to encrypt fixed-size blocks. At this point I'm creating IV and storing it to .txt file. But in decrypting method I'm using the same IV every decryption cycle. How should I change this?

Encryption:

void encrypt() throws Exception{
char[] password = passwordText.getText().toCharArray();
byte[] salt = new byte[8];

/* Creating and saving salt */
salt = saveSalt(salt);

/* Securing password */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

if (choosedFile != null) {
/* Choosing algorithm for decryption */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

/* Getting plain file */
CipherInputStream fis = new CipherInputStream(new FileInputStream(choosedFile), cipher);
CipherOutputStream fos = new CipherOutputStream(new FileOutputStream(choosedFile+".encrypted"), cipher);

/* Encrypting and Measuring */
long startTime = System.currentTimeMillis();
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] rawText = new byte[128];
int count;
while((count = fis.read(rawText)) > 0) {
System.out.println(count);
byte[] encryptedText = cipher.doFinal(rawText);
fos.write(encryptedText, 0, count);
}
long stopTime = System.currentTimeMillis();

fis.close();
fos.close();

/* Creating initialization vector and storing*/
byte[] iVector = cipher.getIV();
saveIVector(iVector);

text.setText(text.getText() + "File was encrypted in " + (stopTime - startTime) + "ms.\n");
}

}


Decryption:

void decrypt() throws Exception {
/* Getting salt */
byte[] salt = getSalt();
/* Getting initialization vector */
byte[] iVector = getIVector();
/* Getting user password */
char[] password = passwordText.getText().toCharArray();


/* Securing password */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

if (choosedFile != null) {

/* Choosing algorithm for decryption */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
/* Getting ciphered file */


CipherInputStream fis = new CipherInputStream(new FileInputStream(choosedFile), cipher);
CipherOutputStream fos = new CipherOutputStream(new FileOutputStream(choosedFile+".decrypted"), cipher);

/* Decrypting and Measuring */
long startTime = System.currentTimeMillis();
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iVector));
byte[] rawText = new byte[128];
int count;
while((count = fis.read(rawText)) > 0) {
byte[] encryptedText = cipher.doFinal(rawText);
fos.write(encryptedText, 0, count);
}

long stopTime = System.currentTimeMillis();

fis.close();
fos.close();

Answer

When using CipherInputStream and CipherOutputStream, the streams handle all the calls to the cipher (that's why you pass the cipher to it on initialization). You just need to initialize it correctly, and stream the data through the stream, and the cipher stream will do the needed calls to update() and doFinal(). Remember to close the steams to trigger the doFinal().

Currently your code passes the data through the cipher several times in an uncontrolled way, and the data is messed up.

Also, you only need a CipherInputStream for decrypt, and a CipherOutputStream for encrypt. In your current code you use both for both encrypt and decrypt.

Encrypt could be something like this (this don't handle the iv ..):

...     
cipher.init(Cipher.ENCRYPT_MODE, secret);
InputStream is = new FileInputStream(choosedFile);
OutputStream os = new CipherOutputStream(new FileOutputStream(choosedFile+".encrypted"), cipher);

byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
    os.write(buffer, 0, len);
}

is.close();
os.close();
...
Comments