Files
Server/Encryption.cs
2026-04-26 12:44:06 +02:00

182 lines
6.4 KiB
C#

namespace GeoSus.Server;
using System.Security.Cryptography;
using System.Text;
// Šifrování komunikace - RSA handshake + AES-256-CBC session (kompatibilní s Unity)
public class ServerEncryption : IDisposable
{
private readonly RSA _rsa;
private readonly string _publicKeyPem;
public ServerEncryption(int keySizeBits = 2048)
{
_rsa = RSA.Create(keySizeBits);
_publicKeyPem = ExportPublicKeyPem();
}
public string PublicKeyPem => _publicKeyPem;
// Dešifruje session key od klienta
public (byte[] Key, byte[] IV) DecryptSessionKey(string encryptedKeyBase64, string encryptedIvBase64)
{
var encryptedKey = Convert.FromBase64String(encryptedKeyBase64);
var encryptedIv = Convert.FromBase64String(encryptedIvBase64);
// Používáme OaepSHA1 pro Unity kompatibilitu
var key = _rsa.Decrypt(encryptedKey, RSAEncryptionPadding.OaepSHA1);
var iv = _rsa.Decrypt(encryptedIv, RSAEncryptionPadding.OaepSHA1);
return (key, iv);
}
private string ExportPublicKeyPem()
{
var publicKey = _rsa.ExportSubjectPublicKeyInfo();
var base64 = Convert.ToBase64String(publicKey);
var sb = new StringBuilder();
sb.AppendLine("-----BEGIN PUBLIC KEY-----");
for (int i = 0; i < base64.Length; i += 64)
{
sb.AppendLine(base64.Substring(i, Math.Min(64, base64.Length - i)));
}
sb.AppendLine("-----END PUBLIC KEY-----");
return sb.ToString();
}
public void Dispose()
{
_rsa.Dispose();
}
}
// Session šifrování pro konkrétní klientské spojení - AES-256-CBC + HMAC-SHA256
public class SessionCrypto : IDisposable
{
private readonly byte[] _key;
private readonly object _lock = new();
public SessionCrypto(byte[] key, byte[] iv)
{
if (key.Length != 32)
throw new ArgumentException("Key musí být 32 bajtů pro AES-256");
if (iv.Length != 16)
throw new ArgumentException("IV musí být 16 bajtů pro AES-CBC");
_key = key;
// IV se nepoužívá přímo - každá zpráva má svůj unikátní IV
}
// Šifruje zprávu s AES-CBC a HMAC
public byte[] Encrypt(byte[] plaintext)
{
lock (_lock)
{
using var aes = Aes.Create();
aes.Key = _key;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.GenerateIV(); // Generujeme nový IV pro každou zprávu
byte[] ciphertext;
using (var encryptor = aes.CreateEncryptor())
{
ciphertext = encryptor.TransformFinalBlock(plaintext, 0, plaintext.Length);
}
// Počítáme HMAC přes IV + ciphertext (používáme AES klíč pro HMAC)
byte[] hmac;
using (var hmacSha = new HMACSHA256(_key))
{
var dataToSign = new byte[aes.IV.Length + ciphertext.Length];
Buffer.BlockCopy(aes.IV, 0, dataToSign, 0, aes.IV.Length);
Buffer.BlockCopy(ciphertext, 0, dataToSign, aes.IV.Length, ciphertext.Length);
hmac = hmacSha.ComputeHash(dataToSign);
}
// Výstup: [16 bytes IV][32 bytes HMAC][ciphertext]
var result = new byte[16 + 32 + ciphertext.Length];
Buffer.BlockCopy(aes.IV, 0, result, 0, 16);
Buffer.BlockCopy(hmac, 0, result, 16, 32);
Buffer.BlockCopy(ciphertext, 0, result, 48, ciphertext.Length);
return result;
}
}
// Dešifruje zprávu a validuje HMAC
public byte[]? Decrypt(byte[] encrypted)
{
if (encrypted.Length < 48) return null; // 16 IV + 32 HMAC + min data
try
{
var iv = new byte[16];
var receivedHmac = new byte[32];
var ciphertext = new byte[encrypted.Length - 48];
Buffer.BlockCopy(encrypted, 0, iv, 0, 16);
Buffer.BlockCopy(encrypted, 16, receivedHmac, 0, 32);
Buffer.BlockCopy(encrypted, 48, ciphertext, 0, ciphertext.Length);
// Ověříme HMAC (používáme AES klíč pro HMAC)
byte[] computedHmac;
using (var hmacSha = new HMACSHA256(_key))
{
var dataToVerify = new byte[16 + ciphertext.Length];
Buffer.BlockCopy(iv, 0, dataToVerify, 0, 16);
Buffer.BlockCopy(ciphertext, 0, dataToVerify, 16, ciphertext.Length);
computedHmac = hmacSha.ComputeHash(dataToVerify);
}
// Constant-time porovnání
if (!CryptographicOperations.FixedTimeEquals(receivedHmac, computedHmac))
{
return null; // HMAC mismatch - zpráva byla změněna
}
// Dešifrujeme
using var aes = Aes.Create();
aes.Key = _key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using var decryptor = aes.CreateDecryptor();
return decryptor.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
}
catch (CryptographicException)
{
return null;
}
}
public void Dispose()
{
Array.Clear(_key, 0, _key.Length);
}
}
// Klientská strana - generuje session key a šifruje ho RSA public key
public static class ClientEncryptionHelper
{
public static (byte[] Key, byte[] IV) GenerateSessionKey()
{
var key = RandomNumberGenerator.GetBytes(32); // AES-256
var iv = RandomNumberGenerator.GetBytes(16); // AES-CBC IV (16 bytes)
return (key, iv);
}
public static (string EncryptedKey, string EncryptedIV) EncryptSessionKey(
string rsaPublicKeyPem, byte[] key, byte[] iv)
{
using var rsa = RSA.Create();
rsa.ImportFromPem(rsaPublicKeyPem);
var encryptedKey = rsa.Encrypt(key, RSAEncryptionPadding.OaepSHA256);
var encryptedIv = rsa.Encrypt(iv, RSAEncryptionPadding.OaepSHA256);
return (Convert.ToBase64String(encryptedKey), Convert.ToBase64String(encryptedIv));
}
}