using System.Security.Cryptography; using System.Text; namespace RelayServer.Services.Chat; /// /// AES-GCM-256 only (no RSA). Used exclusively for "at-rest" encryption of channel messages /// in the SurrealDB channel_messages table. /// /// Why a separate service from E2EeHelper: /// - E2EeHelper is for *transit* between a specific sender and a specific recipient — it /// wraps an ephemeral AES key with the recipient's RSA public key. /// - ChannelCryptoService is for *storage* — the server is both the encryptor and the /// decryptor, and it stores the symmetric channel key in server_encryption_keys.KeyBase64. /// There's no recipient to wrap for. /// /// Server flow for a chat message: /// incoming SocketEncryptedMessage (encrypted with server's RSA public key, by client) /// → E2EeHelper.DecryptForRecipient(serverPrivateKey) → plaintext /// → ChannelCryptoService.Encrypt(channelDbKey) → stored ciphertext /// → … later, on history fetch … /// → ChannelCryptoService.Decrypt(channelDbKey) → plaintext /// → E2EeHelper.EncryptForRecipient(clientPublicKey) → delivered ciphertext /// public sealed class ChannelCryptoService { public string GenerateKey() { return Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); } public (string cipherText, string nonce, string tag) Encrypt(string plainText, string keyBase64) { var key = Convert.FromBase64String(keyBase64); var nonce = RandomNumberGenerator.GetBytes(12); var plainBytes = Encoding.UTF8.GetBytes(plainText); var cipherBytes = new byte[plainBytes.Length]; var tag = new byte[16]; using var aes = new AesGcm(key, 16); aes.Encrypt(nonce, plainBytes, cipherBytes, tag); return ( Convert.ToBase64String(cipherBytes), Convert.ToBase64String(nonce), Convert.ToBase64String(tag) ); } public string Decrypt(string cipherTextBase64, string nonceBase64, string tagBase64, string keyBase64) { var key = Convert.FromBase64String(keyBase64); var nonce = Convert.FromBase64String(nonceBase64); var tag = Convert.FromBase64String(tagBase64); var cipherBytes = Convert.FromBase64String(cipherTextBase64); var plainBytes = new byte[cipherBytes.Length]; using var aes = new AesGcm(key, 16); aes.Decrypt(nonce, cipherBytes, tag, plainBytes); return Encoding.UTF8.GetString(plainBytes); } }