1

I want to encrypt and then decrypt file use AES. I have read many topics about error "Given final block not properly padded". But i don't find solution for me.

Sorry about specify the language of my code, i don't know write language java

Here is my code :

Variables

// IV, secret, salt in the same time
private byte[] salt = { 'h', 'u', 'n', 'g', 'd', 'h', '9', '4' };
public byte[] iv;
public SecretKey secret;

createSecretKey

public void createSecretKey(String password){
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
    SecretKey tmp = factory.generateSecret(spec);
    secret = new SecretKeySpec(tmp.getEncoded(), "AES");
}

method Encrypt

public void encrypt(String inputFile){
    FileInputStream fis = new FileInputStream(inputFile);
    // Save file: inputFile.enc
    FileOutputStream fos = new FileOutputStream(inputFile + ".enc");

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);

    AlgorithmParameters params = cipher.getParameters();
    // Gen Initialization Vector
    iv = (byte[]) ((IvParameterSpec) params
            .getParameterSpec(IvParameterSpec.class)).getIV();
    // read from file (plaint text)  -----> save with .enc
    int readByte;
    byte[] buffer = new byte[1024];
    while ((readByte = fis.read(buffer)) != -1) {
        fos.write(cipher.doFinal(buffer), 0, readByte);
    }
    fis.close();
    fos.flush();
    fos.close();
}

method Decrypt

public void decrypt(String inputFile){
    FileInputStream fis = new FileInputStream(inputFile);
    // Save file: filename.dec
    FileOutputStream fos = new FileOutputStream(inputFile.substring(0,
            inputFile.length() - 4) + ".dec");

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
    // Read from file encrypted  ---> .dec 
    int readByte;
    byte[] buffer = new byte[1024];
    while ((readByte = fis.read(buffer)) != -1) {
        fos.write(cipher.doFinal(buffer), 0, readByte);
    }
    fos.flush();
    fos.close();
    fis.close();
}

Update

Solution: edit size of buffer is multiples of 16. Use CipherInput/ Output for read/ write file.

Tks Artjom B.

hungdh0x5e
  • 55
  • 10
  • Does `cipher.doFinal` always return `readByte` bytes? I bet no. – user253751 Jul 17 '15 at 11:19
  • https://issues.apache.org/jira/browse/PDFBOX-2469 shows what changes had to be done for PDFBox. This may or may not apply to you. A part was about segments that weren't encrypted at all, others were about what to call and how to do exception handling. A real pain. – Tilman Hausherr Jul 17 '15 at 11:46

2 Answers2

8

AES is a block cipher and as such only works on blocks of 16 bytes. A mode of operation such as CBC enables you to chain multiple blocks together. A padding such as PKCS#5 padding enables you to encrypt arbitrary length plaintext by filling the plaintext up to the next multiple of the block size.

The problem is that you're encrypting every 1024 bytes separately. Since 1024 divides the block size, the padding adds a full block before encryption. The ciphertext chunks are therefore 1040 bytes long. Then during decryption, you're only reading 1024 missing the padding. Java tries to decrypt it and then tries to remove the padding. If the padding is malformed (because it's not there), then the exception is thrown.

Easy fix

Simply increase your buffer for decryption to 1040 bytes.

Proper fix

Don't encrypt it in separate chunks, but either use Cipher#update(byte[], int, int) instead of Cipher.doFinal to update the ciphertext for every buffer you read or use a CipherInputStream.


Other security considerations:

You're missing a random IV. Without it, it may be possible for an attacker to see that you encrypted the same plaintext under the same key only by observing the ciphertexts.

You're missing ciphertext authentication. Without it, you can't reliably detect (malicious) changes in the ciphertexts and may open your system to attacks such as padding oracle attack. Either use an authenticated mode like GCM or run your created ciphertext through HMAC to create an authentication tag and write it to the end. Then you can verify the tag during/before decryption.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • Yeah, i edit with 1040 bytes and use CipherInputStream. It's work. – hungdh0x5e Jul 17 '15 at 12:56
  • The random IV and ciphertext authentication are important! – claj Jul 17 '15 at 13:10
  • As far as I can tell, the IV is random, because the JCE would automatically generate a new IV in this case. If you print out the IV, you can see that... – fhissen Jul 18 '15 at 11:58
  • @fhissen That depends entirely on the JCE provider. Not all providers do that. – Artjom B. Jul 18 '15 at 11:59
  • @Artjom B. Interesting and a good point. The default Sun/Oracle provider seems to do that. However, it is of course best practice to explicitly generate an IV - no question about that! – fhissen Jul 18 '15 at 12:05
0

You are under the false assumption that the length of the encrypted data equals the length of the plain data, but the encrypted AES data is always a multiple of the AES block size (16 bytes) and can have an additional full padding block.

The most efficient way of dealing with stream encryption would be to use JCE's CipherOutputStream and CipherInputStream (http://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherInputStream.html). These classes do all the work for you.

Also, make sure you always save the newly generated IV in your encryption method to be able to use it for the decryption.

fhissen
  • 347
  • 2
  • 7