fix encryption

This commit is contained in:
Alex Bezdieniezhnykh
2024-11-09 07:25:47 +02:00
parent 121052a3ef
commit ca6175da7f
3 changed files with 75 additions and 13 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ public class ResourcesService(IOptions<ResourcesConfig> resourcesConfig) : IReso
{ {
var fileStream = new FileStream(GetResourcePath(request.ResourceEnum), FileMode.Open, FileAccess.Read); var fileStream = new FileStream(GetResourcePath(request.ResourceEnum), FileMode.Open, FileAccess.Read);
var key = Security.MakeEncryptionKey(request.Username, request.Password); var key = Security.MakeEncryptionKey(request.Username, request.Password);
await fileStream.Encrypt(outputStream, key, cancellationToken); await fileStream.EncryptTo(outputStream, key, cancellationToken);
} }
public async Task SaveResource(UploadResourceRequest request, CancellationToken cancellationToken = default) public async Task SaveResource(UploadResourceRequest request, CancellationToken cancellationToken = default)
+8 -8
View File
@@ -5,7 +5,7 @@ namespace Azaion.Services;
public static class Security public static class Security
{ {
private const int BUFFER_SIZE = 81920; // 80 KB buffer size private const int BUFFER_SIZE = 524288; // 512 KB buffer size
public static string ToHash(this string str) => public static string ToHash(this string str) =>
Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str))); Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str)));
@@ -13,9 +13,9 @@ public static class Security
public static string MakeEncryptionKey(string username, string password) => public static string MakeEncryptionKey(string username, string password) =>
$"{username}-{password}---#%@AzaionKey@%#---"; $"{username}-{password}---#%@AzaionKey@%#---";
public static async Task Encrypt(this Stream stream, Stream outputStream, string key, CancellationToken cancellationToken = default) public static async Task EncryptTo(this Stream stream, Stream toStream, string key, CancellationToken cancellationToken = default)
{ {
if (stream is { CanSeek: false }) throw new ArgumentNullException(nameof(stream)); if (stream is { CanRead: false }) throw new ArgumentNullException(nameof(stream));
if (key is not { Length: > 0 }) throw new ArgumentNullException(nameof(key)); if (key is not { Length: > 0 }) throw new ArgumentNullException(nameof(key));
using var aes = Aes.Create(); using var aes = Aes.Create();
@@ -23,10 +23,10 @@ public static class Security
aes.GenerateIV(); aes.GenerateIV();
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
await using var cs = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write); await using var cs = new CryptoStream(toStream, encryptor, CryptoStreamMode.Write, leaveOpen: true);
// Prepend IV to the encrypted data // Prepend IV to the encrypted data
await outputStream.WriteAsync(aes.IV.AsMemory(0, aes.IV.Length), cancellationToken); await toStream.WriteAsync(aes.IV.AsMemory(0, aes.IV.Length), cancellationToken);
var buffer = new byte[BUFFER_SIZE]; var buffer = new byte[BUFFER_SIZE];
int bytesRead; int bytesRead;
@@ -34,7 +34,7 @@ public static class Security
await cs.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); await cs.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
} }
public static async Task Decrypt(this Stream encryptedStream, Stream outputStream, string key, CancellationToken cancellationToken = default) public static async Task DecryptTo(this Stream encryptedStream, Stream toStream, string key, CancellationToken cancellationToken = default)
{ {
using var aes = Aes.Create(); using var aes = Aes.Create();
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key)); aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));
@@ -45,12 +45,12 @@ public static class Security
aes.IV = iv; aes.IV = iv;
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
await using var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read); await using var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read, leaveOpen: true);
// Read and write in chunks // Read and write in chunks
var buffer = new byte[BUFFER_SIZE]; var buffer = new byte[BUFFER_SIZE];
int bytesRead; int bytesRead;
while ((bytesRead = await cryptoStream.ReadAsync(buffer, cancellationToken)) > 0) while ((bytesRead = await cryptoStream.ReadAsync(buffer, cancellationToken)) > 0)
await outputStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); await toStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
} }
} }
+66 -4
View File
@@ -1,6 +1,8 @@
using System.Text; using System.Security.Cryptography;
using System.Text;
using Azaion.Services; using Azaion.Services;
using FluentAssertions; using FluentAssertions;
using Newtonsoft.Json;
using Xunit; using Xunit;
namespace Azaion.Test; namespace Azaion.Test;
@@ -15,16 +17,76 @@ public class SecurityTest
var password = "testpw"; var password = "testpw";
var key = Security.MakeEncryptionKey(username, password); var key = Security.MakeEncryptionKey(username, password);
await using var encryptedStream = new MemoryStream(); var encryptedStream = new MemoryStream();
await StringToStream(testString).Encrypt(encryptedStream, key); await StringToStream(testString).EncryptTo(encryptedStream, key);
encryptedStream.Seek(0, SeekOrigin.Begin);
await using var decryptedStream = new MemoryStream(); await using var decryptedStream = new MemoryStream();
await encryptedStream.Decrypt(decryptedStream, key); await encryptedStream.DecryptTo(decryptedStream, key);
encryptedStream.Close();
var str = StreamToString(decryptedStream); var str = StreamToString(decryptedStream);
str.Should().Be(testString); str.Should().Be(testString);
} }
[Fact]
public async Task EncryptDecryptLargeFileTest()
{
var username = "user@azaion.com";
var password = "testpw";
var key = Security.MakeEncryptionKey(username, password);
var largeFilePath = "large.txt";
var largeFileDecryptedPath = "large_decrypted.txt";
var stream = await CreateLargeFile(largeFilePath);
stream.Seek(0, SeekOrigin.Begin);
var encryptedStream = new MemoryStream();
await stream.EncryptTo(encryptedStream, key);
encryptedStream.Seek(0, SeekOrigin.Begin);
File.Delete(largeFileDecryptedPath);
await using var decryptedStream = new FileStream(largeFileDecryptedPath, FileMode.OpenOrCreate, FileAccess.Write);
await encryptedStream.DecryptTo(decryptedStream, key);
encryptedStream.Close();
stream.Close();
decryptedStream.Close();
await CompareFiles(largeFilePath, largeFileDecryptedPath);
File.Delete(largeFilePath);
File.Delete(largeFileDecryptedPath);
}
private async Task CompareFiles(string largeFilePath, string largeFileDecryptedPath)
{
await using var stream1 = new FileStream(largeFilePath, FileMode.Open, FileAccess.Read);
await using var stream2 = new FileStream(largeFileDecryptedPath, FileMode.Open, FileAccess.Read);
var sha256Bytes1 = Encoding.UTF8.GetString(await SHA256.HashDataAsync(stream1));
var sha256Bytes2 = Encoding.UTF8.GetString(await SHA256.HashDataAsync(stream2));
sha256Bytes1.Should().Be(sha256Bytes2);
}
private async Task<Stream> CreateLargeFile(string largeTxtPath)
{
var max = 4000000;
File.Delete(largeTxtPath);
var stream = new FileStream(largeTxtPath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
var numbersList = Enumerable.Range(1, max).Chunk(100000);
foreach (var numbers in numbersList)
{
var dict = numbers.ToDictionary(x => x, _ => DateTime.UtcNow);
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(dict, Formatting.Indented));
await stream.WriteAsync(bytes);
Console.WriteLine($"Writing numbers from {(numbers.FirstOrDefault()*100 / (double)max):F1} %");
}
await stream.FlushAsync();
return stream;
}
private static string StreamToString(Stream stream) private static string StreamToString(Stream stream)
{ {
stream.Position = 0; stream.Position = 0;