/// <param name="data"></param>
 /// <param name="cryptoKey"></param>
 /// <param name="IVec"></param>
 /// <exception cref="UnsupportedEncodingException"></exception>
 /// <returns></returns>
 public static byte[] decryptData(byte[] data, byte[] cryptoKey, byte[] IVec)
 {
     return(AESHelper.decryptData(data, cryptoKey, IVec, true));  // true =^ PKCS7 padding
 }
        public static byte[] DecryptFile(
            string encryptedFilePath,
            byte[] fileCryptoKey,
            string baseIVec,
            int blockSize,
            int offset,
            int padding)
        {
            Console.WriteLine($"AES Decryption of file '{encryptedFilePath}' started");
            if (fileCryptoKey.Length <= 0 || blockSize <= 0)
            {
                throw new Exception("Crypto key for file can't be empty and block size must be bigger than zero");
            }

            // read the encrypted file
            byte[] fileBytes;
            try
            {
                fileBytes = File.ReadAllBytes(Path.GetFullPath(encryptedFilePath));
            }
            catch (IOException e)
            {
                throw new Exception("Could not read file", e);
            }

            // IVec in file header is base 64 encoded
            byte[] decodedFileIV = Base64Helper.decode(baseIVec);

            // report initial status
            int           fileSize        = fileBytes.Length;
            long          fileSizeFivePer = Convert.ToInt64(Math.Floor(fileSize * 0.05)); // 5% of file size; for status reporting
            string        byteProgress    = $" (0 / {fileSize} bytes)";
            StringBuilder routeString     = new StringBuilder();
            StringBuilder spaceString     = new StringBuilder();

            for (int i = 0; i < 20; i++)
            {
                spaceString.Append(" ");
            }
            Console.WriteLine($"Progress: [{spaceString}]{byteProgress}");

            // decrypt each block separately with its own initialization vector
            int blockNo = 0;

            byte[] result = new byte[fileSize - offset - padding];
            for (int byteNo = offset, nextStatusThreshold = offset, currentStep = 0; byteNo <= fileSize; byteNo += blockSize, ++blockNo)
            {
                byte[] blockIVec = AESHelper.ComputeBlockIVec(decodedFileIV, blockNo, fileCryptoKey);

                // get the input data for the current block (the last block may be shorter than [blockSize] bytes)
                int    end        = (byteNo + blockSize >= fileSize) ? fileSize : byteNo + blockSize;
                byte[] blockInput = new byte[end - byteNo];
                BlockCopy(fileBytes, byteNo, blockInput, 0, end - byteNo);

                PaddingMode currentPadding = (end == fileSize && padding > 0) ? PaddingMode.PKCS7 : PaddingMode.None;

                // get the decrypted data for this block ...
                byte[] decryptedBlock = AESHelper.DecryptData(blockInput, fileCryptoKey, blockIVec, true, currentPadding);

                // ... and append it to the previous data
                BlockCopy(decryptedBlock, 0, result, byteNo - offset, decryptedBlock.Length);

                // report intermediate status every 5%
                if (byteNo > nextStatusThreshold)
                {
                    int steps = byteNo / nextStatusThreshold;
                    nextStatusThreshold += Convert.ToInt32(fileSizeFivePer * steps);

                    currentStep       += steps;
                    byteProgress       = $" ({byteNo} / {fileSize} bytes)";
                    routeString.Length = 0;
                    for (int i = 0; i < currentStep; i++)
                    {
                        routeString.Append("#");
                    }
                    spaceString.Length = 0;
                    for (int i = 0; i < 20 - currentStep; i++)
                    {
                        spaceString.Append(" ");
                    }
                    Console.WriteLine($"Progress: [{routeString}{spaceString}]{byteProgress}");
                }
            }

            // newline after Status report
            byteProgress       = $" ({fileSize} / {fileSize} bytes)";
            routeString.Length = 0;
            for (int i = 0; i < 20; i++)
            {
                routeString.Append("#");
            }
            Console.WriteLine($"Progress: [{routeString}]{byteProgress}");

            Console.WriteLine("AES decryption of file finished");
            return(result);
        }
        /// <exception cref="UnsupportedEncodingException"></exception>
        public static byte[] decryptFile(
            string encryptedFilePath,
            byte[] fileCryptoKey,
            string baseIVec,
            int blockSize,
            int offset,
            int padding)
        {
            Console.WriteLine("AES Decryption of file '" + encryptedFilePath + "' started");
            if (fileCryptoKey.Length <= 0 || blockSize <= 0)
            {
                throw new SystemException("Crypto key for file can't be empty and block size must be bigger than zero");
            }

            // read the encrypted file
            byte[] fileBytes;
            try
            {
                fileBytes = File.ReadAllBytes(Path.GetFullPath(encryptedFilePath));
            }
            catch (IOException e)
            {
                throw new SystemException("Could not read file", e);
            }

            // IVec in file header is base 64 encoded
            byte[] decodedFileIV = Base64Helper.decode(baseIVec);

            // report initial status
            int           fileSize        = fileBytes.Length;
            long          fileSizeFivePer = Convert.ToInt64(Math.Floor(fileSize * 0.05)); // 5% of file size; for status reporting
            string        byteProgress    = " (0 / " + fileSize + " bytes)";
            StringBuilder routeString     = new StringBuilder();
            StringBuilder spaceString     = new StringBuilder();

            for (int i = 0; i < 20; i++)
            {
                spaceString.Append(" ");
            }
            Console.WriteLine("Progress: [{0}]{1}", spaceString, byteProgress);

            // decrypt each block separately with its own initialization vector
            int blockNo = 0;

            byte[] result = new byte[fileSize - offset - padding];
            for (int byteNo = offset, nextStatusThreshold = offset, currentStep = 0; byteNo <= fileSize; byteNo += blockSize, ++blockNo)
            {
                byte[] blockIVec = AESHelper.computeBlockIVec(decodedFileIV, blockNo, fileCryptoKey);

                // get the input data for the current block (the last block may be shorter than [blockSize] bytes)
                int    end        = (byteNo + blockSize >= fileSize) ? fileSize : byteNo + blockSize;
                byte[] blockInput = new byte[end - byteNo];
                System.Buffer.BlockCopy(fileBytes, byteNo, blockInput, 0, end - byteNo);

                // PKCS7 padding for the last block if a cipher padding size greater than 0 was specified in file header
                // Note: the only differnce between PKCS5 and 7 is the block size (8 and 0-255 bytes respectively),
                // Java only offers the 'PKCS5PADDING' identifier (legacy from the time only 8 byte block ciphers were available)
                bool currentPadding = (end == fileSize && padding > 0) ? true : false;

                // get the decrypted data for this block ...
                byte[] decryptedBlock = AESHelper.decryptData(blockInput, fileCryptoKey, blockIVec, currentPadding);

                // ... and append it to the previous data
                System.Buffer.BlockCopy(decryptedBlock, 0, result, byteNo - offset, decryptedBlock.Length);

                // report intermediate status every 5%
                if (byteNo > nextStatusThreshold)
                {
                    int steps = byteNo / nextStatusThreshold;
                    nextStatusThreshold += Convert.ToInt32(fileSizeFivePer * steps);

                    currentStep       += steps;
                    byteProgress       = " (" + byteNo + " / " + fileSize + " bytes)";
                    routeString.Length = 0;
                    for (int i = 0; i < currentStep; i++)
                    {
                        routeString.Append("#");
                    }
                    spaceString.Length = 0;
                    for (int i = 0; i < 20 - currentStep; i++)
                    {
                        spaceString.Append(" ");
                    }
                    Console.WriteLine("Progress: [{0}{1}]{2}", routeString, spaceString, byteProgress);
                }
            }

            // newline after Status report
            byteProgress       = " (" + fileSize + " / " + fileSize + " bytes)";
            routeString.Length = 0;
            for (int i = 0; i < 20; i++)
            {
                routeString.Append("#");
            }
            Console.WriteLine("Progress: [{0}]{1}", routeString, byteProgress);

            Console.WriteLine("AES decryption of file finished");
            return(result);
        }
        static void Main(string[] args)
        {
            if (args.Length < 3)
            {
                Console.WriteLine("Usage: bc-file-decryptor \n"
                                  + "[path to .bckey file] "
                                  + "[path to encrypted file] "
                                  + "[pwd] "
                                  + "[path for output (optional)]");
                return;
            }

            try
            {
                Console.WriteLine("Decryption process started");

                // ============================================
                // AES decryption of private key in .bckey file
                // =============================================

                // collect information about the user account
                AccountData accountInfo = new AccountData();
                accountInfo.ParseBCKeyFile(args[0]);
                accountInfo.Password = args[2];

                // decrypt the private key from the .bckey file
                byte[] decryptedPrivateKey = AESHelper.DecryptDataPBKDF2(
                    accountInfo.EncryptedPrivateKey, accountInfo.Password,
                    accountInfo.PBKDF2Salt, accountInfo.PBKDF2Iterations
                    );

                // =============================================
                // RSA decryption of file information (header)
                // =============================================

                // collect information about the file to be decrypted
                FileData fileData       = new FileData();
                string   outputFilePath = args.Length > 3 ? args[3] : "";
                fileData.ParseHeader(args[1], outputFilePath);

                // decrypt the file key (from the header) used for decryption of file data
                byte[] decryptedFileKey = RSAHelper.DecryptData(fileData.EncryptedFileKey, decryptedPrivateKey);

                byte[] fileCryptoKey = new byte[32];
                BlockCopy(decryptedFileKey, 32, fileCryptoKey, 0, 32);
                // =============================================
                // AES decryption of encrypted file
                // =============================================

                // decrypt the file data ...
                byte[] decryptedFileBytes = AESHelper.DecryptFile(
                    fileData.EncryptedFilePath, fileCryptoKey, fileData.BaseIVec,
                    fileData.BlockSize, fileData.HeaderLen, fileData.CipherPadding);

                File.WriteAllBytes(Path.GetFullPath(fileData.OutputFilePath), decryptedFileBytes);

                Console.WriteLine($"Successfully decrypted file '{fileData.EncryptedFilePath}', "
                                  + $"output: '{fileData.OutputFilePath}'");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                if (e.StackTrace != null)
                {
                    Console.WriteLine(e.StackTrace);
                }
            }
        }