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
}
}