diff --git a/src/KeyKeeper/PasswordStore/Crypto/PassStoreContentChunk.cs b/src/KeyKeeper/PasswordStore/Crypto/PassStoreContentChunk.cs
new file mode 100644
index 0000000..bab004a
--- /dev/null
+++ b/src/KeyKeeper/PasswordStore/Crypto/PassStoreContentChunk.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Buffers.Binary;
+using System.IO;
+using System.Security.Cryptography;
+using KeyKeeper.PasswordStore;
+using static KeyKeeper.PasswordStore.FileFormatConstants;
+
+namespace KeyKeeper.Crypto;
+
+///
+/// Класс, представляющий собой обертку над content chunkом, считанным из файла
+/// хранилища. Не расшифровывает содержимое, но проверяет целостность и
+/// подлинность при создании объекта.
+///
+public class PassStoreContentChunk
+{
+ private byte[] chunk;
+ private int chunkLen;
+
+ ///
+ /// Создаёт объект content chunk, считывая массив байт. Бросает исключение
+ /// в случае, если массив не содержит корректный content chunk или не
+ /// совпадает HMAC
+ ///
+ /// Массив байт, содержащий весь content chunk, включая
+ /// длину и HMAC
+ /// Ключ от хранилища. Используется только для
+ /// проверки HMAC и не хранится в объекте
+ /// Порядковый номер content chunk'а, начиная
+ /// с 0
+ public PassStoreContentChunk(byte[] chunk, byte[] key, int chunkOrdinal)
+ {
+ this.chunk = chunk;
+
+ MemoryStream str = new(chunk);
+ BinaryReader rd = new(str);
+
+ try
+ {
+ chunkLen = rd.ReadUInt16();
+ chunkLen = (chunkLen << 8) | rd.ReadByte();
+ }
+ catch (EndOfStreamException)
+ {
+ throw PassStoreFileException.UnexpectedEndOfFile;
+ }
+
+ if (chunk.Length != chunkLen + 3 + HMAC_SIZE)
+ {
+ throw PassStoreFileException.UnexpectedEndOfFile;
+ }
+
+ byte[] storedHmac = new byte[HMAC_SIZE];
+ str.Read(storedHmac, 0, HMAC_SIZE);
+
+ HMACSHA3_512 hmac = new(key);
+ hmac.TransformBlock(chunk, (int)str.Position, Math.Min(chunkLen, chunk.Length - (int)str.Position), null, 0);
+
+ byte[] encodedOrdinal = new byte[sizeof(int)];
+ BinaryPrimitives.WriteInt32LittleEndian(new Span(encodedOrdinal), chunkOrdinal);
+ hmac.TransformBlock(encodedOrdinal, 0, encodedOrdinal.Length, null, 0);
+
+ byte[] actualHmac = hmac.Hash!;
+
+ if (!storedHmac.Equals(actualHmac))
+ {
+ throw PassStoreFileException.ContentHMACMismatch;
+ }
+ }
+
+ ///
+ /// Создаёт объект content chunk, считывая байты из потока. Бросает
+ /// исключение в случае, если массив не содержит корректный content chunk
+ /// или не совпадает HMAC
+ ///
+ /// Массив байт, содержащий весь content chunk, включая
+ /// длину и HMAC
+ /// Ключ от хранилища. Используется только для
+ /// проверки HMAC и не хранится в объекте
+ /// Порядковый номер content chunk'а, начиная
+ /// с 0
+ public static PassStoreContentChunk GetFromStream(Stream s, byte[] key, int chunkOrdinal)
+ {
+ BinaryReader rd = new(s);
+ int chunkLen;
+ try
+ {
+ chunkLen = rd.ReadUInt16();
+ chunkLen = (chunkLen << 8) | rd.ReadByte();
+ }
+ catch (EndOfStreamException)
+ {
+ throw PassStoreFileException.UnexpectedEndOfFile;
+ }
+ byte[] chunk = new byte[3 + HMAC_SIZE + chunkLen];
+ if (s.Read(chunk) < chunk.Length)
+ {
+ throw PassStoreFileException.UnexpectedEndOfFile;
+ }
+ return new PassStoreContentChunk(chunk, key, chunkOrdinal);
+ }
+
+ public ReadOnlySpan GetContent()
+ {
+ return new ReadOnlySpan(chunk, 3 + HMAC_SIZE, chunkLen);
+ }
+}