using System.Globalization;
using System.Security.Cryptography;
using System.Text;
namespace PanoramicData.Encryption;
public class EncryptionService
{
private readonly byte[] _key;
private readonly static RandomNumberGenerator _random = RandomNumberGenerator.Create();
private readonly static UTF8Encoding _encoder = new();
private readonly static Aes _aes = Aes.Create();
private readonly static SHA256 _hashAlgorithm = SHA256.Create();
///
/// Created an EncryptionService
///
/// A 64 character hex string
///
public EncryptionService(string encryptionKey)
{
ArgumentNullException.ThrowIfNull(encryptionKey, nameof(encryptionKey));
if (encryptionKey.Length != 64) {
throw new ArgumentException("EncryptionKey must be a 64 character hex string", nameof(encryptionKey));
}
_key = HexStringToByteArray(encryptionKey);
}
private static byte[] GenerateVector()
{
var vector = new byte[16];
_random.GetBytes(vector);
return vector;
}
public (string cipherText, string salt) Encrypt(string unencrypted)
{
var vector = GenerateVector();
var encryptor = _aes.CreateEncryptor(_key, vector);
return (ByteArrayToHexString(Transform(_encoder.GetBytes(unencrypted), encryptor)), ByteArrayToHexString(vector));
}
public (string cipherText, string salt) Encrypt(string unencrypted, string? salt)
{
if (salt is null) {
return Encrypt(unencrypted);
}
if (salt.Length != 32) {
throw new ArgumentException("Salt must be a 32 character hex string", nameof(salt));
}
var vector = HexStringToByteArray(salt);
var encryptor = _aes.CreateEncryptor(_key, vector);
return (ByteArrayToHexString(Transform(_encoder.GetBytes(unencrypted), encryptor)), ByteArrayToHexString(vector));
}
public string Decrypt(string encryptedString, string salt)
{
ArgumentNullException.ThrowIfNull(encryptedString, nameof(encryptedString));
ArgumentNullException.ThrowIfNull(salt, nameof(salt));
if (salt.Length != 32) {
throw new ArgumentException("Salt must be a 32 character hex string", nameof(salt));
}
var vector = HexStringToByteArray(salt);
var encrypted = HexStringToByteArray(encryptedString);
var decryptor = _aes.CreateDecryptor(_key, vector);
var decrypt = _encoder.GetString(Transform(encrypted, decryptor));
return decrypt;
}
private static byte[] Transform(byte[] buffer, ICryptoTransform cryptoTransform)
{
var stream = new MemoryStream();
using (var cs = new CryptoStream(stream, cryptoTransform, CryptoStreamMode.Write)) {
cs.Write(buffer, 0, buffer.Length);
}
return stream.ToArray();
}
private static string ByteArrayToHexString(byte[] byteArray)
{
var hex = new StringBuilder(byteArray.Length * 2);
foreach (var @byte in byteArray) {
hex.AppendFormat("{0:x2}", @byte);
}
return hex.ToString();
}
private static byte[] HexStringToByteArray(string hexString)
{
if (!hexString.All("0123456789abcdefABCDEF".Contains)) {
throw new ArgumentException("Expected a hexadecimal string.");
}
return Enumerable.Range(0, hexString.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hexString.Substring(x, 2), 16))
.ToArray();
}
public static string GetHash(string inputString)
{
ArgumentNullException.ThrowIfNull(inputString, nameof(inputString));
var sb = new StringBuilder();
foreach (var @byte in _hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString))) {
sb.Append(@byte.ToString("X2", CultureInfo.InvariantCulture));
}
var result = sb.ToString();
return result;
}
///
/// Returns a measure of entropy represented in a given string, per
/// http://en.wikipedia.org/wiki/Entropy_(information_theory)
/// Credit: https://codereview.stackexchange.com/questions/868/calculating-entropy-of-a-string
///
public static double GetShannonEntropy(string text)
{
ArgumentNullException.ThrowIfNull(text, nameof(text));
// Create a dictionary of each character and its frequency
var characterFrequencyMap = new Dictionary();
foreach (var @char in text) {
if (!characterFrequencyMap.ContainsKey(@char)) {
characterFrequencyMap.Add(@char, 1);
}
else {
characterFrequencyMap[@char] += 1;
}
}
// Calculate the entropy
var result = 0.0;
foreach (var item in characterFrequencyMap) {
var frequency = (double)item.Value / text.Length;
result -= frequency * (Math.Log(frequency) / Math.Log(2));
}
// Return the result
return result;
}
}