private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey) { byte[] pbHeaderHmac; byte[] pbBlockKey = HmacBlockStream.GetHmacKey64( pbKey, ulong.MaxValue); using (HMACSHA256 h = new HMACSHA256(pbBlockKey)) { pbHeaderHmac = h.ComputeHash(pbHeader); } MemUtil.ZeroByteArray(pbBlockKey); return(pbHeaderHmac); }
/// <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; 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"); } #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(); } finally { if (pbCipherKey != null) { MemUtil.ZeroByteArray(pbCipherKey); } if (pbHmacKey64 != null) { MemUtil.ZeroByteArray(pbHmacKey64); } CommonCleanUpWrite(lStreams, sHashing); } }
// 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; 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"); } #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(); } finally { if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); CommonCleanUpWrite(lStreams, sHashing); } }
/// <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; m_pbsBinaries.Clear(); 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 // FileStream fsOut = new FileStream("Raw.xml", FileMode.Create, // FileAccess.Write, FileShare.None); // try // { // while(true) // { // int b = sXml.ReadByte(); // if(b == -1) break; // fsOut.WriteByte((byte)b); // } // } // catch(Exception) { } // fsOut.Close(); #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 }