Add encryption for Kaigen server auth
This commit is contained in:
147
src/Managing.Core/CryptoHelpers.cs
Normal file
147
src/Managing.Core/CryptoHelpers.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user