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()}"); } }