public ValidateFooterChecksum ( byte chunkCount, byte chunkOverallLength, byte ephemeralKey, int footerChecksumLength ) : void | ||
chunkCount | byte | Number of chunks in the file. |
chunkOverallLength | byte | Length of all chunks in the file. |
ephemeralKey | byte | A 64 byte key. |
footerChecksumLength | int | The length of the checksum. |
return | void |
/// <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; }
/// <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; }