diff --git a/src/KeyKeeper/PasswordStore/PassStoreFileAccessor.cs b/src/KeyKeeper/PasswordStore/PassStoreFileAccessor.cs
new file mode 100644
index 0000000..0fa7a40
--- /dev/null
+++ b/src/KeyKeeper/PasswordStore/PassStoreFileAccessor.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using KeyKeeper.PasswordStore.Crypto;
+using KeyKeeper.PasswordStore.Crypto.KeyDerivation;
+using static KeyKeeper.PasswordStore.FileFormatConstants;
+
+namespace KeyKeeper.PasswordStore;
+
+///
+/// Класс, содержащий реализацию доступа к хранилищу паролей
+///
+public class PassStoreFileAccessor : IPassStore
+{
+ private const ushort FORMAT_VERSION_MAJOR = 0;
+ private const ushort FORMAT_VERSION_MINOR = 0;
+ private static readonly byte[] FORMAT_MAGIC = [0xf5, 0x3a, 0xa4, 0xb7, 0xeb, 0xd9, 0xc2, 0x12];
+
+ private string filename;
+ private byte[]? key;
+ private IPassStoreDirectory? root;
+
+ public PassStoreFileAccessor(string filename, bool create, StoreCreationOptions? createOptions)
+ {
+ this.filename = filename;
+ this.key = null;
+ if (!create)
+ {
+ CheckStoreFile();
+ } else if (createOptions != null)
+ {
+ CreateNewAndUnlock(createOptions);
+ } else throw new ArgumentException("createOptions must not be null when creating a new store");
+ }
+
+ public bool Locked
+ {
+ get { return key != null; }
+ }
+
+ public IPassStoreDirectory GetRootDirectory()
+ {
+ return root!;
+ }
+
+ public int GetTotalEntryCount()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Unlock(CompositeKey key)
+ {
+ if (!Locked) return;
+ }
+
+ public void Lock()
+ {
+ if (Locked) return;
+ }
+
+ ///
+ /// Проверяет внешнюю целостность файла хранилища, то есть:
+ /// 1. совпадает сигнатура (magic number)
+ /// 2. совпадает версия формата
+ /// 3. поля криптозаголовка корректны
+ ///
+ void CheckStoreFile()
+ {
+ using FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ BinaryReader f = new(file);
+ byte[] magic = new byte[8];
+ if (f.Read(magic, 0, 8) < 8)
+ {
+ throw PassStoreFileException.UnexpectedEndOfFile;
+ }
+ if (magic != FORMAT_MAGIC)
+ {
+ throw PassStoreFileException.IncorrectMagicNumber;
+ }
+
+ ushort version;
+ try
+ {
+ version = f.ReadUInt16();
+ }
+ catch (EndOfStreamException)
+ {
+ throw PassStoreFileException.UnexpectedEndOfFile;
+ }
+ if (version != FORMAT_VERSION_MAJOR)
+ {
+ throw PassStoreFileException.UnsupportedVersion;
+ }
+
+ try
+ {
+ version = f.ReadUInt16();
+ }
+ catch (EndOfStreamException)
+ {
+ throw PassStoreFileException.UnexpectedEndOfFile;
+ }
+ if (version != FORMAT_VERSION_MINOR)
+ {
+ throw PassStoreFileException.UnsupportedVersion;
+ }
+
+ OuterEncryptionUtil.CheckOuterEncryptionHeader(file);
+ }
+
+ private void CreateNewAndUnlock(StoreCreationOptions options)
+ {
+ using FileStream file = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.None);
+ FileHeader newHeader = FileHeader.Default();
+ newHeader.WriteTo(file);
+
+ options.Key.Salt = newHeader.PreSalt;
+
+ int randomPaddingLen = 4096 - (int)(file.Position % 4096);
+ if (randomPaddingLen > 0)
+ {
+ byte[] randomPadding = new byte[randomPaddingLen];
+ RandomNumberGenerator.Fill(randomPadding);
+ }
+
+ byte[] masterKey = newHeader.KdfInfo.GetKdf().Derive(options.Key, 32);
+ // пока предполагаем что везде используется AES
+ OuterEncryptionWriter cryptoWriter = new(file, masterKey, ((OuterAesHeader)newHeader.OuterCryptoHeader).InitVector);
+
+ BinaryWriter wr = new(cryptoWriter);
+
+ wr.Write(FILE_FIELD_BEGIN);
+ cryptoWriter.Write(BEGIN_MARKER);
+
+ byte[] innerKey = new byte[32];
+ RandomNumberGenerator.Fill(innerKey);
+ byte[] innerIv = new byte[16];
+ RandomNumberGenerator.Fill(innerIv);
+
+ wr.Write(FILE_FIELD_INNER_CRYPTO);
+ cryptoWriter.Write(innerKey);
+ cryptoWriter.Write(innerIv);
+
+ wr.Write(FILE_FIELD_CONFIG);
+
+ wr.Write(FILE_FIELD_STORE);
+ WriteInitialStoreTree(cryptoWriter);
+ cryptoWriter.Flush();
+ cryptoWriter.Dispose();
+ }
+
+ private void WriteInitialStoreTree(OuterEncryptionWriter w)
+ {
+ PassStoreEntry root =
+ new PassStoreEntryGroup(
+ Guid.NewGuid(),
+ DateTime.UtcNow,
+ DateTime.UtcNow,
+ Guid.Empty,
+ "",
+ GROUP_TYPE_ROOT
+ );
+ root.WriteToStream(w);
+ }
+
+ record FileHeader (
+ ushort FileVersionMajor,
+ ushort FileVersionMinor,
+ byte[] PreSalt,
+ OuterEncryptionHeader OuterCryptoHeader,
+ KdfHeader KdfInfo
+ )
+ {
+ public static FileHeader Default()
+ {
+ int saltLen = (MIN_MASTER_SALT_LEN + MAX_MASTER_SALT_LEN) / 2;
+ byte[] preSalt = new byte[saltLen];
+ RandomNumberGenerator.Fill(preSalt);
+
+ byte[] iv = new byte[16];
+ RandomNumberGenerator.Fill(iv);
+
+ byte[] aesKdfSeed = new byte[32];
+ RandomNumberGenerator.Fill(aesKdfSeed);
+
+ return new FileHeader
+ (
+ FORMAT_VERSION_MAJOR,
+ FORMAT_VERSION_MINOR,
+ preSalt,
+ new OuterAesHeader
+ (
+ iv
+ ),
+ new AesKdfHeader
+ (
+ MAX_AESKDF_ROUNDS,
+ aesKdfSeed
+ )
+ );
+ }
+ public int WriteTo(Stream s)
+ {
+ int written = 0;
+
+ s.Write(FORMAT_MAGIC);
+ written += FORMAT_MAGIC.Length;
+
+ BinaryWriter wr = new(s);
+ wr.Write(FORMAT_VERSION_MAJOR);
+ wr.Write(FORMAT_VERSION_MINOR);
+ written += 4;
+
+ wr.Write((byte)PreSalt.Length);
+ s.Write(PreSalt);
+ written += 1 + PreSalt.Length;
+
+ if (OuterCryptoHeader is OuterAesHeader aes)
+ {
+ wr.Write(ENCRYPT_ALGO_AES);
+ s.Write(aes.InitVector);
+ written += 1 + aes.InitVector.Length;
+ }
+ if (KdfInfo is AesKdfHeader aesKdf)
+ {
+ long pos = s.Position;
+ wr.Write(KDF_TYPE_AESKDF);
+ wr.Write7BitEncodedInt(aesKdf.Rounds);
+ wr.Write(aesKdf.Seed);
+ written += (int)(s.Position - pos);
+ }
+ return written;
+ }
+ };
+
+ record OuterEncryptionHeader {}
+
+ record OuterAesHeader(
+ byte[] InitVector
+ ) : OuterEncryptionHeader;
+
+ abstract record KdfHeader
+ {
+ public abstract MasterKeyDerivationFunction GetKdf();
+ }
+
+ record AesKdfHeader(
+ int Rounds,
+ byte[] Seed
+ ) : KdfHeader
+ {
+ public override MasterKeyDerivationFunction GetKdf()
+ {
+ return new AesKdf(Rounds, Seed);
+ }
+ }
+}