175 lines
6.6 KiB
C#
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()}");
|
|
}
|
|
} |