Exemple #1
1
        /// <summary>
        /// Decrypts a file asynchron with libsodium and protobuf-net.
        /// </summary>
        /// <param name="recipientPrivateKey">A 32 byte private key.</param>
        /// <param name="inputFile">An encrypted file.</param>
        /// <param name="outputFolder">There the decrypted file will be stored.</param>
        /// <param name="decryptionProgress">StreamCryptorTaskAsyncProgress object.</param>
        /// <param name="overWrite">Overwrite the output file if it exist.</param>
        /// <param name="cancellationToken">Token to request task cancellation.</param>
        /// <returns>The fullpath to the decrypted file.</returns>
        /// <remarks>This method needs a revision.</remarks>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="FileNotFoundException"></exception>
        /// <exception cref="DirectoryNotFoundException"></exception>
        /// <exception cref="BadLastFileChunkException"></exception>
        /// <exception cref="BadFileChunkException"></exception>
        /// <exception cref="BadFileFooterException"></exception>
        /// <exception cref="BadFileHeaderException"></exception>
        /// <exception cref="IOException"></exception>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="OperationCanceledException"></exception>
        public static async Task<string> DecryptFileWithStreamAsync(byte[] recipientPrivateKey, string inputFile, string outputFolder, IProgress<StreamCryptorTaskAsyncProgress> decryptionProgress = null, bool overWrite = false, CancellationToken cancellationToken = default(CancellationToken))
        {
            string outputFile = String.Empty;
            string outputFullPath = String.Empty;
            //used to check the file length of the unencrypted file, will be renamed to the outputFile (if the file is valid)
            string tmpFile = String.Empty;
            string tmpFullPath = String.Empty;
            try
            {
                //validate the recipientPrivateKey
                if (recipientPrivateKey == null || recipientPrivateKey.Length != ASYNC_KEY_LENGTH)
                {
                    throw new ArgumentOutOfRangeException("recipientPrivateKey", "invalid recipientPrivateKey");
                }
                //validate the inputFile
                if (string.IsNullOrEmpty(inputFile))
                {
                    throw new ArgumentOutOfRangeException("inputFile", (inputFile == null) ? 0 : inputFile.Length,
                      string.Format("inputFile must be greater {0} in length.", 0));
                }
                if (!File.Exists(inputFile))
                {
                    throw new FileNotFoundException("inputFile", "inputFile could not be found.");
                }
                //validate the outputFolder
                if (string.IsNullOrEmpty(outputFolder) || !Directory.Exists(outputFolder))
                {
                    throw new DirectoryNotFoundException("outputFolder must exist");
                }

                if (outputFolder.IndexOfAny(Path.GetInvalidPathChars()) > -1)
                    throw new ArgumentException("The given path to the output folder contains invalid characters!");

                //get a tmp name
                tmpFile = Utils.GetRandomFileName(MASKED_FILENAME_LENGTH, TEMP_FILE_EXTENSION);
                tmpFullPath = Path.Combine(outputFolder, tmpFile);
                using (FileStream fileStreamEncrypted = File.OpenRead(inputFile))
                {
                    //first read the file header
                    EncryptedFileHeader encryptedFileHeader = Serializer.DeserializeWithLengthPrefix<EncryptedFileHeader>(fileStreamEncrypted, PrefixStyle.Base128, 1);
                    if (encryptedFileHeader == null)
                    {
                        throw new BadFileHeaderException("Missing file header: maybe not a StreamCryptor encrypted file");
                    }
                    //decrypt the ephemeral key with our public box 
                    byte[] ephemeralKey = PublicKeyBox.Open(encryptedFileHeader.Key, encryptedFileHeader.EphemeralNonce, recipientPrivateKey, encryptedFileHeader.SenderPublicKey);
                    //validate our file header
                    encryptedFileHeader.ValidateHeaderChecksum(ephemeralKey, HEADER_CHECKSUM_LENGTH);
                    //check file header for compatibility
                    if ((encryptedFileHeader.Version >= MIN_VERSION) && (encryptedFileHeader.BaseNonce.Length == CHUNK_BASE_NONCE_LENGTH))
                    {
                        long overallChunkLength = 0;
                        long overallBytesRead = 0;
                        //restore the original file name
                        byte[] decryptedPaddedFileName = SecretBox.Open(encryptedFileHeader.Filename, encryptedFileHeader.FilenameNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                        //remove the padding
                        outputFile = Utils.PaddedByteArrayToString(decryptedPaddedFileName);
                        //check the decrypted outputFile name for invalid characters to prevent directory traversal
                        if (outputFile.IndexOfAny(Path.GetInvalidFileNameChars()) > -1)
                            throw new ArgumentException("The given name of the decrypted output filename contains invalid characters!");

                        outputFullPath = Path.Combine(outputFolder, outputFile);
                        //check for the correct result of Path.Combine
                        if (!outputFullPath.StartsWith(outputFolder))
                            throw new ArgumentException("The given output path seems to be bad!");

                        //keep the position for the footer
                        long fileStreamEncryptedPosition = 0;
                        int chunkNumber = CHUNK_COUNT_START;
                        //write the file to the tmpFullPath
                        using (FileStream fileStreamUnencrypted = File.OpenWrite(tmpFullPath))
                        {
                            //start reading the chunks
                            EncryptedFileChunk encryptedFileChunk = new EncryptedFileChunk();
                            while ((encryptedFileChunk = Serializer.DeserializeWithLengthPrefix<EncryptedFileChunk>(fileStreamEncrypted, PrefixStyle.Base128, 2)) != null)
                            {
                                //cancel the task if requested
                                cancellationToken.ThrowIfCancellationRequested();
                                //indicates if ChunkIsLast was found, to prepend more than one last chnunks.
                                bool isLastChunkFound = false;
                                byte[] chunkNonce = new byte[NONCE_LENGTH];
                                //check if this is the last chunk
                                if (encryptedFileChunk.ChunkIsLast)
                                {
                                    if (!isLastChunkFound)
                                    {
                                        //last
                                        chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber, true);
                                        isLastChunkFound = true;
                                    }
                                    else
                                    {
                                        throw new BadLastFileChunkException("there are more than one last chunk, file could be damaged or manipulated!");
                                    }
                                }
                                else
                                {
                                    //there will propably come more
                                    chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber, false);
                                }
                                //check the current chunk checksum
                                encryptedFileChunk.ValidateChunkChecksum(ephemeralKey, CHUNK_CHECKSUM_LENGTH);
                                byte[] decrypted = SecretBox.Open(encryptedFileChunk.Chunk, chunkNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                                await fileStreamUnencrypted.WriteAsync(decrypted, 0, decrypted.Length, cancellationToken).ConfigureAwait(false);
                                overallBytesRead += (long)decrypted.Length;
                                chunkNumber++;
                                overallChunkLength += encryptedFileChunk.ChunkLength;
                                fileStreamEncryptedPosition = fileStreamEncrypted.Position;
                                //report status
                                if (decryptionProgress != null)
                                {
                                    var args = new StreamCryptorTaskAsyncProgress();
                                    args.ProgressPercentage = (int)(encryptedFileHeader.UnencryptedFileLength <= 0 ? 0 : (100 * overallBytesRead) / encryptedFileHeader.UnencryptedFileLength);
                                    decryptionProgress.Report(args);
                                }
                            }
                        }
                        //set the last position
                        fileStreamEncrypted.Position = fileStreamEncryptedPosition;
                        //prepare the EncryptedFileFooter
                        EncryptedFileFooter encryptedFileFooter = new EncryptedFileFooter();
                        //get the file footer and validate him
                        encryptedFileFooter = Serializer.DeserializeWithLengthPrefix<EncryptedFileFooter>(fileStreamEncrypted, PrefixStyle.Base128, 3);
                        if (encryptedFileFooter == null)
                        {
                            throw new BadFileFooterException("Missing file footer: file could be damaged or manipulated!");
                        }
                        //validate the footer checksum
                        encryptedFileFooter.ValidateFooterChecksum(BitConverter.GetBytes(chunkNumber), BitConverter.GetBytes(overallChunkLength), ephemeralKey, FOOTER_CHECKSUM_LENGTH);
                    }
                    else
                    {
                        throw new BadFileHeaderException("Incompatible file header: maybe different library version!");
                    }
                    //check the produced output for the correct length
                    if (encryptedFileHeader.UnencryptedFileLength == new FileInfo(tmpFullPath).Length)
                    {
                        //check if the new output file already exists
                        if (File.Exists(outputFullPath))
                        {
                            if (!overWrite)
                            {
                                //we don`t overwrite the file
                                throw new IOException("Decrypted file aleary exits, won`t overwrite");
                            }
                            else
                            {
                                //just delete the output file, so we can write a new one
                                File.Delete(outputFullPath);
                            }
                        }
                        File.Move(tmpFullPath, outputFullPath);
                    }
                    else
                    {
                        //File is not valid (return null)
                        outputFile = null;
                        File.Delete(tmpFullPath);
                    }
                }
            }
            catch (AggregateException ex)
            {
                //delete the temp file
                File.Delete(tmpFullPath);
                //and throw the exception
                ExceptionDispatchInfo.Capture(ex).Throw();
            }
            catch (OperationCanceledException ex)
            {
                //delete the temp file
                File.Delete(tmpFullPath);
                //and throw the exception
                ExceptionDispatchInfo.Capture(ex).Throw();
            }
            return outputFile;
        }
Exemple #2
1
 /// <summary>
 /// Encrypts a file asynchron with libsodium and protobuf-net.
 /// </summary>
 /// <param name="senderPrivateKey">A 32 byte private key.</param>
 /// <param name="senderPublicKey">A 32 byte public key.</param>
 /// <param name="recipientPublicKey">A 32 byte public key.</param>
 /// <param name="inputFile">The input file.</param>
 /// <param name="encryptionProgress">StreamCryptorTaskAsyncProgress object.</param>
 /// <param name="outputFolder">There the encrypted file will be stored, if this is null the input directory is used.</param>
 /// <param name="fileExtension">Set a custom file extenstion: .whatever</param>
 /// <param name="maskFileName">Replaces the filename with some random name.</param>
 /// <param name="cancellationToken">Token to request task cancellation.</param>
 /// <returns>The name of the encrypted file.</returns>
 /// <exception cref="ArgumentOutOfRangeException"></exception>
 /// <exception cref="FileNotFoundException"></exception>
 /// <exception cref="DirectoryNotFoundException"></exception>
 /// <exception cref="OperationCanceledException"></exception>
 public static async Task<string> EncryptFileWithStreamAsync(byte[] senderPrivateKey, byte[] senderPublicKey, byte[] recipientPublicKey, string inputFile, IProgress<StreamCryptorTaskAsyncProgress> encryptionProgress = null, string outputFolder = null, string fileExtension = DEFAULT_FILE_EXTENSION, bool maskFileName = false, CancellationToken cancellationToken = default(CancellationToken))
 {
     string outputFullPath = String.Empty;
     string outputFile = String.Empty;
     //validate the senderPrivateKey
     if (senderPrivateKey == null || senderPrivateKey.Length != ASYNC_KEY_LENGTH)
     {
         throw new ArgumentOutOfRangeException("senderPrivateKey", "invalid senderPrivateKey");
     }
     //validate the senderPublicKey
     if (senderPublicKey == null || senderPublicKey.Length != ASYNC_KEY_LENGTH)
     {
         throw new ArgumentOutOfRangeException("senderPublicKey", "invalid senderPublicKey");
     }
     //validate the recipientPublicKey
     if (recipientPublicKey == null || recipientPublicKey.Length != ASYNC_KEY_LENGTH)
     {
         throw new ArgumentOutOfRangeException("recipientPublicKey", "invalid recipientPublicKey");
     }
     //validate the inputFile
     if (string.IsNullOrEmpty(inputFile))
     {
         throw new ArgumentOutOfRangeException("inputFile", (inputFile == null) ? 0 : inputFile.Length,
           string.Format("inputFile must be greater {0} in length.", 0));
     }
     if (!File.Exists(inputFile))
     {
         throw new FileNotFoundException("inputFile", "inputFile could not be found.");
     }
     //retrieve file info
     FileInfo inputFileInfo = new FileInfo(inputFile);
     if (inputFileInfo.Name.Length > MAX_FILENAME_LENGTH)
     {
         throw new ArgumentOutOfRangeException("inputFile", string.Format("inputFile name must be smaller {0} in length.", MAX_FILENAME_LENGTH));
     }
     //validate the file extension
     if (!fileExtension[0].Equals('.'))
     {
         throw new ArgumentOutOfRangeException("fileExtension", "fileExtension must start with: .");
     }
     //validate the outputFolder
     if (string.IsNullOrEmpty(outputFolder))
     {
         //use the same directory as inputFile
         outputFolder = inputFileInfo.DirectoryName;
     }
     else
     {
         if (!Directory.Exists(outputFolder))
         {
             throw new DirectoryNotFoundException("outputFolder could not be found.");
         }
     }
     //generate the name of the output file
     if (maskFileName)
     {
         //store the output file with a masked file name and the fileExtension
         outputFile = Utils.GetRandomFileName(MASKED_FILENAME_LENGTH, fileExtension);
         outputFullPath = Path.Combine(outputFolder, outputFile);
     }
     else
     {
         //store the output file, just with the fileExtension
         outputFile = inputFileInfo.Name + fileExtension;
         outputFullPath = Path.Combine(outputFolder, outputFile);
     }
     //go for the streams
     using (FileStream fileStreamEncrypted = File.OpenWrite(outputFullPath))
     {
         using (FileStream fileStreamUnencrypted = File.OpenRead(inputFile))
         {
             //initialize our file header for encryption
             EncryptedFileHeader encryptedFileHeader = new EncryptedFileHeader(
                 CURRENT_VERSION, NONCE_LENGTH, CHUNK_BASE_NONCE_LENGTH, fileStreamUnencrypted.Length, 
                 senderPrivateKey, senderPublicKey, recipientPublicKey);
             //protect and set the file name to the header
             encryptedFileHeader.ProtectFileName(inputFileInfo.Name, MAX_FILENAME_LENGTH);
             //generate and set the checksum to validate our file header on decryption
             encryptedFileHeader.SetHeaderChecksum(HEADER_CHECKSUM_LENGTH);
             //write the file header to the stream
             Serializer.SerializeWithLengthPrefix(fileStreamEncrypted, encryptedFileHeader, PrefixStyle.Base128, 1);
             //we start at chunk number 0
             int chunkNumber = CHUNK_COUNT_START;
             //used to calculate the footer checksum
             long overallChunkLength = 0;
             //used for progress reporting
             long overallBytesRead = 0;
             int bytesRead;
             do
             {
                 //cancel the task if requested
                 cancellationToken.ThrowIfCancellationRequested();
                 //start reading the unencrypted file in chunks of the given length: CHUNK_LENGTH
                 byte[] unencryptedChunk = new byte[CHUNK_LENGTH];
                 bytesRead = await fileStreamUnencrypted.ReadAsync(unencryptedChunk, 0, CHUNK_LENGTH, cancellationToken).ConfigureAwait(false);
                 //check if there is still some work
                 if (bytesRead != 0)
                 {
                     //prepare the EncryptedFileChunk
                     EncryptedFileChunk encryptedFileChunk = new EncryptedFileChunk();
                     byte[] readedBytes = new byte[bytesRead];
                     //cut unreaded bytes
                     Array.Copy(unencryptedChunk, readedBytes, bytesRead);
                     //check if the file is smaller or equal the CHUNK_LENGTH
                     if (encryptedFileHeader.UnencryptedFileLength <= CHUNK_LENGTH)
                     {
                         //so we have the one and only chunk
                         encryptedFileChunk = EncryptFileChunk(readedBytes, chunkNumber, encryptedFileHeader.BaseNonce, encryptedFileHeader.UnencryptedEphemeralKey, true);
                     }
                     else
                     {
                         //let`s check if this chunk is smaller than the given CHUNK_LENGTH
                         if (bytesRead < CHUNK_LENGTH)
                         {
                             //it`s the last chunk in the stream
                             encryptedFileChunk = EncryptFileChunk(readedBytes, chunkNumber, encryptedFileHeader.BaseNonce, encryptedFileHeader.UnencryptedEphemeralKey, true);
                         }
                         else
                         {
                             //it`s a full chunk
                             encryptedFileChunk = EncryptFileChunk(readedBytes, chunkNumber, encryptedFileHeader.BaseNonce, encryptedFileHeader.UnencryptedEphemeralKey, false);
                         }
                     }
                     overallChunkLength += encryptedFileChunk.Chunk.Length;
                     //write encryptedFileChunk to the output stream
                     Serializer.SerializeWithLengthPrefix(fileStreamEncrypted, encryptedFileChunk, PrefixStyle.Base128, 2);
                     //increment for the next chunk
                     chunkNumber++;
                     overallBytesRead += bytesRead;
                     //report status
                     if (encryptionProgress != null)
                     {
                         var args = new StreamCryptorTaskAsyncProgress();
                         args.ProgressPercentage = (int)(encryptedFileHeader.UnencryptedFileLength <= 0 ? 0 : (100 * overallBytesRead) / encryptedFileHeader.UnencryptedFileLength);
                         encryptionProgress.Report(args);
                     }
                 }
                 else
                 {
                     //Prepare the EncryptedFileFooter for encryption.
                     EncryptedFileFooter encryptedFileFooter = new EncryptedFileFooter();
                     //generate the FooterChecksum
                     encryptedFileFooter.SetFooterChecksum(BitConverter.GetBytes(chunkNumber), BitConverter.GetBytes(overallChunkLength), encryptedFileHeader.UnencryptedEphemeralKey, FOOTER_CHECKSUM_LENGTH);
                     //put the footer to the stream
                     Serializer.SerializeWithLengthPrefix(fileStreamEncrypted, encryptedFileFooter, PrefixStyle.Base128, 3);
                 }
             } while (bytesRead != 0);
         }
     }
     return outputFile;
 }
Exemple #3
0
 /// <summary>
 /// Encrypts a file chunk.
 /// </summary>
 /// <param name="unencryptedChunk">The bytes to encrypt.</param>
 /// <param name="chunkNumber">The current chunk number.</param>
 /// <param name="baseNonce">The base nonce.</param>
 /// <param name="ephemeralKey">The generated ephemeral key.</param>
 /// <param name="isLast">last chunk in the row.</param>
 /// <returns>An EncryptedFileChunk.</returns>
 private static EncryptedFileChunk EncryptFileChunk(byte[] unencryptedChunk, int chunkNumber, byte[] baseNonce, byte[] ephemeralKey, bool isLast)
 {
     //prepare the EncryptedFileChunk
     EncryptedFileChunk encryptedFileChunk = new EncryptedFileChunk();
     byte[] chunkNonce = new byte[NONCE_LENGTH];
     //generate the chunk nonce
     chunkNonce = GetChunkNonce(baseNonce, chunkNumber, isLast);
     //is it the last chunk?
     encryptedFileChunk.ChunkIsLast = isLast;
     //sym encrypt the chunk 
     byte[] encryptedChunk = SecretBox.Create(unencryptedChunk, chunkNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
     //set the encrypted chunk
     encryptedFileChunk.Chunk = encryptedChunk;
     //and also the length of it
     encryptedFileChunk.ChunkLength = encryptedChunk.Length;
     //generate a 64 byte checksum for this chunk
     encryptedFileChunk.SetChunkChecksum(ephemeralKey, CHUNK_CHECKSUM_LENGTH);
     return encryptedFileChunk;
 }
Exemple #4
0
        /// <summary>
        /// Decrypts a file asynchron into memory with libsodium and protobuf-net.
        /// </summary>
        /// <param name="recipientPrivateKey">A 32 byte private key.</param>
        /// <param name="inputFile">An encrypted file.</param>
        /// <param name="decryptionProgress">StreamCryptorTaskAsyncProgress object.</param>
        /// <param name="cancellationToken">Token to request task cancellation.</param>
        /// <returns>A DecryptedFile object.</returns>
        /// <remarks>This method can throw an OutOfMemoryException when there is not enough ram to hold the DecryptedFile!</remarks>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="FileNotFoundException"></exception>
        /// <exception cref="BadLastFileChunkException"></exception>
        /// <exception cref="BadFileChunkException"></exception>
        /// <exception cref="BadFileFooterException"></exception>
        /// <exception cref="BadFileHeaderException"></exception>
        /// <exception cref="IOException"></exception>
        /// <exception cref="OutOfMemoryException"></exception>
        /// <exception cref="OperationCanceledException"></exception>
        public static async Task<DecryptedFile> DecryptFileWithStreamAsync(byte[] recipientPrivateKey, string inputFile, IProgress<StreamCryptorTaskAsyncProgress> decryptionProgress = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            DecryptedFile decryptedFile = new DecryptedFile();
            try
            {
                //validate the recipientPrivateKey
                if (recipientPrivateKey == null || recipientPrivateKey.Length != ASYNC_KEY_LENGTH)
                {
                    throw new ArgumentOutOfRangeException("recipientPrivateKey", "invalid recipientPrivateKey");
                }
                //validate the inputFile
                if (string.IsNullOrEmpty(inputFile))
                {
                    throw new ArgumentOutOfRangeException("inputFile", (inputFile == null) ? 0 : inputFile.Length,
                      string.Format("inputFile must be greater {0} in length.", 0));
                }
                if (!File.Exists(inputFile))
                {
                    throw new FileNotFoundException("inputFile", "inputFile could not be found.");
                }

                using (FileStream fileStreamEncrypted = File.OpenRead(inputFile))
                {
                    //first read the file header
                    EncryptedFileHeader encryptedFileHeader = new EncryptedFileHeader();
                    encryptedFileHeader = Serializer.DeserializeWithLengthPrefix<EncryptedFileHeader>(fileStreamEncrypted, PrefixStyle.Base128, 1);
                    //decrypt the ephemeral key with our public box 
                    byte[] ephemeralKey = PublicKeyBox.Open(encryptedFileHeader.Key, encryptedFileHeader.EphemeralNonce, recipientPrivateKey, encryptedFileHeader.SenderPublicKey);
                    //validate our file header
                    encryptedFileHeader.ValidateHeaderChecksum(ephemeralKey, HEADER_CHECKSUM_LENGTH);
                    //check file header for compatibility
                    if ((encryptedFileHeader.Version >= MIN_VERSION) && (encryptedFileHeader.BaseNonce.Length == CHUNK_BASE_NONCE_LENGTH))
                    {
                        long overallChunkLength = 0;
                        long overallBytesRead = 0;
                        //restore the original file name
                        byte[] encryptedPaddedFileName = SecretBox.Open(encryptedFileHeader.Filename, encryptedFileHeader.FilenameNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                        //remove the padding
                        decryptedFile.FileName = Utils.PaddedByteArrayToString(encryptedPaddedFileName);
                        //keep the position for the footer
                        long fileStreamEncryptedPosition = 0;
                        int chunkNumber = CHUNK_COUNT_START;
                        //write the file to the tmpFullPath
                        using (MemoryStream fileStreamUnencrypted = new MemoryStream())
                        {
                            //start reading the chunks
                            EncryptedFileChunk encryptedFileChunk = new EncryptedFileChunk();
                            while ((encryptedFileChunk = Serializer.DeserializeWithLengthPrefix<EncryptedFileChunk>(fileStreamEncrypted, PrefixStyle.Base128, 2)) != null)
                            {
                                //cancel the task if requested
                                cancellationToken.ThrowIfCancellationRequested();
                                //indicates if ChunkIsLast was found, to prepend more than one last chnunks.
                                bool isLastChunkFound = false;
                                byte[] chunkNonce = new byte[NONCE_LENGTH];
                                //check if this is the last chunk
                                if (encryptedFileChunk.ChunkIsLast)
                                {
                                    if (!isLastChunkFound)
                                    {
                                        //last
                                        chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber, true);
                                        isLastChunkFound = true;
                                    }
                                    else
                                    {
                                        throw new BadLastFileChunkException("there are more than one last chunk, file could be damaged or manipulated!");
                                    }
                                }
                                else
                                {
                                    //there will propably come more
                                    chunkNonce = GetChunkNonce(encryptedFileHeader.BaseNonce, chunkNumber);
                                }
                                //check the current chunk checksum
                                encryptedFileChunk.ValidateChunkChecksum(ephemeralKey, CHUNK_CHECKSUM_LENGTH);
                                byte[] decrypted = SecretBox.Open(encryptedFileChunk.Chunk, chunkNonce, Utils.GetEphemeralEncryptionKey(ephemeralKey));
                                await fileStreamUnencrypted.WriteAsync(decrypted, 0, decrypted.Length, cancellationToken).ConfigureAwait(false);
                                overallBytesRead += (long)decrypted.Length;
                                chunkNumber++;
                                overallChunkLength += encryptedFileChunk.ChunkLength;
                                fileStreamEncryptedPosition = fileStreamEncrypted.Position;
                                //report status
                                if (decryptionProgress != null)
                                {
                                    var args = new StreamCryptorTaskAsyncProgress();
                                    args.ProgressPercentage = (int)(encryptedFileHeader.UnencryptedFileLength <= 0 ? 0 : (100 * overallBytesRead) / encryptedFileHeader.UnencryptedFileLength);
                                    decryptionProgress.Report(args);
                                }
                            }
                            decryptedFile.FileData = fileStreamUnencrypted.ToArray();
                            decryptedFile.FileSize = decryptedFile.FileData.Length;
                        }
                        //set the last position
                        fileStreamEncrypted.Position = fileStreamEncryptedPosition;
                        //prepare the EncryptedFileFooter
                        EncryptedFileFooter encryptedFileFooter = new EncryptedFileFooter();
                        //get the file footer and validate him
                        encryptedFileFooter = Serializer.DeserializeWithLengthPrefix<EncryptedFileFooter>(fileStreamEncrypted, PrefixStyle.Base128, 3);
                        if (encryptedFileFooter == null)
                        {
                            throw new BadFileFooterException("Missing file footer: file could be damaged or manipulated!");
                        }
                        //validate the footer checksum
                        encryptedFileFooter.ValidateFooterChecksum(BitConverter.GetBytes(chunkNumber), BitConverter.GetBytes(overallChunkLength), ephemeralKey, FOOTER_CHECKSUM_LENGTH);
                    }
                    else
                    {
                        throw new BadFileHeaderException("Incompatible file header: maybe different library version!");
                    }
                    //check the produced output for the correct length
                    if (encryptedFileHeader.UnencryptedFileLength != decryptedFile.FileSize)
                    {
                        //File is not valid (return null)
                        decryptedFile = null;
                    }
                }
            }
            catch (AggregateException ex)
            {
                //and throw the exception
                ExceptionDispatchInfo.Capture(ex).Throw();
            }
            return decryptedFile;
        }