using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Security.Cryptography; namespace FSI.Lib.EasyEncryption { /// /// Encryption algorithm types. /// public enum EncryptionAlgorithm { /// /// Specifies the Advanced Encryption Standard (AES) symmetric encryption algorithm. /// Aes, /// /// Specifies the Data Encryption Standard (DES) symmetric encryption algorithm. /// Des, /// /// Specifies the RC2 symmetric encryption algorithm. /// Rc2, /// /// Specifies the Rijndael symmetric encryption algorithm. /// Rijndael, /// /// Specifies the TripleDES symmetric encryption algorithm. /// TripleDes } /// /// Class to provide encryption and decryption services. /// /// /// /// The Encryption class performs encryption and decryption using the specified algorithm. /// /// /// The encrypted values may appear considerably larger than the decrypted values /// because encrypted values contains some additional meta data. If either data size or /// performance is a concern, use the or /// methods to work with the streaming classes /// instead. /// /// public class Encryption { private static readonly int SaltLength = 8; private static readonly Dictionary AlgorithmLookup = new Dictionary { [EncryptionAlgorithm.Aes] = typeof(Aes), [EncryptionAlgorithm.Des] = typeof(DES), [EncryptionAlgorithm.Rc2] = typeof(RC2), [EncryptionAlgorithm.Rijndael] = typeof(Rijndael), [EncryptionAlgorithm.TripleDes] = typeof(TripleDES), }; private string Password { get; } private Type AlgorithmType { get; } /// /// Converts a byte array to a string. /// /// The byte array to be converted. /// Returns the converted string. public static string EncodeBytesToString(byte[] bytes) => Convert.ToBase64String(bytes); /// /// Converts a string to a byte array. /// /// The string to be converted. /// Returns the converted byte array. public static byte[] DecodeBytesFromString(string s) => Convert.FromBase64String(s); /// /// Constructs a new Encryption instance. /// /// Specifies the encryption password. Leading and trailing spaces are removed. /// Specifies which encryption algorithm is used. public Encryption(string password, EncryptionAlgorithm algorithm) { if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Password is required.", nameof(password)); Password = password.Trim(); AlgorithmType = AlgorithmLookup[algorithm]; } #region Encryption stream creation methods /// /// Creates an instance using the specified stream. /// /// /// The class is the preferred method to encrypt data /// as it will store some necessary meta data only once for all data in the stream. /// It is also more performant. The other encryption methods defer to the EncryptionWriter /// class for actual encryption. /// /// The stream the encrypted data will be written to. /// An instance of the class. public EncryptionWriter CreateStreamWriter(Stream stream) { // Create a random salt and write to stream byte[] salt = CreateSalt(); stream.Write(salt, 0, salt.Length); // Create symmetric algorithm SymmetricAlgorithm algorithm = CreateAlgorithm(); algorithm.Padding = PaddingMode.PKCS7; // Create key and IV byte[] key, iv; GenerateKeyAndIv(algorithm, salt, out key, out iv); // Create EncryptionWriter ICryptoTransform encryptor = algorithm.CreateEncryptor(key, iv); CryptoStream cs = new CryptoStream(stream, encryptor, CryptoStreamMode.Write); return new EncryptionWriter(algorithm, encryptor, stream); } /// /// Creates an instance using the specified file name. /// /// /// The class is the preferred method to encrypt data /// as it will store some necessary meta data only once for all data in the stream. /// It is also more performant. The other encryption methods defer to the EncryptionWriter /// class for actual encryption. /// /// The file name the encrypted data will be written to. /// An instance of the class. public EncryptionWriter CreateStreamWriter(string path) { return CreateStreamWriter(File.Open(path, FileMode.OpenOrCreate, FileAccess.Write)); } /// /// Creates an instance using the specified stream. /// /// /// The class is the preferred method to decrypt data. /// It is also more performant. The other decryption methods defer to the EncryptionReader /// class for actual decryption. /// /// The stream the encrypted data will be read from. /// An instance of the class. public EncryptionReader CreateStreamReader(Stream stream) { // Read salt from input stream byte[] salt = new byte[SaltLength]; if (stream.Read(salt, 0, salt.Length) < SaltLength) throw new ArgumentOutOfRangeException("Reached end of input stream before reading encryption metadata."); // Create symmetric algorithm SymmetricAlgorithm algorithm = CreateAlgorithm(); algorithm.Padding = PaddingMode.PKCS7; // Create key and IV byte[] key, iv; GenerateKeyAndIv(algorithm, salt, out key, out iv); // Create EncryptionReader ICryptoTransform decryptor = algorithm.CreateDecryptor(key, iv); CryptoStream cs = new CryptoStream(stream, decryptor, CryptoStreamMode.Read); return new EncryptionReader(algorithm, decryptor, stream); } /// /// Creates an instance using the specified file name. /// /// /// The class is the preferred method to decrypt data. /// It is also more performant. The other decryption methods defer to the EncryptionReader /// class for actual decryption. /// /// The file name the encrypted data will be read from. /// An instance of the class. public EncryptionReader CreateStreamReader(string path) { return CreateStreamReader(File.Open(path, FileMode.Open, FileAccess.Read)); } #endregion #region Encryption /// /// Encrypts a string value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(string value) => Encrypt(w => w.Write(value ?? string.Empty)); /// /// Encrypts a bool value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(bool value) => Encrypt(w => w.Write(value)); /// /// Encrypts a char value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(char value) => Encrypt(w => w.Write(value)); /// /// Encrypts a sbyte value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(sbyte value) => Encrypt(w => w.Write(value)); /// /// Encrypts a byte value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(byte value) => Encrypt(w => w.Write(value)); /// /// Encrypts a short value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(short value) => Encrypt(w => w.Write(value)); /// /// Encrypts a ushort value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(ushort value) => Encrypt(w => w.Write(value)); /// /// Encrypts a int value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(int value) => Encrypt(w => w.Write(value)); /// /// Encrypts a uint value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(uint value) => Encrypt(w => w.Write(value)); /// /// Encrypts a long value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(long value) => Encrypt(w => w.Write(value)); /// /// Encrypts a ulong value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(ulong value) => Encrypt(w => w.Write(value)); /// /// Encrypts a float value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(float value) => Encrypt(w => w.Write(value)); /// /// Encrypts a double value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(double value) => Encrypt(w => w.Write(value)); /// /// Encrypts a decimal value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(decimal value) => Encrypt(w => w.Write(value)); /// /// Encrypts a DateTime value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(DateTime value) => Encrypt(w => w.Write(value)); /// /// Encrypts a byte[] value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(byte[] value) => Encrypt(w => w.Write(value)); /// /// Encrypts a string[] value. /// /// /// The encrypted value will be larger than the unencrypted value because the /// encrypted value will contain some additional meta data. To minimize the /// size of this meta data when encrypting multiple values, use /// to create a instead. /// /// The value to decrypt. /// Returns the encrypted value public string Encrypt(string[] value) => Encrypt(w => w.Write(value)); private string Encrypt(Action action) { using (MemoryStream stream = new MemoryStream()) using (EncryptionWriter writer = CreateStreamWriter(stream)) { action(writer); return EncodeBytesToString(stream.ToArray()); } } #endregion #region Decryption /// /// Decrypts a string value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public string DecryptString(string encryptedValue) => (string)Decrypt(encryptedValue, r => r.ReadString()); /// /// Decrypts a bool value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public bool DecryptBoolean(string encryptedValue) => (bool)Decrypt(encryptedValue, r => r.ReadBoolean()); /// /// Decrypts a char value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public char DecryptChar(string encryptedValue) => (char)Decrypt(encryptedValue, r => r.ReadChar()); /// /// Decrypts a sbyte value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public sbyte DecryptSByte(string encryptedValue) => (sbyte)Decrypt(encryptedValue, r => r.ReadSByte()); /// /// Decrypts a byte value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public byte DecryptByte(string encryptedValue) => (byte)Decrypt(encryptedValue, r => r.ReadByte()); /// /// Decrypts a short value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public short DecryptInt16(string encryptedValue) => (short)Decrypt(encryptedValue, r => r.ReadInt16()); /// /// Decrypts a ushort value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public ushort DecryptUInt16(string encryptedValue) => (ushort)Decrypt(encryptedValue, r => r.ReadUInt16()); /// /// Decrypts a int value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public int DecryptInt32(string encryptedValue) => (int)Decrypt(encryptedValue, r => r.ReadInt32()); /// /// Decrypts a uint value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public uint DecryptUInt32(string encryptedValue) => (uint)Decrypt(encryptedValue, r => r.ReadUInt32()); /// /// Decrypts a long value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public long DecryptInt64(string encryptedValue) => (long)Decrypt(encryptedValue, r => r.ReadInt64()); /// /// Decrypts a ulong value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public ulong DecryptUInt64(string encryptedValue) => (ulong)Decrypt(encryptedValue, r => r.ReadUInt64()); /// /// Decrypts a float value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public float DecryptSingle(string encryptedValue) => (float)Decrypt(encryptedValue, r => r.ReadSingle()); /// /// Decrypts a double value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public double DecryptDouble(string encryptedValue) => (double)Decrypt(encryptedValue, r => r.ReadDouble()); /// /// Decrypts a decimal value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public decimal DecryptDecimal(string encryptedValue) => (decimal)Decrypt(encryptedValue, r => r.ReadDecimal()); /// /// Decrypts a DateTime value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public DateTime DecryptDateTime(string encryptedValue) => (DateTime)Decrypt(encryptedValue, r => r.ReadDateTime()); /// /// Decrypts a byte[] value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public byte[] DecryptByteArray(string encryptedValue) => (byte[])Decrypt(encryptedValue, r => r.ReadByteArray()); /// /// Decrypts a string[] value encrypted with . /// /// The value to decrypt. /// Returns the decrypted value public string[] DecryptStringArray(string encryptedValue) => (string[])Decrypt(encryptedValue, r => r.ReadStringArray()); private object Decrypt(string encryptedValue, Func action) { using (MemoryStream stream = new MemoryStream(DecodeBytesFromString(encryptedValue))) using (EncryptionReader reader = CreateStreamReader(stream)) { return action(reader); } } #endregion #region Encryption/decryption of objects /// /// Class to hold encrypt/decrypt functions for each supported data type. /// private class TypeInfo { public Func Encrypt { get; set; } public Func Decrypt { get; set; } public TypeInfo(Func encrypt, Func decrypt) { Encrypt = encrypt; Decrypt = decrypt; } } private static readonly Dictionary TypeInfoLookup = new Dictionary() { [typeof(string)] = new TypeInfo((e, v) => e.Encrypt((string)v), (e, s) => e.DecryptString(s)), [typeof(bool)] = new TypeInfo((e, v) => e.Encrypt((bool)v), (e, s) => e.DecryptBoolean(s)), [typeof(char)] = new TypeInfo((e, v) => e.Encrypt((char)v), (e, s) => e.DecryptChar(s)), [typeof(sbyte)] = new TypeInfo((e, v) => e.Encrypt((sbyte)v), (e, s) => e.DecryptSByte(s)), [typeof(byte)] = new TypeInfo((e, v) => e.Encrypt((byte)v), (e, s) => e.DecryptByte(s)), [typeof(short)] = new TypeInfo((e, v) => e.Encrypt((short)v), (e, s) => e.DecryptInt16(s)), [typeof(ushort)] = new TypeInfo((e, v) => e.Encrypt((ushort)v), (e, s) => e.DecryptUInt16(s)), [typeof(int)] = new TypeInfo((e, v) => e.Encrypt((int)v), (e, s) => e.DecryptInt32(s)), [typeof(uint)] = new TypeInfo((e, v) => e.Encrypt((uint)v), (e, s) => e.DecryptUInt32(s)), [typeof(long)] = new TypeInfo((e, v) => e.Encrypt((long)v), (e, s) => e.DecryptInt64(s)), [typeof(ulong)] = new TypeInfo((e, v) => e.Encrypt((ulong)v), (e, s) => e.DecryptUInt64(s)), [typeof(float)] = new TypeInfo((e, v) => e.Encrypt((float)v), (e, s) => e.DecryptSingle(s)), [typeof(double)] = new TypeInfo((e, v) => e.Encrypt((double)v), (e, s) => e.DecryptDouble(s)), [typeof(decimal)] = new TypeInfo((e, v) => e.Encrypt((decimal)v), (e, s) => e.DecryptDecimal(s)), [typeof(DateTime)] = new TypeInfo((e, v) => e.Encrypt((DateTime)v), (e, s) => e.DecryptDateTime(s)), [typeof(byte[])] = new TypeInfo((e, v) => e.Encrypt((byte[])v), (e, s) => e.DecryptByteArray(s)), [typeof(string[])] = new TypeInfo((e, v) => e.Encrypt((string[])v), (e, s) => e.DecryptStringArray(s)), }; /// /// Indicates if the specified data type is supported by the encryption and decryption methods. /// /// /// The encryption code supports all basic .NET data types in addition to byte[] /// and string[]. More complex data types are not supported. /// /// The data type to be tested. /// True if the specified type is supported. False otherwise. public static bool IsTypeSupported(Type type) => TypeInfoLookup.ContainsKey(type); /// /// Encrypts an object value. The object must hold one of the supported data types. /// /// Object to be encrypted. /// holds an unsupported data type. /// An encrypted string that can be decrypted using Decrypt. public string Encrypt(object value) { if (value == null) return null; if (TypeInfoLookup.TryGetValue(value.GetType(), out TypeInfo info)) return info.Encrypt(this, value); throw new ArgumentException(string.Format("Cannot encrypt value : Data type '{0}' is not supported", value.GetType())); } /// /// Decrypts an object value of the specified type. /// /// The encrypted string to be decrypted. /// The type of data that was originally encrypted into . /// /// Returns the decrypted value. public object Decrypt(string encryptedValue, Type targetType) { if (TypeInfoLookup.TryGetValue(targetType, out TypeInfo info)) return info.Decrypt(this, encryptedValue); throw new ArgumentException(string.Format("Cannot decrypt value : Data type '{0}' is not supported", targetType)); } #endregion #region Support methods /// /// Creates a SymmetricAlgorithm instance for the current encryption algorithm. /// /// /// Returns the created SymmetricAlgorithm instance. /// protected SymmetricAlgorithm CreateAlgorithm() { MethodInfo method = AlgorithmType.GetMethod("Create", Array.Empty()); if (method != null) { if (method.Invoke(null, null) is SymmetricAlgorithm algorithm) return algorithm; } throw new Exception($"Unable to create instance of {AlgorithmType.FullName}."); } /// /// Generates a salt that contains a cryptographically strong sequence of random values. /// /// The generated salt value. private byte[] CreateSalt() { byte[] salt = new byte[SaltLength]; using (RNGCryptoServiceProvider generator = new RNGCryptoServiceProvider()) { generator.GetBytes(salt); } return salt; } /// /// Generates a pseudorandom key and initialization vector from the current password and the /// given salt. /// /// being used to encrypt. /// The salt used to derive the key and initialization vector. /// Returns the generated key. /// Returns the generated initialization vector. protected void GenerateKeyAndIv(SymmetricAlgorithm algorithm, byte[] salt, out byte[] key, out byte[] iv) { int keyLength = algorithm.KeySize >> 3; int ivLength = algorithm.BlockSize >> 3; byte[] bytes = DeriveBytes(salt, keyLength + ivLength); key = new byte[keyLength]; Buffer.BlockCopy(bytes, 0, key, 0, keyLength); iv = new byte[ivLength]; Buffer.BlockCopy(bytes, keyLength, iv, 0, ivLength); } /// /// Generates a series of pseudorandom bytes of the specified length based on the current /// password and the given salt. /// /// The salt used to derive the bytes. /// The number of bits of data to generate. /// Returns the derived bytes. protected byte[] DeriveBytes(byte[] salt, int bytes) { Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes(Password, salt, 1000); return derivedBytes.GetBytes(bytes); } #endregion } }