|
| 1 | +using System.Security.Cryptography; |
| 2 | +using System.Text; |
| 3 | + |
| 4 | +namespace OrderMonitor.Infrastructure.Security; |
| 5 | + |
| 6 | +/// <summary> |
| 7 | +/// Utility for encrypting and decrypting passwords using AES encryption. |
| 8 | +/// </summary> |
| 9 | +public static class PasswordEncryptor |
| 10 | +{ |
| 11 | + // Default key - in production, use a key from environment variable or secure storage |
| 12 | + private static readonly string DefaultKey = "OrderMonitor2026SecureKey32Bytes!"; |
| 13 | + |
| 14 | + /// <summary> |
| 15 | + /// Encrypts a plain text password. |
| 16 | + /// </summary> |
| 17 | + /// <param name="plainText">The plain text password to encrypt.</param> |
| 18 | + /// <param name="key">Optional encryption key (32 characters). If not provided, uses default key.</param> |
| 19 | + /// <returns>Base64 encoded encrypted string with IV prepended.</returns> |
| 20 | + public static string Encrypt(string plainText, string? key = null) |
| 21 | + { |
| 22 | + if (string.IsNullOrEmpty(plainText)) |
| 23 | + return plainText; |
| 24 | + |
| 25 | + var encryptionKey = GetKeyBytes(key ?? DefaultKey); |
| 26 | + |
| 27 | + using var aes = Aes.Create(); |
| 28 | + aes.Key = encryptionKey; |
| 29 | + aes.GenerateIV(); |
| 30 | + |
| 31 | + using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); |
| 32 | + var plainBytes = Encoding.UTF8.GetBytes(plainText); |
| 33 | + var encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); |
| 34 | + |
| 35 | + // Prepend IV to encrypted data |
| 36 | + var result = new byte[aes.IV.Length + encryptedBytes.Length]; |
| 37 | + Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length); |
| 38 | + Buffer.BlockCopy(encryptedBytes, 0, result, aes.IV.Length, encryptedBytes.Length); |
| 39 | + |
| 40 | + return Convert.ToBase64String(result); |
| 41 | + } |
| 42 | + |
| 43 | + /// <summary> |
| 44 | + /// Decrypts an encrypted password. |
| 45 | + /// </summary> |
| 46 | + /// <param name="encryptedText">The Base64 encoded encrypted string.</param> |
| 47 | + /// <param name="key">Optional encryption key (32 characters). If not provided, uses default key.</param> |
| 48 | + /// <returns>The decrypted plain text password.</returns> |
| 49 | + public static string Decrypt(string encryptedText, string? key = null) |
| 50 | + { |
| 51 | + if (string.IsNullOrEmpty(encryptedText)) |
| 52 | + return encryptedText; |
| 53 | + |
| 54 | + // Check if it looks like an encrypted value (Base64 with reasonable length) |
| 55 | + if (!IsEncrypted(encryptedText)) |
| 56 | + return encryptedText; // Return as-is if not encrypted |
| 57 | + |
| 58 | + try |
| 59 | + { |
| 60 | + var encryptionKey = GetKeyBytes(key ?? DefaultKey); |
| 61 | + var fullCipher = Convert.FromBase64String(encryptedText); |
| 62 | + |
| 63 | + using var aes = Aes.Create(); |
| 64 | + aes.Key = encryptionKey; |
| 65 | + |
| 66 | + // Extract IV from the beginning |
| 67 | + var iv = new byte[aes.BlockSize / 8]; |
| 68 | + var cipherBytes = new byte[fullCipher.Length - iv.Length]; |
| 69 | + |
| 70 | + Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length); |
| 71 | + Buffer.BlockCopy(fullCipher, iv.Length, cipherBytes, 0, cipherBytes.Length); |
| 72 | + |
| 73 | + aes.IV = iv; |
| 74 | + |
| 75 | + using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); |
| 76 | + var plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); |
| 77 | + |
| 78 | + return Encoding.UTF8.GetString(plainBytes); |
| 79 | + } |
| 80 | + catch |
| 81 | + { |
| 82 | + // If decryption fails, return original (might be plain text) |
| 83 | + return encryptedText; |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + /// <summary> |
| 88 | + /// Checks if a string appears to be encrypted (Base64 with minimum length for IV + data). |
| 89 | + /// </summary> |
| 90 | + public static bool IsEncrypted(string value) |
| 91 | + { |
| 92 | + if (string.IsNullOrEmpty(value) || value.Length < 24) |
| 93 | + return false; |
| 94 | + |
| 95 | + try |
| 96 | + { |
| 97 | + var bytes = Convert.FromBase64String(value); |
| 98 | + return bytes.Length >= 32; // At least IV (16) + some encrypted data |
| 99 | + } |
| 100 | + catch |
| 101 | + { |
| 102 | + return false; |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + private static byte[] GetKeyBytes(string key) |
| 107 | + { |
| 108 | + // Ensure key is exactly 32 bytes (256 bits) for AES-256 |
| 109 | + var keyBytes = Encoding.UTF8.GetBytes(key); |
| 110 | + var result = new byte[32]; |
| 111 | + |
| 112 | + if (keyBytes.Length >= 32) |
| 113 | + { |
| 114 | + Buffer.BlockCopy(keyBytes, 0, result, 0, 32); |
| 115 | + } |
| 116 | + else |
| 117 | + { |
| 118 | + Buffer.BlockCopy(keyBytes, 0, result, 0, keyBytes.Length); |
| 119 | + // Pad with derived bytes |
| 120 | + using var sha = SHA256.Create(); |
| 121 | + var hash = sha.ComputeHash(keyBytes); |
| 122 | + Buffer.BlockCopy(hash, 0, result, keyBytes.Length, 32 - keyBytes.Length); |
| 123 | + } |
| 124 | + |
| 125 | + return result; |
| 126 | + } |
| 127 | +} |
0 commit comments