Files
mmorales.photo/back/services/engine/Crypto/CryptoService.cs
2025-08-24 14:18:20 +02:00

175 lines
6.6 KiB
C#

using Microsoft.Extensions.Caching.Memory;
using System.Security.Cryptography;
namespace back.services.engine.Crypto;
public class CryptoService(IMemoryCache cache) : ICryptoService
{
private readonly IMemoryCache _cache = cache;
private readonly MemoryCacheEntryOptions _CacheOptions = new()
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1),
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
SlidingExpiration = TimeSpan.FromMinutes(30),
Priority = CacheItemPriority.High,
PostEvictionCallbacks =
{
new PostEvictionCallbackRegistration
{
EvictionCallback = (key, value, reason, state) =>
{
var clientId = key.ToString()?.Replace("_public","").Replace("_private","");
if(string.IsNullOrEmpty(clientId)) { return; }
// Handle the eviction of the certificate - removing public/private keys from the cache
try{ cache.Remove($"{clientId}_public"); } catch{ }
try{ cache.Remove($"{clientId}_private"); } catch{ }
}
}
}
};
public string? Encrypt(string clientId,string plainText)
{
// get keys from cache
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
{
throw new InvalidOperationException("Private certificate not found for the client.");
}
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
{
throw new InvalidOperationException("Public certificate not found for the client.");
}
// import rsa keys and configure RSA for encryption
using var rsa = RSA.Create(2048);
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
// Encrypt the plain text using RSA
string? encryptedText = null;
try
{
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
var encryptedBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.OaepSHA256);
encryptedText = Convert.ToBase64String(encryptedBytes);
}
catch (CryptographicException ex)
{
// Handle encryption errors
throw new InvalidOperationException("Encryption failed.", ex);
}
return encryptedText;
}
public string? Decrypt(string clientId, string encryptedText)
{
// get keys from cache
if (!_cache.TryGetValue($"{clientId}_private", out string? privateCert) || string.IsNullOrEmpty(privateCert))
{
throw new InvalidOperationException("Private certificate not found for the client.");
}
if (!_cache.TryGetValue($"{clientId}_public", out string? publicCert) || string.IsNullOrEmpty(publicCert))
{
throw new InvalidOperationException("Private certificate not found for the client.");
}
// import rsa keys and configure RSA for decryption
using var rsa = RSA.Create(2048);
rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicCert), out _);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateCert), out _);
// Decrypt the encrypted text using RSA
string? plainText = null;
try
{
var encryptedBytes = Convert.FromBase64String(encryptedText);
var decryptedBytes = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA256);
plainText = System.Text.Encoding.UTF8.GetString(decryptedBytes);
}
catch (CryptographicException ex)
{
// Handle decryption errors
throw new InvalidOperationException("Decryption failed.", ex);
}
return plainText;
}
public string GetPublicCertificate(string clientId)
{
if (_cache.TryGetValue($"{clientId}_public", out string? publicCert) && !string.IsNullOrEmpty(publicCert))
{
return publicCert;
}
(publicCert, string privateCert) = GenerateCertificate();
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
return publicCert;
}
public string GetPrivateCertificate(string clientId)
{
if (_cache.TryGetValue($"{clientId}_private", out string? privateCert) && !string.IsNullOrEmpty(privateCert))
{
return privateCert;
}
(string publicCert, privateCert) = GenerateCertificate();
_cache.Set($"{clientId}_public", publicCert, _CacheOptions);
_cache.Set($"{clientId}_private", privateCert, _CacheOptions);
return privateCert;
}
private static (string publicCert, string privateCert) GenerateCertificate()
{
// Generate a new RSA key pair for the client
using var rsa = RSA.Create(2048);
var publicKey = rsa.ExportSubjectPublicKeyInfo();
var privateKey = rsa.ExportRSAPrivateKey();
// Convert to Base64 strings for storage
var publicCert = Convert.ToBase64String(publicKey);
var privateCert = Convert.ToBase64String(privateKey);
return (publicCert, privateCert);
}
public string? Hash(string plainText)
{
string? hash = null;
if (string.IsNullOrEmpty(plainText))
{
return hash;
}
var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
var hashBytes = SHA256.HashData(plainBytes);
hash = Convert.ToBase64String(hashBytes);
return hash;
}
public bool VerifyHash(string plainText, string hash)
{
var plainTextHash = Hash(plainText);
if (string.IsNullOrEmpty(plainTextHash) || string.IsNullOrEmpty(hash))
{
return false;
}
return plainTextHash.Equals(hash, StringComparison.OrdinalIgnoreCase);
}
public string Pepper()
{
// get pepper from environtment variable
var pepper = Environment.GetEnvironmentVariable("PEPPER");
if (string.IsNullOrEmpty(pepper))
{
return "BactilForteFlash20mg";
}
return pepper;
}
public string Salt()
{
var saltBytes = new byte[32]; // 256 bits
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(saltBytes);
return Convert.ToBase64String(saltBytes);
}
public string? HashPassword(string plainPassword, string plainSalt)
{
return Hash($"{plainPassword}{plainSalt}{Pepper()}");
}
}