using System.Security.Cryptography; namespace MailSharp.MailClient.Services; public interface IPasswordProtector { string Protect(string plainText); string Unprotect(string protectedText); } // AES-GCM encryption using a key material file kept outside wwwroot. // Not a substitute for a real secrets manager, but avoids storing plaintext passwords in LiteDB. public class AesPasswordProtector : IPasswordProtector { private readonly byte[] _key; public AesPasswordProtector(IWebHostEnvironment env) { var keyPath = Path.Combine(env.ContentRootPath, "App_Data", "protect.key"); Directory.CreateDirectory(Path.GetDirectoryName(keyPath)!); if (File.Exists(keyPath)) { _key = Convert.FromBase64String(File.ReadAllText(keyPath)); } else { _key = RandomNumberGenerator.GetBytes(32); File.WriteAllText(keyPath, Convert.ToBase64String(_key)); } } public string Protect(string plainText) { var nonce = RandomNumberGenerator.GetBytes(12); var plainBytes = System.Text.Encoding.UTF8.GetBytes(plainText); var cipher = new byte[plainBytes.Length]; var tag = new byte[16]; using (var aes = new AesGcm(_key, 16)) { aes.Encrypt(nonce, plainBytes, cipher, tag); } var result = new byte[nonce.Length + tag.Length + cipher.Length]; Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length); Buffer.BlockCopy(tag, 0, result, nonce.Length, tag.Length); Buffer.BlockCopy(cipher, 0, result, nonce.Length + tag.Length, cipher.Length); return Convert.ToBase64String(result); } public string Unprotect(string protectedText) { var data = Convert.FromBase64String(protectedText); var nonce = data[..12]; var tag = data[12..28]; var cipher = data[28..]; var plain = new byte[cipher.Length]; using (var aes = new AesGcm(_key, 16)) { aes.Decrypt(nonce, cipher, tag, plain); } return System.Text.Encoding.UTF8.GetString(plain); } }