mirror of
https://github.com/KeyKeeperApp/KeyKeeper.git
synced 2026-04-17 18:16:28 +03:00
implement OuterEncryptionWriter
This commit is contained in:
134
src/KeyKeeper/PasswordStore/Crypto/OuterEncryptionWriter.cs
Normal file
134
src/KeyKeeper/PasswordStore/Crypto/OuterEncryptionWriter.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace KeyKeeper.PasswordStore.Crypto;
|
||||
|
||||
public class OuterEncryptionWriter : Stream
|
||||
{
|
||||
public override bool CanRead => false;
|
||||
public override bool CanWrite => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanTimeout => false;
|
||||
public override int ReadTimeout
|
||||
{
|
||||
get { throw new InvalidOperationException(); }
|
||||
set { throw new InvalidOperationException(); }
|
||||
}
|
||||
public override int WriteTimeout
|
||||
{
|
||||
get { throw new InvalidOperationException(); }
|
||||
set { throw new InvalidOperationException(); }
|
||||
}
|
||||
public override long Position
|
||||
{
|
||||
get => position;
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
private FileStream file;
|
||||
private byte[] key;
|
||||
private Aes aes;
|
||||
private ICryptoTransform encryptor;
|
||||
|
||||
private byte[] currentChunk;
|
||||
private int currentChunkOrdinal = 0;
|
||||
private int chunkPosition = 0;
|
||||
private long position = 0;
|
||||
|
||||
public OuterEncryptionWriter(FileStream file, byte[] key, byte[] iv)
|
||||
{
|
||||
if (!file.CanWrite)
|
||||
throw new ArgumentException("file must be writeable");
|
||||
this.file = file;
|
||||
this.key = key;
|
||||
|
||||
currentChunk = new byte[524288];
|
||||
|
||||
aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CFB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
encryptor = aes.CreateEncryptor();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count,
|
||||
AsyncCallback? callback, object? state)
|
||||
=> throw new NotSupportedException();
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count,
|
||||
AsyncCallback? callback, object? state)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
=> throw new NotSupportedException();
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override int Read(Span<byte> buffer)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> Write(new ReadOnlySpan<byte>(buffer, offset, count));
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
Console.WriteLine("OE write " + buffer.Length);
|
||||
int written = 0;
|
||||
while (written < buffer.Length)
|
||||
{
|
||||
if (chunkPosition == currentChunk.Length)
|
||||
EncryptAndStoreCurrentFullChunk();
|
||||
int n = Math.Min(buffer.Length, currentChunk.Length - chunkPosition);
|
||||
Console.WriteLine("OEW: copy " + n + " bytes buffer+" + written + " -> chunk+" + chunkPosition);
|
||||
buffer.Slice(written, n).CopyTo(new Span<byte>(currentChunk, chunkPosition, n));
|
||||
written += n;
|
||||
chunkPosition += n;
|
||||
position += n;
|
||||
Console.WriteLine(string.Format("written={} pos={}", written, chunkPosition));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Шифрует оставшиеся данные и добавляет к файлу чанк с пометкой о том
|
||||
/// что он последний. Вызов после завершения записи полезных данных
|
||||
/// обязателен, в противном случае потеря данных неизбежна.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
byte[] encryptedData = encryptor.TransformFinalBlock(currentChunk, 0, chunkPosition);
|
||||
PassStoreContentChunk chunk = PassStoreContentChunk.FromEncryptedContent(encryptedData, key, currentChunkOrdinal, true);
|
||||
file.Write(chunk.Chunk);
|
||||
EraseCurrentChunk();
|
||||
chunkPosition = 0;
|
||||
}
|
||||
|
||||
private void EncryptAndStoreCurrentFullChunk()
|
||||
{
|
||||
byte[] encryptedData = new byte[currentChunk.Length];
|
||||
encryptor.TransformBlock(currentChunk, 0, currentChunk.Length, encryptedData, 0);
|
||||
PassStoreContentChunk chunk = PassStoreContentChunk.FromEncryptedContent(encryptedData, key, currentChunkOrdinal, false);
|
||||
currentChunkOrdinal += 1;
|
||||
file.Write(chunk.Chunk);
|
||||
EraseCurrentChunk();
|
||||
chunkPosition = 0;
|
||||
}
|
||||
|
||||
private void EraseCurrentChunk()
|
||||
{
|
||||
if (currentChunk == null) return;
|
||||
Array.Fill<byte>(currentChunk, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user