add project

This commit is contained in:
GuilhermeStrice
2025-07-09 19:24:12 +01:00
parent 8d7c2d4b04
commit 1108bf3ef6
17 changed files with 4752 additions and 0 deletions

View File

@ -0,0 +1,324 @@
using System.Security.Cryptography;
using System.Text;
namespace DatabaseSnapshotsService.Services
{
public class EncryptionService
{
private readonly string _encryptionKey;
private readonly bool _encryptionEnabled;
public EncryptionService(string? encryptionKey, bool encryptionEnabled = false)
{
_encryptionEnabled = encryptionEnabled;
if (encryptionEnabled && string.IsNullOrWhiteSpace(encryptionKey))
{
throw new ArgumentException("Encryption key is required when encryption is enabled");
}
_encryptionKey = encryptionKey ?? string.Empty;
}
public bool IsEncryptionEnabled => _encryptionEnabled;
/// <summary>
/// Encrypts data using AES-256-CBC
/// </summary>
/// <param name="plaintext">Data to encrypt</param>
/// <returns>Encrypted data as base64 string</returns>
public async Task<string> EncryptAsync(string plaintext)
{
if (!_encryptionEnabled)
{
return plaintext;
}
if (string.IsNullOrEmpty(plaintext))
{
return string.Empty;
}
try
{
using var aes = Aes.Create();
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// Derive key from the provided encryption key
var key = DeriveKey(_encryptionKey, aes.KeySize / 8);
aes.Key = key;
// Generate random IV
aes.GenerateIV();
using var encryptor = aes.CreateEncryptor();
var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
var ciphertext = encryptor.TransformFinalBlock(plaintextBytes, 0, plaintextBytes.Length);
// Combine IV and ciphertext
var result = new byte[aes.IV.Length + ciphertext.Length];
Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length);
Buffer.BlockCopy(ciphertext, 0, result, aes.IV.Length, ciphertext.Length);
return Convert.ToBase64String(result);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Encryption failed: {ex.Message}", ex);
}
}
/// <summary>
/// Decrypts data using AES-256-CBC
/// </summary>
/// <param name="ciphertext">Encrypted data as base64 string</param>
/// <returns>Decrypted data</returns>
public async Task<string> DecryptAsync(string ciphertext)
{
if (!_encryptionEnabled)
{
return ciphertext;
}
if (string.IsNullOrEmpty(ciphertext))
{
return string.Empty;
}
try
{
var encryptedData = Convert.FromBase64String(ciphertext);
using var aes = Aes.Create();
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// Derive key from the provided encryption key
var key = DeriveKey(_encryptionKey, aes.KeySize / 8);
aes.Key = key;
// Extract IV and ciphertext
var ivSize = aes.IV.Length;
var ciphertextSize = encryptedData.Length - ivSize;
if (ciphertextSize < 0)
{
throw new ArgumentException("Invalid encrypted data format");
}
var iv = new byte[ivSize];
var ciphertextBytes = new byte[ciphertextSize];
Buffer.BlockCopy(encryptedData, 0, iv, 0, ivSize);
Buffer.BlockCopy(encryptedData, ivSize, ciphertextBytes, 0, ciphertextSize);
aes.IV = iv;
using var decryptor = aes.CreateDecryptor();
var plaintext = decryptor.TransformFinalBlock(ciphertextBytes, 0, ciphertextBytes.Length);
return Encoding.UTF8.GetString(plaintext);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Decryption failed: {ex.Message}", ex);
}
}
/// <summary>
/// Encrypts a file
/// </summary>
/// <param name="sourceFilePath">Path to the source file</param>
/// <param name="destinationFilePath">Path for the encrypted file</param>
public async Task EncryptFileAsync(string sourceFilePath, string destinationFilePath)
{
if (!_encryptionEnabled)
{
// If encryption is disabled, just copy the file
File.Copy(sourceFilePath, destinationFilePath, true);
return;
}
try
{
using var sourceStream = File.OpenRead(sourceFilePath);
using var destinationStream = File.Create(destinationFilePath);
using var aes = Aes.Create();
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var key = DeriveKey(_encryptionKey, aes.KeySize / 8);
aes.Key = key;
aes.GenerateIV();
// Write IV to the beginning of the file
await destinationStream.WriteAsync(aes.IV);
using var encryptor = aes.CreateEncryptor();
using var cryptoStream = new CryptoStream(destinationStream, encryptor, CryptoStreamMode.Write);
await sourceStream.CopyToAsync(cryptoStream);
await cryptoStream.FlushFinalBlockAsync();
}
catch (Exception ex)
{
throw new InvalidOperationException($"File encryption failed: {ex.Message}", ex);
}
}
/// <summary>
/// Decrypts a file
/// </summary>
/// <param name="sourceFilePath">Path to the encrypted file</param>
/// <param name="destinationFilePath">Path for the decrypted file</param>
public async Task DecryptFileAsync(string sourceFilePath, string destinationFilePath)
{
if (!_encryptionEnabled)
{
// If encryption is disabled, just copy the file
File.Copy(sourceFilePath, destinationFilePath, true);
return;
}
try
{
using var sourceStream = File.OpenRead(sourceFilePath);
using var destinationStream = File.Create(destinationFilePath);
using var aes = Aes.Create();
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var key = DeriveKey(_encryptionKey, aes.KeySize / 8);
aes.Key = key;
// Read IV from the beginning of the file
var iv = new byte[aes.IV.Length];
await sourceStream.ReadAsync(iv);
aes.IV = iv;
using var decryptor = aes.CreateDecryptor();
using var cryptoStream = new CryptoStream(sourceStream, decryptor, CryptoStreamMode.Read);
await cryptoStream.CopyToAsync(destinationStream);
}
catch (Exception ex)
{
throw new InvalidOperationException($"File decryption failed: {ex.Message}", ex);
}
}
/// <summary>
/// Generates a secure encryption key
/// </summary>
/// <param name="keySize">Size of the key in bits (default: 256)</param>
/// <returns>Base64 encoded encryption key</returns>
public static string GenerateEncryptionKey(int keySize = 256)
{
if (keySize != 128 && keySize != 192 && keySize != 256)
{
throw new ArgumentException("Key size must be 128, 192, or 256 bits");
}
using var aes = Aes.Create();
aes.KeySize = keySize;
aes.GenerateKey();
return Convert.ToBase64String(aes.Key);
}
/// <summary>
/// Validates an encryption key
/// </summary>
/// <param name="key">The encryption key to validate</param>
/// <returns>True if the key is valid, false otherwise</returns>
public static bool ValidateEncryptionKey(string key)
{
if (string.IsNullOrWhiteSpace(key))
{
return false;
}
try
{
var keyBytes = Convert.FromBase64String(key);
return keyBytes.Length == 16 || keyBytes.Length == 24 || keyBytes.Length == 32; // 128, 192, or 256 bits
}
catch
{
return false;
}
}
/// <summary>
/// Derives a key from a password using PBKDF2
/// </summary>
/// <param name="password">The password to derive the key from</param>
/// <param name="keySize">Size of the derived key in bytes</param>
/// <returns>The derived key</returns>
private static byte[] DeriveKey(string password, int keySize)
{
// Create a deterministic salt from the password hash
using var sha256 = SHA256.Create();
var passwordHash = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
var salt = new byte[32];
Array.Copy(passwordHash, salt, Math.Min(passwordHash.Length, salt.Length));
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256);
return pbkdf2.GetBytes(keySize);
}
/// <summary>
/// Creates a checksum of encrypted data for integrity verification
/// </summary>
/// <param name="data">The data to create a checksum for</param>
/// <returns>SHA-256 hash of the data</returns>
public static string CreateChecksum(byte[] data)
{
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(data);
return Convert.ToBase64String(hash);
}
/// <summary>
/// Creates a checksum of a string
/// </summary>
/// <param name="data">The string to create a checksum for</param>
/// <returns>SHA-256 hash of the string</returns>
public static string CreateChecksum(string data)
{
var bytes = Encoding.UTF8.GetBytes(data);
return CreateChecksum(bytes);
}
/// <summary>
/// Verifies the integrity of encrypted data
/// </summary>
/// <param name="data">The data to verify</param>
/// <param name="expectedChecksum">The expected checksum</param>
/// <returns>True if the checksum matches, false otherwise</returns>
public static bool VerifyChecksum(byte[] data, string expectedChecksum)
{
var actualChecksum = CreateChecksum(data);
return actualChecksum.Equals(expectedChecksum, StringComparison.Ordinal);
}
/// <summary>
/// Verifies the integrity of a string
/// </summary>
/// <param name="data">The string to verify</param>
/// <param name="expectedChecksum">The expected checksum</param>
/// <returns>True if the checksum matches, false otherwise</returns>
public static bool VerifyChecksum(string data, string expectedChecksum)
{
var actualChecksum = CreateChecksum(data);
return actualChecksum.Equals(expectedChecksum, StringComparison.Ordinal);
}
}
}