Ejemplo n.º 1
0
        public void Initialize(DerivedBytesProvider derivedBytesProvider, Action <Dictionary <string, byte[]> > afterHeaderRead = null)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(nameof(HelixFileDecryptor));
            }
            if (m_Initialized)
            {
                throw new InvalidOperationException("Decryptor has already been initialized");
            }
            if (derivedBytesProvider == null)
            {
                throw new ArgumentNullException(nameof(derivedBytesProvider));
            }

            try
            {
                decryptor = new MultiBlockDecryptor(streamIn);
                decryptor.Initialize(derivedBytesProvider, afterHeaderRead);
                m_Initialized = true;
            }
            catch (FileDesignatorException ex)
            {
                throw new UnsupportedFileTypeException("Unsupported file type", ex);
            }
            catch (HMACAuthenticationException ex)
            {
                throw new InvalidPasswordException("Invalid password", ex);
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Returns the header for an encrypted file
        /// </summary>
        public static FileEntry DecryptHeader(string encrFile, DerivedBytesProvider derivedBytesProvider)
        {
            using var inputStream = File.OpenRead(encrFile);
            using var decryptor   = new HelixFileDecryptor(inputStream);
            try
            {
                decryptor.Initialize(derivedBytesProvider);

                FileEntry header = decryptor.ReadHeader();

                if (!header.IsValid(out HeaderCorruptionException ex))
                {
                    throw ex;
                }

                return(header);
            }
            catch (FileCorruptionException ex)
            {
                throw new FileCorruptionException($"Failed to decrypt {encrFile}, {ex.Message}", ex);
            }
            catch (AuthenticatedEncryptionException ex)
            {
                throw new FileCorruptionException($"Failed to decrypt {encrFile}, {ex.Message}", ex);
            }
        }
Ejemplo n.º 3
0
 public DirectoryPair(string decrDirectory, string encrDirectory, DerivedBytesProvider derivedBytesProvider, bool whatIf)
 {
     this.DecrDirectory        = new FSDirectory(new DirectoryInfo(decrDirectory).FullName, whatIf);
     this.EncrDirectory        = new FSDirectory(new DirectoryInfo(encrDirectory).FullName, whatIf);
     this.DerivedBytesProvider = derivedBytesProvider;
     this.WhatIf = whatIf;
 }
Ejemplo n.º 4
0
        public static DirectoryHeader Load(string filePath, DerivedBytesProvider derivedBytesProvider)
        {
            DirectoryHeader directoryHeader = new DirectoryHeader();

            using Stream streamIn = File.OpenRead(filePath);
            using HelixFileDecryptor decryptor = new HelixFileDecryptor(streamIn);
            decryptor.Initialize(derivedBytesProvider);
            directoryHeader.FileVersion = decryptor.FileVersion;

            directoryHeader.Header      = decryptor.ReadHeader();
            directoryHeader.FileVersion = decryptor.FileVersion;

            var contentSerialized = decryptor.GetContentString();

            //var contentDeserialized = JsonConvert.DeserializeAnonymousType(contentSerialized, new { EncryptedFileNameSalt = new byte[] { } });
            JsonConvert.PopulateObject(contentSerialized, directoryHeader);

            if (!Regex.IsMatch(directoryHeader.DirectoryId, "^[0-9A-F]{32}$"))
            {
                throw new HelixException("Data curruption directory header, DirectoryId malformed");
            }
            if (directoryHeader.FileNameKey == null || directoryHeader.FileNameKey.Length != 32)
            {
                throw new HelixException("Data curruption directory header, FileNameKey missing or insufficient length");
            }

            return(directoryHeader);
        }
Ejemplo n.º 5
0
        public void Initialize(DerivedBytesProvider derivedBytesProvider, Action <Dictionary <string, byte[]> > afterHeaderRead = null)
        {
            if (derivedBytesProvider == null)
            {
                throw new ArgumentNullException(nameof(derivedBytesProvider));
            }

            //header
            //FileDesignator (8 bytes) + passwordSalt (32 bytes) + hmac salt (32 bytes) + iv (32 bytes)
            FileHeader header = new FileHeader();

            StreamRead(streamIn, header.fileDesignator);
            StreamRead(streamIn, header.passwordSalt);
            StreamRead(streamIn, header.hmacSalt);
            StreamRead(streamIn, header.iv);
            StreamRead(streamIn, header.headerAuthnDisk);

            afterHeaderRead?.Invoke(header.ToDictionary());


            FileVersion = HelixFileVersion.GetVersion(header.fileDesignator);
            if (FileVersion == null)
            {
                throw new FileDesignatorException("Invalid file format, file designator not correct");
            }

            DerivedBytes = derivedBytesProvider.GetDerivedBytes(header.passwordSalt, FileVersion.DerivedBytesIterations);

            byte[] headerBytes = header.GetBytesToHash();
            byte[] hmacFullKey = ByteBlock.ConcatenateBytes(header.hmacSalt, DerivedBytes.Key);

            hmacHash = new HMACSHA256(hmacFullKey);


            //Validate Header HMAC
            byte[] headerAuthnComputed = hmacHash.ComputeHash(headerBytes);
            if (!ByteBlock.Equals(header.headerAuthnDisk, headerAuthnComputed))
            {
                throw new HMACAuthenticationException("Header HMAC Authentication Failed");
            }

            hmacTransform = new HMACDecrypt(headerAuthnComputed, hmacHash);
            hmacStream    = new CryptoStream2(streamIn, hmacTransform, CryptoStreamMode.Read);

            aesTransform           = Aes.Create();
            aesTransform.KeySize   = 256;
            aesTransform.BlockSize = 128;
            aesTransform.Mode      = CipherMode.CBC;
            aesTransform.Padding   = PaddingMode.PKCS7;
            aesTransform.IV        = header.iv;
            aesTransform.Key       = DerivedBytes.Key;

            aesStream = new CryptoStream2(hmacStream, aesTransform.CreateDecryptor(), CryptoStreamMode.Read);

            gzipStream = new GZipStream(aesStream, CompressionMode.Decompress, true);

            initialized = true;
        }
Ejemplo n.º 6
0
        public static int Inspect(InspectOptions options, ConsoleEx consoleEx = null)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            consoleEx ??= new ConsoleEx();

            using (Stream fileIn = File.OpenRead(options.File))
                using (HelixFileDecryptor decryptor = new HelixFileDecryptor(fileIn))
                {
                    consoleEx.WriteLine("== Outer Header ==");
                    Dictionary <string, byte[]> header = null;
                    decryptor.Initialize(DerivedBytesProvider.FromPassword(options.Password, options.KeyFile), (h) => header = h);
                    consoleEx.WriteLine("Designator:    "
                                        + BitConverter.ToString(header[nameof(FileHeader.fileDesignator)]).Replace("-", "")
                                        + " (" + new string(Encoding.ASCII.GetString(header[nameof(FileHeader.fileDesignator)])
                                                            .Select(c => Char.IsControl(c) ? '?' : c)
                                                            .ToArray()) + ")");
                    consoleEx.WriteLine("Password Salt: " + BitConverter.ToString(header[nameof(FileHeader.passwordSalt)]).Replace("-", ""));
                    consoleEx.WriteLine("HMAC Salt:     " + BitConverter.ToString(header[nameof(FileHeader.hmacSalt)]).Replace("-", ""));
                    consoleEx.WriteLine("IV:            " + BitConverter.ToString(header[nameof(FileHeader.iv)]).Replace("-", ""));
                    consoleEx.WriteLine("Authn (HMAC):  " + BitConverter.ToString(header[nameof(FileHeader.headerAuthnDisk)]).Replace("-", ""));
                    consoleEx.WriteLine();

                    consoleEx.WriteLine("== Inner Header ==");
                    decryptor.ReadHeader(
                        afterRawMetadata: (r) => consoleEx.WriteLine(JsonFormat(r)));
                    consoleEx.WriteLine();

                    using Stream content = decryptor.GetContentStream();
                    ContentPreview(content, consoleEx, options.ContentFormat);
                    consoleEx.WriteLine();
                }

            //Reopen to calculate the checksum
            using (Stream fileIn = File.OpenRead(options.File))
                using (HelixFileDecryptor decryptor = new HelixFileDecryptor(fileIn))
                {
                    decryptor.Initialize(DerivedBytesProvider.FromPassword(options.Password, options.KeyFile));
                    decryptor.ReadHeader();
                    using Stream content = decryptor.GetContentStream();
                    Checksum(content, consoleEx);


                    return(0);
                }
        }
Ejemplo n.º 7
0
        public HelixFileEncryptor(Stream streamOut, DerivedBytesProvider derivedBytesProvider, HelixFileVersion fileVersion = null)
        {
            if (streamOut == null)
            {
                throw new ArgumentNullException(nameof(streamOut));
            }
            if (!streamOut.CanWrite)
            {
                throw new ArgumentException("streamOut must be writable", nameof(streamOut));
            }
            if (derivedBytesProvider == null)
            {
                throw new ArgumentNullException(nameof(derivedBytesProvider));
            }

            this.FileVersion = fileVersion ?? HelixFileVersion.Default;

            this.StreamOut    = streamOut;
            this.DerivedBytes = derivedBytesProvider.GetDerivedBytes(FileVersion.DerivedBytesIterations);
        }
Ejemplo n.º 8
0
        public void Save(string filePath, DerivedBytesProvider derivedBytesProvider, HelixFileVersion fileVersion = null)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                throw new ArgumentNullException(nameof(filePath));
            }
            if (derivedBytesProvider == null)
            {
                throw new ArgumentNullException(nameof(derivedBytesProvider));
            }

            FileVersion = fileVersion ?? HelixFileVersion.Default;

            using Stream streamOut             = File.Open(filePath, FileMode.Create, FileAccess.Write);
            using HelixFileEncryptor encryptor = new HelixFileEncryptor(streamOut, derivedBytesProvider, fileVersion);
            encryptor.WriteHeader(Header);

            var contentSerialized = JsonConvert.SerializeObject(this);

            encryptor.WriteContent(contentSerialized);
        }
Ejemplo n.º 9
0
        public static string Decrypt(string encrPath, string decrPath, DerivedBytesProvider derivedBytesProvider, FileDecryptOptions options = null)
        {
            if (string.IsNullOrWhiteSpace(encrPath))
            {
                throw new ArgumentNullException(nameof(encrPath));
            }
            if (string.IsNullOrWhiteSpace(decrPath))
            {
                throw new ArgumentNullException(nameof(decrPath));
            }
            if (derivedBytesProvider == null)
            {
                throw new ArgumentNullException(nameof(derivedBytesProvider));
            }

            options ??= new FileDecryptOptions();

            using (FileStream inputStream = File.OpenRead(encrPath))
                using (HelixFileDecryptor decryptor = new HelixFileDecryptor(inputStream))
                {
                    decryptor.Initialize(derivedBytesProvider);

                    FileEntry header = decryptor.ReadHeader();

                    options?.AfterMetadataRead?.Invoke(header, options);

                    if (!header.IsValid(out HeaderCorruptionException ex))
                    {
                        throw ex;
                    }

                    string decrFullFileName   = decrPath;
                    string decrStagedFileName = decrPath + HelixConsts.StagedHxExtention;
                    string decrBackupFileName = decrPath + HelixConsts.BackupExtention;

                    if (header.EntryType == FileEntryType.File)
                    {
                        if (!string.IsNullOrEmpty(Path.GetDirectoryName(decrStagedFileName)))
                        {
                            Directory.CreateDirectory(Path.GetDirectoryName(decrStagedFileName));
                        }

                        using (var contentStream = decryptor.GetContentStream())
                            using (var stagedFile = File.OpenWrite(decrStagedFileName))
                            {
                                contentStream.CopyTo(stagedFile);
                            }
                        File.SetLastWriteTimeUtc(decrStagedFileName, header.LastWriteTimeUtc);


                        if (File.Exists(decrFullFileName))
                        {
                            File.Move(decrFullFileName, decrBackupFileName);
                        }
                        else if (Directory.Exists(decrFullFileName))
                        {
                            if (Directory.GetFiles(decrFullFileName).Length > 0)
                            {
                                throw new IOException($"Unable to process entry, directory is not empty ({decrFullFileName})");
                            }
                            Directory.Move(decrFullFileName, decrBackupFileName);
                        }

                        File.Move(decrStagedFileName, decrFullFileName);

                        if (File.Exists(decrBackupFileName))
                        {
                            File.SetAttributes(decrBackupFileName, FileAttributes.Normal); //incase it was read only
                            File.Delete(decrBackupFileName);
                        }
                        else if (Directory.Exists(decrBackupFileName))
                        {
                            Directory.Delete(decrBackupFileName);
                        }
                    }
                    else if (header.EntryType == FileEntryType.Directory)
                    {
                        if (File.Exists(decrFullFileName))
                        {
                            File.Move(decrFullFileName, decrBackupFileName);
                        }

                        if (Directory.Exists(decrFullFileName))
                        {
                            //If there is a case difference need to delete the directory
                            if (Path.GetFileName(decrFullFileName) != Path.GetFileName(new DirectoryInfo(decrFullFileName).Name))
                            {
                                if (Directory.GetFiles(decrFullFileName).Length > 0)
                                {
                                    throw new IOException($"Unable to process entry, directory is not empty ({decrFullFileName})");
                                }
                                Directory.Move(decrFullFileName, decrBackupFileName);
                            }
                        }


                        Directory.CreateDirectory(decrFullFileName);
                    }
                    else //purge or delete
                    {
                        if (File.Exists(decrFullFileName))
                        {
                            File.Move(decrFullFileName, decrBackupFileName);
                        }
                        else if (Directory.Exists(decrFullFileName))
                        {
                            if (Directory.GetFiles(decrFullFileName).Length > 0)
                            {
                                throw new IOException($"Unable to process entry, directory is not empty ({decrFullFileName})");
                            }
                            Directory.Move(decrFullFileName, decrBackupFileName);
                        }
                    }

                    if (File.Exists(decrBackupFileName))
                    {
                        File.Delete(decrBackupFileName);
                    }
                    else if (Directory.Exists(decrBackupFileName))
                    {
                        Directory.Delete(decrBackupFileName);
                    }
                }
            return(decrPath);
        }
Ejemplo n.º 10
0
 public static string Decrypt(string encrPath, string decrPath, string password, FileDecryptOptions options = null)
 {
     options = (options ?? new FileDecryptOptions()).Clone();
     return(Decrypt(encrPath, decrPath, DerivedBytesProvider.FromPassword(password), options));
 }
Ejemplo n.º 11
0
 public static void Encrypt(string decrFile, string encrFile, string password, FileEncryptOptions options = null)
 {
     Encrypt(decrFile, encrFile, DerivedBytesProvider.FromPassword(password), options);
 }
Ejemplo n.º 12
0
        /// <summary>
        /// Encrypts a file and returns the name of the encrypted file
        /// </summary>
        /// <param name="decrFilePath">The file name and path of the file to be encrypted</param>
        /// <param name="encrFilePath">The file name and path for th encrypted file to be saved</param>
        public static void Encrypt(string decrFilePath, string encrFilePath, DerivedBytesProvider derivedBytesProvider, FileEncryptOptions options = null)
        {
            if (string.IsNullOrWhiteSpace(decrFilePath))
            {
                throw new ArgumentNullException(nameof(decrFilePath));
            }
            if (string.IsNullOrEmpty(encrFilePath))
            {
                throw new ArgumentNullException(nameof(encrFilePath));
            }
            if (derivedBytesProvider == null)
            {
                throw new ArgumentNullException(nameof(derivedBytesProvider));
            }

            options ??= new FileEncryptOptions();

            var encrStagedFileName = encrFilePath + HelixConsts.StagedHxExtention;
            var encrBackupFileName = encrFilePath + HelixConsts.BackupExtention;

            if (!string.IsNullOrEmpty(Path.GetDirectoryName(encrStagedFileName)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(encrStagedFileName));
            }

            FileEntry header = FileEntry.FromFile(decrFilePath, Path.GetDirectoryName(decrFilePath));

            if (!string.IsNullOrWhiteSpace(options.StoredFileName))
            {
                header.FileName = options.StoredFileName;
            }

            using (Stream streamIn = (header.EntryType != FileEntryType.File)
                                       ? (Stream)(new MemoryStream())
                                       : File.Open(decrFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (var streamOut = File.Open(encrStagedFileName, FileMode.CreateNew, FileAccess.Write))
                    using (var encryptor = new HelixFileEncryptor(streamOut, derivedBytesProvider, options.FileVersion))
                    {
                        options.Log?.Invoke("Writing: Header");
                        options.BeforeWriteHeader?.Invoke(header);
                        encryptor.WriteHeader(header);

                        options.Log?.Invoke("Writing: Content");
                        encryptor.WriteContent(streamIn, streamIn.Length);
                    }


            if (File.Exists(encrFilePath))
            {
                if (File.Exists(encrBackupFileName))
                {
                    options.Log?.Invoke("Destaging: Removing incomplete backup");
                    File.Delete(encrBackupFileName);
                }

                options.Log?.Invoke("Destaging: Moving staged to normal");

                if (File.Exists(encrFilePath))
                {
                    File.Move(encrFilePath, encrBackupFileName);
                }
                File.Move(encrStagedFileName, encrFilePath);

                options.Log?.Invoke("Destaging: Removing backup");
                File.Delete(encrBackupFileName);
            }
            else
            {
                options.Log?.Invoke("Destaging: Moving staged to normal");
                File.Move(encrStagedFileName, encrFilePath);
            }
        }
Ejemplo n.º 13
0
 public void Initialize(DerivedBytesProvider derivedBytesProvider, Action <Dictionary <string, byte[]> > afterHeaderRead = null)
 {
     Decryptor.Initialize(derivedBytesProvider, afterHeaderRead);
 }
Ejemplo n.º 14
0
        public static DirectoryPair Open(string decrDirectoryPath, string encrDirectoryPath, DerivedBytesProvider derivedBytesProvider, bool initialize = false, HelixFileVersion fileVersion = null)
        {
            if (derivedBytesProvider == null)
            {
                throw new ArgumentNullException(nameof(derivedBytesProvider));
            }

            DirectoryPair pair = new DirectoryPair(decrDirectoryPath, encrDirectoryPath, derivedBytesProvider, false);

            if (initialize && pair.InitializeFullNeeded())
            {
                pair.InitializeFull(null, fileVersion);
            }
            pair.OpenEncr(null);
            if (initialize && pair.InitializeDecrNeeded())
            {
                pair.InitializeDecr(null);
            }
            pair.OpenDecr(null);

            return(pair);
        }
Ejemplo n.º 15
0
 public HelixFileEncryptor(Stream streamOut, string password)
     : this(streamOut, DerivedBytesProvider.FromPassword(password))
 {
 }
Ejemplo n.º 16
0
        public static SyncSummary Sync(SyncOptions options, ConsoleEx consoleEx = null, HelixFileVersion fileVersion = null)
        {
            var sum = new SyncSummary();

            consoleEx ??= new ConsoleEx();
            consoleEx.Verbosity = options.Verbosity;

            consoleEx.WriteLine("Sync");
            if (options.WhatIf)
            {
                consoleEx.WriteLine("..Options: WhatIf");
            }
            consoleEx.WriteLine($"..DecrDir: {options.DecrDirectory}");
            consoleEx.WriteLine($"..EncrDir: {options.EncrDirectory}");
            //consoleEx.WriteLine($"..Direction: {options.Direction}");
            consoleEx.WriteLine($"..Verbosity: {options.Verbosity}");

            consoleEx.WriteLine();

            if (options.WhatIf)
            {
                consoleEx.WriteLine("** WhatIf Mode - No Changes Made **");
                consoleEx.WriteLine("");
            }


            DerivedBytesProvider derivedBytesProvider = DerivedBytesProvider.FromPassword(options.Password, options.KeyFile);

            using DirectoryPair pair = new DirectoryPair(options.DecrDirectory, options.EncrDirectory, derivedBytesProvider, options.WhatIf);
            pair.PreInitializationCheck();

            if (pair.InitializeFullNeeded())
            {
                if (options.Initialize)
                {
                    //continue, unprompted
                    if (pair.InitializeMergeWarning())
                    {
                        consoleEx.WriteLine("WARNING: Decrypted directory is not empty and will be merged");
                    }
                }
                else
                {
                    consoleEx.WriteLine("Directories require initialization...");

                    if (!consoleEx.PromptBool("Initialized encrypted and decrypted directories now? [y/N] ", false))
                    {
                        consoleEx.WriteErrorLine("Operation cancelled");
                        sum.Error = new InitializationCanceledException();
                        return(sum);
                    }

                    if (pair.InitializeMergeWarning())
                    {
                        if (!consoleEx.PromptBool("Decrypted directory is not empty and will be merged, continue? [y/N] ", false))
                        {
                            consoleEx.WriteErrorLine("Operation cancelled");
                            sum.Error = new InitializationCanceledException();
                            return(sum);
                        }
                    }
                }

                pair.InitializeFull(consoleEx);
            }

            pair.OpenEncr(consoleEx);

            if (pair.InitializeDecrNeeded())
            {
                if (options.Initialize)
                {
                    //continue, unprompted
                    if (pair.InitializeMergeWarning())
                    {
                        consoleEx.WriteLine("WARNING: Decrypted directory is not empty and will be merged");
                    }
                }
                else
                {
                    consoleEx.WriteLine("Decrypted directory require initialization...");

                    if (!consoleEx.PromptBool("Initialized decrypted directories now? [y/N] ", false))
                    {
                        consoleEx.WriteErrorLine("Operation cancelled");
                        sum.Error = new InitializationCanceledException();
                        return(sum);
                    }

                    if (pair.InitializeMergeWarning())
                    {
                        if (!consoleEx.PromptBool("Decrypted directory is not empty and will be merged, continue? [y/N] ", false))
                        {
                            consoleEx.WriteErrorLine("Operation cancelled");
                            sum.Error = new InitializationCanceledException();
                            return(sum);
                        }
                    }
                }

                pair.InitializeDecr(consoleEx);
            }

            pair.OpenDecr(consoleEx);

            pair.Cleanup(consoleEx);

            List <PreSyncDetails> changes = pair.FindChanges(clearCache: false, console: consoleEx);

            if (changes.Count == 0)
            {
                consoleEx.WriteLine("--No Changes--");
            }



            var defaultConflictAction = "";

            consoleEx.WriteLine(VerbosityLevel.Normal, 0, "Performing Sync...");
            foreach (PreSyncDetails change in changes)
            {
                consoleEx.WriteLine(change);

                if (change.SyncMode == PreSyncMode.Conflict)
                {
                    var decrModified = change.DecrInfo == null ? (object)null : change.DecrInfo.LastWriteTimeUtc.ToLocalTime();
                    var decrSize     = change.DecrInfo == null ? (object)null : HelixUtil.FormatBytes5(change.DecrInfo.Length);
                    var encrModified = change.EncrInfo == null ? (object)null : change.EncrInfo.LastWriteTimeUtc.ToLocalTime();
                    var encrSize     = change.EncrHeader == null ? (object)null : HelixUtil.FormatBytes5(change.EncrHeader.Length);


                    string response;
                    if (defaultConflictAction != "")
                    {
                        response = defaultConflictAction;
                    }
                    else
                    {
                        consoleEx.WriteLine($"    Decrypted - Modified: {decrModified}, Size: {decrSize}");
                        consoleEx.WriteLine($"    Encrypted - Modified: {encrModified}, Size: {encrSize}");
                        consoleEx.WriteLine($"");
                        consoleEx.WriteLine($"    D - Decrypted, E - Encrypted, S - Skip, + Always perform this action"); //todo: support newer, support always
                        response = consoleEx.PromptChoice("    Select Option [D,E,S,D+,E+,S+]? ", new string[] { "D", "E", "S", "D+", "E+", "S+" }, "S");
                        if (response.EndsWith("+"))
                        {
                            response = response.Substring(0, 1);
                            defaultConflictAction = response;
                        }
                    }
                    if (response == "D")
                    {
                        change.SyncMode = PreSyncMode.DecryptedSide;
                    }
                    else if (response == "E")
                    {
                        change.SyncMode = PreSyncMode.EncryptedSide;
                    }

                    if (change.SyncMode != PreSyncMode.Conflict)
                    {
                        consoleEx.WriteLine(change);
                    }
                }

                if (change.SyncMode == PreSyncMode.EncryptedSide)
                {
                    //todo: make display operation be the actual operation
                    if (change.DisplayOperation == PreSyncOperation.Add)
                    {
                        sum.EncrAdd++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Remove)
                    {
                        sum.EncrRemove++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Change)
                    {
                        sum.EncrChange++;
                    }
                    else
                    {
                        sum.EncrOther++;
                    }
                }
                else if (change.SyncMode == PreSyncMode.DecryptedSide)
                {
                    if (change.DisplayOperation == PreSyncOperation.Add)
                    {
                        sum.DecrAdd++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Remove)
                    {
                        sum.DecrRemove++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Change)
                    {
                        sum.DecrChange++;
                    }
                    else
                    {
                        sum.DecrOther++;
                    }
                }
                else if (change.SyncMode == PreSyncMode.Conflict)
                {
                    sum.Conflict++;
                }


                var syncResult = pair.TrySync(change, consoleEx);
                //todo: add to error log
                if (syncResult.Exception != null)
                {
                    consoleEx.WriteErrorLine("..." + syncResult.Exception.Message);
                }
            }

            consoleEx.WriteLine("== Summary ==");
            consoleEx.WriteLine(sum);

            //todo: fix unchanged
            consoleEx.WriteLine();


            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "");
            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "==Decr Directory==");
            //foreach (var entry in decrDirectory.FSDirectory.GetEntries(SearchOption.AllDirectories))
            //{
            //    if (entry is FSDirectory dirEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"<dir> {entry.RelativePath}");
            //    else if (entry is FSFile fileEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"{HelixUtil.FormatBytes5(entry.Length)} {entry.RelativePath}");
            //}

            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "");
            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "==Encr Directory==");
            //foreach (var entry in encrDirectory.FSDirectory.GetEntries(SearchOption.AllDirectories))
            //{
            //    if (entry is FSDirectory dirEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"<dir> {entry.RelativePath}");
            //    else if (entry is FSFile fileEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"{HelixUtil.FormatBytes5(entry.Length)} {entry.RelativePath}");
            //}

            return(sum);
        }