Add encryption for Kaigen server auth

This commit is contained in:
2025-07-17 16:50:54 +07:00
parent e27b4c4a76
commit f6013b8e9d
6 changed files with 266 additions and 48 deletions

View File

@@ -0,0 +1,147 @@
using System.Security.Cryptography;
using System.Text;
namespace Managing.Core;
/// <summary>
/// Provides AES-256-CBC encryption utilities with HMAC-SHA256 authentication for secure token generation.
/// </summary>
public static class CryptoHelpers
{
/// <summary>
/// Encrypts a string using AES-256-CBC encryption with HMAC-SHA256 authentication.
/// </summary>
/// <param name="plaintext">The text to encrypt</param>
/// <param name="secretKey">The secret key for encryption (should be 32 bytes for AES-256)</param>
/// <returns>Base64 encoded encrypted data with IV and HMAC</returns>
public static string EncryptAesCbc(string plaintext, string secretKey)
{
if (string.IsNullOrEmpty(plaintext))
throw new ArgumentException("Plaintext cannot be null or empty", nameof(plaintext));
if (string.IsNullOrEmpty(secretKey))
throw new ArgumentException("Secret key cannot be null or empty", nameof(secretKey));
// Convert secret key to bytes (ensure it's 32 bytes for AES-256)
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
if (keyBytes.Length != 32)
{
// If key is not 32 bytes, hash it to get 32 bytes
using var sha256 = SHA256.Create();
keyBytes = sha256.ComputeHash(keyBytes);
}
// Convert plaintext to bytes
var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
// Generate a random IV (16 bytes for AES)
var iv = new byte[16];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(iv);
}
// Encrypt using AES-CBC
using var aes = Aes.Create();
aes.Key = keyBytes;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.IV = iv;
using var encryptor = aes.CreateEncryptor();
var ciphertext = encryptor.TransformFinalBlock(plaintextBytes, 0, plaintextBytes.Length);
// Create HMAC for authentication
using var hmac = new HMACSHA256(keyBytes);
var hmacBytes = hmac.ComputeHash(ciphertext);
// Combine IV + encrypted data + HMAC
var result = new byte[iv.Length + ciphertext.Length + hmacBytes.Length];
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
Buffer.BlockCopy(ciphertext, 0, result, iv.Length, ciphertext.Length);
Buffer.BlockCopy(hmacBytes, 0, result, iv.Length + ciphertext.Length, hmacBytes.Length);
return Convert.ToBase64String(result);
}
/// <summary>
/// Decrypts a string using AES-256-CBC decryption with HMAC-SHA256 authentication.
/// </summary>
/// <param name="encryptedData">Base64 encoded encrypted data with IV and HMAC</param>
/// <param name="secretKey">The secret key for decryption (should be 32 bytes for AES-256)</param>
/// <returns>The decrypted plaintext</returns>
public static string DecryptAesCbc(string encryptedData, string secretKey)
{
if (string.IsNullOrEmpty(encryptedData))
throw new ArgumentException("Encrypted data cannot be null or empty", nameof(encryptedData));
if (string.IsNullOrEmpty(secretKey))
throw new ArgumentException("Secret key cannot be null or empty", nameof(secretKey));
// Convert secret key to bytes (ensure it's 32 bytes for AES-256)
var keyBytes = Encoding.UTF8.GetBytes(secretKey);
if (keyBytes.Length != 32)
{
// If key is not 32 bytes, hash it to get 32 bytes
using var sha256 = SHA256.Create();
keyBytes = sha256.ComputeHash(keyBytes);
}
// Decode the base64 data
var encryptedBytes = Convert.FromBase64String(encryptedData);
// Extract IV (first 16 bytes), HMAC (last 32 bytes), and ciphertext (middle)
if (encryptedBytes.Length < 48) // 16 (IV) + 32 (HMAC) = minimum 48 bytes
throw new ArgumentException("Encrypted data is too short", nameof(encryptedData));
var iv = new byte[16];
var hmacBytes = new byte[32];
var ciphertext = new byte[encryptedBytes.Length - 48];
Buffer.BlockCopy(encryptedBytes, 0, iv, 0, 16);
Buffer.BlockCopy(encryptedBytes, encryptedBytes.Length - 32, hmacBytes, 0, 32);
Buffer.BlockCopy(encryptedBytes, 16, ciphertext, 0, ciphertext.Length);
// Verify HMAC for authentication
using var hmac = new HMACSHA256(keyBytes);
var computedHmac = hmac.ComputeHash(ciphertext);
if (!computedHmac.SequenceEqual(hmacBytes))
{
throw new CryptographicException("HMAC verification failed - data may have been tampered with");
}
// Decrypt using AES-CBC
using var aes = Aes.Create();
aes.Key = keyBytes;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.IV = iv;
using var decryptor = aes.CreateDecryptor();
var decryptedBytes = decryptor.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
return Encoding.UTF8.GetString(decryptedBytes);
}
/// <summary>
/// Encrypts a string using AES-256-CBC encryption (alias for EncryptAesCbc for backward compatibility).
/// </summary>
/// <param name="plaintext">The text to encrypt</param>
/// <param name="secretKey">The secret key for encryption</param>
/// <returns>Base64 encoded encrypted data</returns>
public static string EncryptAesGcm(string plaintext, string secretKey)
{
return EncryptAesCbc(plaintext, secretKey);
}
/// <summary>
/// Decrypts a string using AES-256-CBC decryption (alias for DecryptAesCbc for backward compatibility).
/// </summary>
/// <param name="encryptedData">Base64 encoded encrypted data</param>
/// <param name="secretKey">The secret key for decryption</param>
/// <returns>The decrypted plaintext</returns>
public static string DecryptAesGcm(string encryptedData, string secretKey)
{
return DecryptAesCbc(encryptedData, secretKey);
}
}