Beispiel #1
0
 public KeyCipher(IntPtr keePassWindowHandle)
 {
     _randomSeedBits = 256;
     _encryptionIV   = new byte[16];
     _cipherEngine   = CipherPool.GlobalPool.GetCipher(StandardAesEngine.AesUuid);
     _cryptProvider  = GetAuthProvider(keePassWindowHandle);
 }
Beispiel #2
0
		private ICipherEngine GetCipher(out int cbEncKey, out int cbEncIV)
		{
			PwUuid pu = m_pwDatabase.DataCipherUuid;
			ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu);
			if(iCipher == null) // CryptographicExceptions are translated to "file corrupted"
				throw new Exception(KLRes.FileUnknownCipher +
					MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq +
					MessageService.NewParagraph + "UUID: " + pu.ToHexString() + ".");

			ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2);
			if(iCipher2 != null)
			{
				cbEncKey = iCipher2.KeyLength;
				if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length");

				cbEncIV = iCipher2.IVLength;
				if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length");
			}
			else
			{
				cbEncKey = 32;
				cbEncIV = 16;
			}

			return iCipher;
		}
 public KeyCipher(string message, IntPtr windowHandle)
 {
     _randomSeedBits = 256;
     _encryptionIV   = CryptoRandom.Instance.GetRandomBytes(16);
     _cipherEngine   = CipherPool.GlobalPool.GetCipher(StandardAesEngine.AesUuid);
     _cryptProvider  = AuthProviderFactory.GetInstance(message, windowHandle);
 }
Beispiel #4
0
		/// <summary>
		/// Add a cipher engine to the pool.
		/// </summary>
		/// <param name="csEngine">Cipher engine to add. Must not be <c>null</c>.</param>
		public void AddCipher(ICipherEngine csEngine)
		{
			Debug.Assert(csEngine != null);
			if(csEngine == null) throw new ArgumentNullException("csEngine");

			// Return if a cipher with that ID is registered already.
			for(int i = 0; i < m_vCiphers.Count; ++i)
				if(m_vCiphers[i].CipherUuid.Equals(csEngine.CipherUuid))
					return;

			m_vCiphers.Add(csEngine);
		}
Beispiel #5
0
		private Stream EncryptStream(Stream s, ICipherEngine iCipher,
			byte[] pbKey, int cbIV, bool bEncrypt)
		{
			byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray);
			if(pbIV.Length != cbIV)
			{
				Debug.Assert(false);
				throw new Exception(KLRes.FileCorrupted);
			}

			if(bEncrypt)
				return iCipher.EncryptStream(s, pbKey, pbIV);
			return iCipher.DecryptStream(s, pbKey, pbIV);
		}
Beispiel #6
0
        /// <summary>
        /// Add a cipher engine to the pool.
        /// </summary>
        /// <param name="c">Cipher engine to add. Must not be <c>null</c>.</param>
        public void AddCipher(ICipherEngine c)
        {
            if (c == null)
            {
                Debug.Assert(false); throw new ArgumentNullException("c");
            }

            // Return if a cipher with that ID is registered already
            foreach (ICipherEngine cEx in m_lCiphers)
            {
                if (cEx.CipherUuid.Equals(c.CipherUuid))
                {
                    return;
                }
            }

            m_lCiphers.Add(c);
        }
Beispiel #7
0
        private Stream AttachStreamEncryptor(Stream s)
        {
            MemoryStream ms = new MemoryStream();

            Debug.Assert(m_pbMasterSeed != null);
            Debug.Assert(m_pbMasterSeed.Length == 32);
            ms.Write(m_pbMasterSeed, 0, 32);

            Debug.Assert(m_pwDatabase != null);
            Debug.Assert(m_pwDatabase.MasterKey != null);
            ProtectedBinary pbinKey = m_pwDatabase.MasterKey.GenerateKey32(
                m_pbTransformSeed, m_pwDatabase.KeyEncryptionRounds);

            Debug.Assert(pbinKey != null);
            if (pbinKey == null)
            {
                throw new SecurityException(KLRes.InvalidCompositeKey);
            }
            byte[] pKey32 = pbinKey.ReadData();
            if ((pKey32 == null) || (pKey32.Length != 32))
            {
                throw new SecurityException(KLRes.InvalidCompositeKey);
            }
            ms.Write(pKey32, 0, 32);

#if KeePass2PCL
            var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
            var aesKey = sha256.HashData(ms.ToArray());
#else
            SHA256Managed sha256 = new SHA256Managed();
            byte[]        aesKey = sha256.ComputeHash(ms.ToArray());
#endif

            ms.Dispose();
            Array.Clear(pKey32, 0, 32);

            Debug.Assert(CipherPool.GlobalPool != null);
            ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid);
            if (iEngine == null)
            {
                throw new SecurityException(KLRes.FileUnknownCipher);
            }
            return(iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV));
        }
Beispiel #8
0
        /// <summary>
        /// Add a cipher engine to the pool.
        /// </summary>
        /// <param name="csEngine">Cipher engine to add. Must not be <c>null</c>.</param>
        public void AddCipher(ICipherEngine csEngine)
        {
            Debug.Assert(csEngine != null);
            if (csEngine == null)
            {
                throw new ArgumentNullException("csEngine");
            }

            // Return if a cipher with that ID is registered already.
            for (int i = 0; i < m_vCiphers.Count; ++i)
            {
                if (m_vCiphers[i].CipherUuid.Equals(csEngine.CipherUuid))
                {
                    return;
                }
            }

            m_vCiphers.Add(csEngine);
        }
Beispiel #9
0
        /// <summary>
        /// The attach stream decryptor.
        /// </summary>
        /// <param name="s">
        /// The s.
        /// </param>
        /// <returns>
        /// The <see cref="Stream"/>.
        /// </returns>
        /// <exception cref="FormatException">
        /// </exception>
        /// <exception cref="SecurityException">
        /// </exception>
        private Stream AttachStreamDecryptor(Stream s, CancellationToken token)
        {
            MemoryStream ms = new MemoryStream();

            Debug.Assert(this.m_pbMasterSeed.Length == 32);
            if (this.m_pbMasterSeed.Length != 32)
            {
                throw new FormatException(KLRes.MasterSeedLengthInvalid);
            }

            ms.Write(this.m_pbMasterSeed, 0, 32);

            byte[] pKey32 = this.m_pwDatabase.MasterKey.GenerateKey32(this.m_pbTransformSeed, this.m_pwDatabase.KeyEncryptionRounds, token).ReadData();
            if ((pKey32 == null) || (pKey32.Length != 32))
            {
                throw new SecurityException(KLRes.InvalidCompositeKey);
            }

            ms.Write(pKey32, 0, 32);

            var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);

            byte[] aesKey = sha256.HashData(ms.ToArray().AsBuffer()).ToArray();

            ms.Dispose();
            Array.Clear(pKey32, 0, 32);

            if ((aesKey == null) || (aesKey.Length != 32))
            {
                throw new SecurityException(KLRes.FinalKeyCreationFailed);
            }

            ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(this.m_pwDatabase.DataCipherUuid);

            if (iEngine == null)
            {
                throw new SecurityException(KLRes.FileUnknownCipher);
            }

            return(iEngine.DecryptStream(s, aesKey, this.m_pbEncryptionIV));
        }
Beispiel #10
0
        private Stream AttachStreamDecryptor(Stream s)
        {
            MemoryStream ms = new MemoryStream();

            Debug.Assert(m_pbMasterSeed.Length == 32);
            if (m_pbMasterSeed.Length != 32)
            {
                throw new FormatException(KLRes.MasterSeedLengthInvalid);
            }
            ms.Write(m_pbMasterSeed, 0, 32);

            byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed,
                                                                 m_pwDatabase.KeyEncryptionRounds).ReadData();
            if ((pKey32 == null) || (pKey32.Length != 32))
            {
                throw new SecurityException(KLRes.InvalidCompositeKey);
            }
            ms.Write(pKey32, 0, 32);

            SHA256Managed sha256 = new SHA256Managed();

            byte[] aesKey = sha256.ComputeHash(ms.ToArray());

            ms.Close();
            Array.Clear(pKey32, 0, 32);

            if ((aesKey == null) || (aesKey.Length != 32))
            {
                throw new SecurityException(KLRes.FinalKeyCreationFailed);
            }

            ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid);

            if (iEngine == null)
            {
                throw new SecurityException(KLRes.FileUnknownCipher);
            }
            return(iEngine.DecryptStream(s, aesKey, m_pbEncryptionIV));
        }
        private Stream AttachStreamEncryptor(Stream s)
        {
            MemoryStream ms = new MemoryStream();

            Debug.Assert(m_pbMasterSeed != null);
            Debug.Assert(m_pbMasterSeed.Length == 32);
            ms.Write(m_pbMasterSeed, 0, 32);

            Debug.Assert(m_pwDatabase != null);
            Debug.Assert(m_pwDatabase.MasterKey != null);
            ProtectedBinary pbinKey = m_pwDatabase.MasterKey.GenerateKey32(
                m_pbTransformSeed, m_pwDatabase.KeyEncryptionRounds);

            Debug.Assert(pbinKey != null);
            if (pbinKey == null)
            {
                throw new SecurityException(KLRes.InvalidCompositeKey);
            }
            byte[] pKey32 = pbinKey.ReadData();
            if ((pKey32 == null) || (pKey32.Length != 32))
            {
                throw new SecurityException(KLRes.InvalidCompositeKey);
            }
            ms.Write(pKey32, 0, 32);

            byte[] aesKey = Crypto.SHA256.ComputeHash(ms.ToArray());

            ms.Close();
            Array.Clear(pKey32, 0, 32);

            Debug.Assert(CipherPool.GlobalPool != null);
            ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid);

            if (iEngine == null)
            {
                throw new SecurityException(KLRes.FileUnknownCipher);
            }
            return(iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV));
        }
        // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt,
        //	IStatusLogger slLogger)
        // {
        //	bool bMadeUnhidden = UrlUtil.UnhideFile(strFile);
        //
        //	IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile);
        //	this.Save(IOConnection.OpenWrite(ioc), pgDataSource, format, slLogger);
        //
        //	if(bMadeUnhidden) UrlUtil.HideFile(strFile, true); // Hide again
        // }

        /// <summary>
        /// Save the contents of the current <c>PwDatabase</c> to a KDBX file.
        /// </summary>
        /// <param name="sSaveTo">Stream to write the KDBX file into.</param>
        /// <param name="pgDataSource">Group containing all groups and
        /// entries to write. If <c>null</c>, the complete database will
        /// be written.</param>
        /// <param name="fmt">Format of the file to create.</param>
        /// <param name="slLogger">Logger that recieves status information.</param>
        public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt,
                         IStatusLogger slLogger)
        {
            Debug.Assert(sSaveTo != null);
            if (sSaveTo == null)
            {
                throw new ArgumentNullException("sSaveTo");
            }

            if (m_bUsedOnce)
            {
                throw new InvalidOperationException("Do not reuse KdbxFile objects!");
            }
            m_bUsedOnce = true;

            m_format    = fmt;
            m_slLogger  = slLogger;
            m_xmlWriter = null;

            PwGroup      pgRoot   = (pgDataSource ?? m_pwDatabase.RootGroup);
            UTF8Encoding encNoBom = StrUtil.Utf8;
            CryptoRandom cr       = CryptoRandom.Instance;

            byte[] pbCipherKey = null;
            byte[] pbHmacKey64 = null;

            m_pbsBinaries.Clear();
            m_pbsBinaries.AddFrom(pgRoot);

            List <Stream> lStreams = new List <Stream>();

            lStreams.Add(sSaveTo);

            HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null);

            lStreams.Add(sHashing);

            try
            {
                m_uFileVersion = GetMinKdbxVersion();

                int           cbEncKey, cbEncIV;
                ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);

                m_pbMasterSeed   = cr.GetRandomBytes(32);
                m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV);

                // m_pbTransformSeed = cr.GetRandomBytes(32);
                PwUuid    puKdf = m_pwDatabase.KdfParameters.KdfUuid;
                KdfEngine kdf   = KdfPool.Get(puKdf);
                if (kdf == null)
                {
                    throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph +
                                        // KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph +
                                        "UUID: " + puKdf.ToHexString() + ".");
                }
                kdf.Randomize(m_pwDatabase.KdfParameters);

                if (m_format == KdbxFormat.Default)
                {
                    if (m_uFileVersion < FileVersion32_4)
                    {
                        m_craInnerRandomStream   = CrsAlgorithm.Salsa20;
                        m_pbInnerRandomStreamKey = cr.GetRandomBytes(32);
                    }
                    else                     // KDBX >= 4
                    {
                        m_craInnerRandomStream   = CrsAlgorithm.ChaCha20;
                        m_pbInnerRandomStreamKey = cr.GetRandomBytes(64);
                    }

                    m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
                                                            m_pbInnerRandomStreamKey);
                }

                if (m_uFileVersion < FileVersion32_4)
                {
                    m_pbStreamStartBytes = cr.GetRandomBytes(32);
                }

                Stream sXml;
                if (m_format == KdbxFormat.Default)
                {
                    byte[] pbHeader = GenerateHeader();
                    m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);

                    MemUtil.Write(sHashing, pbHeader);
                    sHashing.Flush();

                    ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);

                    Stream sPlain;
                    if (m_uFileVersion < FileVersion32_4)
                    {
                        Stream sEncrypted = EncryptStream(sHashing, iCipher,
                                                          pbCipherKey, cbEncIV, true);
                        if ((sEncrypted == null) || (sEncrypted == sHashing))
                        {
                            throw new SecurityException(KLRes.CryptoStreamFailed);
                        }
                        lStreams.Add(sEncrypted);

                        MemUtil.Write(sEncrypted, m_pbStreamStartBytes);

                        sPlain = new HashedBlockStream(sEncrypted, true);
                    }
                    else                     // KDBX >= 4
                    {
                        // For integrity checking (without knowing the master key)
                        MemUtil.Write(sHashing, m_pbHashOfHeader);

                        byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
                        MemUtil.Write(sHashing, pbHeaderHmac);

                        Stream sBlocks = new HmacBlockStream(sHashing, true,
                                                             true, pbHmacKey64);
                        lStreams.Add(sBlocks);

                        sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
                                               cbEncIV, true);
                        if ((sPlain == null) || (sPlain == sBlocks))
                        {
                            throw new SecurityException(KLRes.CryptoStreamFailed);
                        }
                    }
                    lStreams.Add(sPlain);

                    if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
                    {
                        sXml = new GZipStream(sPlain, CompressionMode.Compress);
                        lStreams.Add(sXml);
                    }
                    else
                    {
                        sXml = sPlain;
                    }

                    if (m_uFileVersion >= FileVersion32_4)
                    {
                        WriteInnerHeader(sXml);                         // Binary header before XML
                    }
                }
                else if (m_format == KdbxFormat.PlainXml)
                {
                    sXml = sHashing;
                }
                else
                {
                    Debug.Assert(false);
                    throw new ArgumentOutOfRangeException("fmt");
                }

                m_xmlWriter = XmlUtilEx.CreateXmlWriter(sXml);

                WriteDocument(pgRoot);

                m_xmlWriter.Flush();
            }
            finally
            {
                CommonCleanUpWrite(lStreams, sHashing);

                if (pbCipherKey != null)
                {
                    MemUtil.ZeroByteArray(pbCipherKey);
                }
                if (pbHmacKey64 != null)
                {
                    MemUtil.ZeroByteArray(pbHmacKey64);
                }
            }
        }
Beispiel #13
0
        /// <summary>
        /// Load a KDBX file from a stream.
        /// </summary>
        /// <param name="sSource">Stream to read the data from. Must contain
        /// a KDBX stream.</param>
        /// <param name="fmt">Format.</param>
        /// <param name="slLogger">Status logger (optional).</param>
        public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger)
        {
            Debug.Assert(sSource != null);
            if (sSource == null)
            {
                throw new ArgumentNullException("sSource");
            }

            if (m_bUsedOnce)
            {
                throw new InvalidOperationException("Do not reuse KdbxFile objects!");
            }
            m_bUsedOnce = true;

#if KDBX_BENCHMARK
            Stopwatch swTime = Stopwatch.StartNew();
#endif

            m_format   = fmt;
            m_slLogger = slLogger;

            // Other applications might not perform a deduplication
            m_pbsBinaries = new ProtectedBinarySet(false);

            UTF8Encoding encNoBom    = StrUtil.Utf8;
            byte[]       pbCipherKey = null;
            byte[]       pbHmacKey64 = null;

            List <Stream> lStreams = new List <Stream>();
            lStreams.Add(sSource);

            HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null);
            lStreams.Add(sHashing);

            try
            {
                Stream sXml;
                if (fmt == KdbxFormat.Default)
                {
                    BinaryReaderEx br = new BinaryReaderEx(sHashing,
                                                           encNoBom, KLRes.FileCorrupted);
                    byte[] pbHeader = LoadHeader(br);
                    m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);

                    int           cbEncKey, cbEncIV;
                    ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);

                    ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);

                    string strIncomplete = KLRes.FileHeaderCorrupted + " " +
                                           KLRes.FileIncomplete;

                    Stream sPlain;
                    if (m_uFileVersion < FileVersion32_4)
                    {
                        Stream sDecrypted = EncryptStream(sHashing, iCipher,
                                                          pbCipherKey, cbEncIV, false);
                        if ((sDecrypted == null) || (sDecrypted == sHashing))
                        {
                            throw new SecurityException(KLRes.CryptoStreamFailed);
                        }
                        lStreams.Add(sDecrypted);

                        BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
                                                                        encNoBom, strIncomplete);
                        byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);

                        if ((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
                        {
                            throw new EndOfStreamException(strIncomplete);
                        }
                        if (!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
                        {
                            throw new InvalidCompositeKeyException();
                        }

                        sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
                    }
                    else                     // KDBX >= 4
                    {
                        byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
                        if ((pbStoredHash == null) || (pbStoredHash.Length != 32))
                        {
                            throw new EndOfStreamException(strIncomplete);
                        }
                        if (!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
                        {
                            throw new InvalidDataException(KLRes.FileHeaderCorrupted);
                        }

                        byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
                        byte[] pbStoredHmac = MemUtil.Read(sHashing, 32);
                        if ((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
                        {
                            throw new EndOfStreamException(strIncomplete);
                        }
                        if (!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac))
                        {
                            throw new InvalidCompositeKeyException();
                        }

                        HmacBlockStream sBlocks = new HmacBlockStream(sHashing,
                                                                      false, !m_bRepairMode, pbHmacKey64);
                        lStreams.Add(sBlocks);

                        sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
                                               cbEncIV, false);
                        if ((sPlain == null) || (sPlain == sBlocks))
                        {
                            throw new SecurityException(KLRes.CryptoStreamFailed);
                        }
                    }
                    lStreams.Add(sPlain);

                    if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
                    {
                        sXml = new GZipStream(sPlain, CompressionMode.Decompress);
                        lStreams.Add(sXml);
                    }
                    else
                    {
                        sXml = sPlain;
                    }

                    if (m_uFileVersion >= FileVersion32_4)
                    {
                        LoadInnerHeader(sXml);                         // Binary header before XML
                    }
                }
                else if (fmt == KdbxFormat.PlainXml)
                {
                    sXml = sHashing;
                }
                else
                {
                    Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt");
                }

                if (fmt == KdbxFormat.Default)
                {
                    if (m_pbInnerRandomStreamKey == null)
                    {
                        Debug.Assert(false);
                        throw new SecurityException("Invalid inner random stream key!");
                    }

                    m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
                                                            m_pbInnerRandomStreamKey);
                }

#if KeePassDebug_WriteXml
#warning XML output is enabled!

                /* using(FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
                 *      FileAccess.Write, FileShare.None))
                 * {
                 *      while(true)
                 *      {
                 *              int b = sXml.ReadByte();
                 *              if(b == -1) throw new EndOfStreamException();
                 *              fsOut.WriteByte((byte)b);
                 *      }
                 * } */
#endif

                ReadXmlStreamed(sXml, sHashing);
                // ReadXmlDom(sXml);
            }
            catch (CryptographicException)            // Thrown on invalid padding
            {
                throw new CryptographicException(KLRes.FileCorrupted);
            }
            finally
            {
                if (pbCipherKey != null)
                {
                    MemUtil.ZeroByteArray(pbCipherKey);
                }
                if (pbHmacKey64 != null)
                {
                    MemUtil.ZeroByteArray(pbHmacKey64);
                }

                CommonCleanUpRead(lStreams, sHashing);
            }

#if KDBX_BENCHMARK
            swTime.Stop();
            MessageService.ShowInfo("Loading KDBX took " +
                                    swTime.ElapsedMilliseconds.ToString() + " ms.");
#endif
        }
        // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt,
        //	IStatusLogger slLogger)
        // {
        //	bool bMadeUnhidden = UrlUtil.UnhideFile(strFile);
        //
        //	IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile);
        //	this.Save(IOConnection.OpenWrite(ioc), pgDataSource, format, slLogger);
        //
        //	if(bMadeUnhidden) UrlUtil.HideFile(strFile, true); // Hide again
        // }

        /// <summary>
        /// Save the contents of the current <c>PwDatabase</c> to a KDBX file.
        /// </summary>
        /// <param name="sSaveTo">Stream to write the KDBX file into.</param>
        /// <param name="pgDataSource">Group containing all groups and
        /// entries to write. If <c>null</c>, the complete database will
        /// be written.</param>
        /// <param name="fmt">Format of the file to create.</param>
        /// <param name="slLogger">Logger that recieves status information.</param>
        public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt,
                         IStatusLogger slLogger)
        {
            Debug.Assert(sSaveTo != null);
            if (sSaveTo == null)
            {
                throw new ArgumentNullException("sSaveTo");
            }

            if (m_bUsedOnce)
            {
                throw new InvalidOperationException("Do not reuse KdbxFile objects!");
            }
            m_bUsedOnce = true;

            m_format    = fmt;
            m_slLogger  = slLogger;
            m_xmlWriter = null;

            PwGroup      pgRoot   = (pgDataSource ?? m_pwDatabase.RootGroup);
            UTF8Encoding encNoBom = StrUtil.Utf8;
            CryptoRandom cr       = CryptoRandom.Instance;

            byte[] pbCipherKey = null;
            byte[] pbHmacKey64 = null;

            m_pbsBinaries = new ProtectedBinarySet(true);
            m_pbsBinaries.AddFrom(pgRoot);

            List <Stream> lStreams = new List <Stream>();

            lStreams.Add(sSaveTo);

            HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null);

            lStreams.Add(sHashing);

            try
            {
                // Fix history entries (should not be necessary; just for safety,
                // as e.g. XPath searches depend on correct history entry UUIDs)
                if (m_pwDatabase.MaintainBackups())
                {
                    Debug.Assert(false);
                }

                m_uFileVersion = GetMinKdbxVersion();

                int           cbEncKey, cbEncIV;
                ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);

                m_pbMasterSeed   = cr.GetRandomBytes(32);
                m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV);

                // m_pbTransformSeed = cr.GetRandomBytes(32);
                PwUuid    puKdf = m_pwDatabase.KdfParameters.KdfUuid;
                KdfEngine kdf   = KdfPool.Get(puKdf);
                if (kdf == null)
                {
                    throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph +
                                        // KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph +
                                        "UUID: " + puKdf.ToHexString() + ".");
                }
                kdf.Randomize(m_pwDatabase.KdfParameters);

                if (m_format == KdbxFormat.Default)
                {
                    if (m_uFileVersion < FileVersion32_4)
                    {
                        m_craInnerRandomStream   = CrsAlgorithm.Salsa20;
                        m_pbInnerRandomStreamKey = cr.GetRandomBytes(32);
                    }
                    else                     // KDBX >= 4
                    {
                        m_craInnerRandomStream   = CrsAlgorithm.ChaCha20;
                        m_pbInnerRandomStreamKey = cr.GetRandomBytes(64);
                    }

                    m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
                                                            m_pbInnerRandomStreamKey);
                }

                if (m_uFileVersion < FileVersion32_4)
                {
                    m_pbStreamStartBytes = cr.GetRandomBytes(32);
                }

                Stream sXml;
                if (m_format == KdbxFormat.Default || m_format == KdbxFormat.ProtocolBuffers)
                {
                    byte[] pbHeader = GenerateHeader();
                    m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);

                    MemUtil.Write(sHashing, pbHeader);
                    sHashing.Flush();

                    ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);

                    Stream sPlain;
                    if (m_uFileVersion < FileVersion32_4)
                    {
                        Stream sEncrypted = EncryptStream(sHashing, iCipher,
                                                          pbCipherKey, cbEncIV, true);
                        if ((sEncrypted == null) || (sEncrypted == sHashing))
                        {
                            throw new SecurityException(KLRes.CryptoStreamFailed);
                        }
                        lStreams.Add(sEncrypted);

                        MemUtil.Write(sEncrypted, m_pbStreamStartBytes);

                        sPlain = new HashedBlockStream(sEncrypted, true);
                    }
                    else                     // KDBX >= 4
                    {
                        // For integrity checking (without knowing the master key)
                        MemUtil.Write(sHashing, m_pbHashOfHeader);

                        byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
                        MemUtil.Write(sHashing, pbHeaderHmac);

                        Stream sBlocks = new HmacBlockStream(sHashing, true,
                                                             true, pbHmacKey64);
                        lStreams.Add(sBlocks);

                        sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
                                               cbEncIV, true);
                        if ((sPlain == null) || (sPlain == sBlocks))
                        {
                            throw new SecurityException(KLRes.CryptoStreamFailed);
                        }
                    }
                    lStreams.Add(sPlain);

                    if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
                    {
                        sXml = new GZipStream(sPlain, CompressionMode.Compress);
                        lStreams.Add(sXml);
                    }
                    else
                    {
                        sXml = sPlain;
                    }

                    if (m_uFileVersion >= FileVersion32_4)
                    {
                        WriteInnerHeader(sXml);                         // Binary header before XML
                    }
                }
                else if (m_format == KdbxFormat.PlainXml)
                {
                    sXml = sHashing;
                }
                else
                {
                    Debug.Assert(false);
                    throw new ArgumentOutOfRangeException("fmt");
                }

                var stopWatch = Stopwatch.StartNew();

                if (m_format == KdbxFormat.ProtocolBuffers)
                {
                    KdbpFile.WriteDocument(m_pwDatabase, sXml, m_pbInnerRandomStreamKey, m_pbHashOfHeader);
                }
                else
                {
#if KeePassUAP
                    XmlWriterSettings xws = new XmlWriterSettings();
                    xws.Encoding            = encNoBom;
                    xws.Indent              = true;
                    xws.IndentChars         = "\t";
                    xws.NewLineOnAttributes = false;

                    XmlWriter xw = XmlWriter.Create(sXml, xws);
#else
                    XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom);

                    xw.Formatting  = Formatting.Indented;
                    xw.IndentChar  = '\t';
                    xw.Indentation = 1;
#endif
                    m_xmlWriter = xw;

                    WriteDocument(pgRoot);

                    m_xmlWriter.Flush();
                    m_xmlWriter.Close();
                }
                Kp2aLog.Log(String.Format("{1}: {0}ms", stopWatch.ElapsedMilliseconds, m_format == KdbxFormat.ProtocolBuffers ? "KdbpFile.WriteDocument" : "Xml WriteDocument"));
            }
            finally
            {
                if (pbCipherKey != null)
                {
                    MemUtil.ZeroByteArray(pbCipherKey);
                }
                if (pbHmacKey64 != null)
                {
                    MemUtil.ZeroByteArray(pbHmacKey64);
                }

                CommonCleanUpWrite(lStreams, sHashing);
            }
        }