182 lines
6.4 KiB
C#
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));
|
|
}
|
|
}
|