// Path: src/main/java/com/github/horrorho/liquiddonkey/exception/BadDataException.java
// public class BadDataException extends IOException {
// 
//     public BadDataException() {
//     }
// 
//     public BadDataException(String message) {
//         super(message);
//     }
// 
//     public BadDataException(String message, Throwable cause) {
//         super(message, cause);
//     }
// 
//     public BadDataException(Throwable cause) {
//         super(cause);
//     }
// }


import com.github.horrorho.liquiddonkey.exception.BadDataException;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.util.Arrays;
import net.jcip.annotations.NotThreadSafe;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.digests.GeneralDigest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
            KeyParameter fileKey = deriveFileKey(key);

            long blockCount = Files.size(path) + (decryptedSize > 0 ? 0x0FFF : 0) >> 12;

            encrypted = path.getParent().resolve(path.getFileName() + ".encrypted");
            Files.move(path, encrypted, StandardCopyOption.REPLACE_EXISTING);

            try (InputStream input = Files.newInputStream(encrypted, READ);
                    OutputStream output = Files.newOutputStream(path, CREATE, WRITE, TRUNCATE_EXISTING)) {

                byte[] checksum = decrypt(input, output, blockCount, ivKey, fileKey);

                if (decryptedSize == 0) {
                    // iOS 5
                    decryptedSize = trailer(input, checksum);

                    if (decryptedSize == -1) {
                        logger.warn("-- decrypt() > bad trailer/ checksum");
                    }
                }
            }

            long size = Files.size(path);
            if (decryptedSize > 0 && size > decryptedSize) {
                logger.debug("-- decrypt() > truncating to: {} from: {}", decryptedSize, size);
                Files.newByteChannel(path, WRITE).truncate(decryptedSize).close();
            } else if (Files.size(path) < decryptedSize) {
                logger.warn("-- decrypt() > short output size: {} expected: {}", Files.size(path), decryptedSize);
            }
        } catch (BufferUnderflowException | DataLengthException ex) {
// Path: src/main/java/com/github/horrorho/liquiddonkey/exception/BadDataException.java
// public class BadDataException extends IOException {
// 
//     public BadDataException() {
//     }
// 
//     public BadDataException(String message) {
//         super(message);
//     }
// 
//     public BadDataException(String message, Throwable cause) {
//         super(message, cause);
//     }
// 
//     public BadDataException(Throwable cause) {
//         super(cause);
//     }
// }


// Path: src/main/java/com/github/horrorho/liquiddonkey/cloud/file/FileDecrypter.java
import com.github.horrorho.liquiddonkey.exception.BadDataException;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.util.Arrays;
import net.jcip.annotations.NotThreadSafe;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.digests.GeneralDigest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

            KeyParameter fileKey = deriveFileKey(key);

            long blockCount = Files.size(path) + (decryptedSize > 0 ? 0x0FFF : 0) >> 12;

            encrypted = path.getParent().resolve(path.getFileName() + ".encrypted");
            Files.move(path, encrypted, StandardCopyOption.REPLACE_EXISTING);

            try (InputStream input = Files.newInputStream(encrypted, READ);
                    OutputStream output = Files.newOutputStream(path, CREATE, WRITE, TRUNCATE_EXISTING)) {

                byte[] checksum = decrypt(input, output, blockCount, ivKey, fileKey);

                if (decryptedSize == 0) {
                    // iOS 5
                    decryptedSize = trailer(input, checksum);

                    if (decryptedSize == -1) {
                        logger.warn("-- decrypt() > bad trailer/ checksum");
                    }
                }
            }

            long size = Files.size(path);
            if (decryptedSize > 0 && size > decryptedSize) {
                logger.debug("-- decrypt() > truncating to: {} from: {}", decryptedSize, size);
                Files.newByteChannel(path, WRITE).truncate(decryptedSize).close();
            } else if (Files.size(path) < decryptedSize) {
                logger.warn("-- decrypt() > short output size: {} expected: {}", Files.size(path), decryptedSize);
            }
        } catch (BufferUnderflowException | DataLengthException ex) {
            throw new BadDataException("Cipher exception", ex);