819 lines
23 KiB
C#
819 lines
23 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace Strata.Base.Internal.EncryptionUtils
|
|
{
|
|
|
|
#region Hash
|
|
|
|
/// <summary>
|
|
/// Hash functions are fundamental to modern cryptography. These functions map binary
|
|
/// strings of an arbitrary length to small binary strings of a fixed length, known as
|
|
/// hash values. A cryptographic hash function has the property that it is computationally
|
|
/// infeasible to find two distinct inputs that hash to the same value. Hash functions
|
|
/// are commonly used with digital signatures and for data integrity.
|
|
/// </summary>
|
|
public class Hasher
|
|
{
|
|
|
|
/// <summary>
|
|
/// Type of hash; some are security oriented, others are fast and simple
|
|
/// </summary>
|
|
internal enum Provider
|
|
{
|
|
/// <summary>
|
|
/// Secure Hashing Algorithm provider, SHA-2 variant, 256-bit
|
|
/// </summary>
|
|
SHA256,
|
|
/// <summary>
|
|
/// Secure Hashing Algorithm provider, SHA-2 variant, 384-bit
|
|
/// </summary>
|
|
SHA384,
|
|
/// <summary>
|
|
/// Secure Hashing Algorithm provider, SHA-2 variant, 512-bit
|
|
/// </summary>
|
|
SHA512
|
|
}
|
|
|
|
private HashAlgorithm _Hash;
|
|
private Data _HashValue = new Data();
|
|
|
|
internal Hasher()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiate a new hash of the specified type
|
|
/// </summary>
|
|
internal Hasher(Provider p)
|
|
{
|
|
switch (p)
|
|
{
|
|
case Provider.SHA256:
|
|
{
|
|
_Hash = SHA256.Create();
|
|
break;
|
|
}
|
|
case Provider.SHA384:
|
|
{
|
|
_Hash = SHA384.Create();
|
|
break;
|
|
}
|
|
case Provider.SHA512:
|
|
{
|
|
_Hash = SHA512.Create();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
_Hash = SHA256.Create(); // Default to SHA256 for unknown providers
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the previously calculated hash
|
|
/// </summary>
|
|
internal Data Value
|
|
{
|
|
get
|
|
{
|
|
return _HashValue;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates hash on a stream of arbitrary length
|
|
/// </summary>
|
|
internal Data Calculate(ref Stream s)
|
|
{
|
|
_HashValue.Bytes = _Hash.ComputeHash(s);
|
|
return _HashValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates hash for fixed length <see cref="Data"/>
|
|
/// </summary>
|
|
internal Data Calculate(Data d)
|
|
{
|
|
return CalculatePrivate(d.Bytes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates hash for a string with a prefixed salt value.
|
|
/// A "salt" is random data prefixed to every hashed value to prevent
|
|
/// common dictionary attacks.
|
|
/// </summary>
|
|
internal Data Calculate(Data d, Data salt)
|
|
{
|
|
var nb = new byte[(d.Bytes.Length + salt.Bytes.Length)];
|
|
salt.Bytes.CopyTo(nb, 0);
|
|
d.Bytes.CopyTo(nb, salt.Bytes.Length);
|
|
return CalculatePrivate(nb);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates hash for an array of bytes
|
|
/// </summary>
|
|
private Data CalculatePrivate(byte[] b)
|
|
{
|
|
_HashValue.Bytes = _Hash.ComputeHash(b);
|
|
return _HashValue;
|
|
}
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region Symmetric
|
|
|
|
/// <summary>
|
|
/// Symmetric encryption uses a single key to encrypt and decrypt.
|
|
/// Both parties (encryptor and decryptor) must share the same secret key.
|
|
/// </summary>
|
|
internal class SymmetricEncryptor
|
|
{
|
|
|
|
private const string _DefaultIntializationVector = "%1Az=-@qT";
|
|
private const int _BufferSize = 2048;
|
|
|
|
internal enum Provider
|
|
{
|
|
/// <summary>
|
|
/// Advanced Encryption Standard (AES) provider
|
|
/// </summary>
|
|
AES
|
|
}
|
|
|
|
private Data _data;
|
|
private Data _key;
|
|
private Data _iv;
|
|
private SymmetricAlgorithm _crypto;
|
|
private byte[] _EncryptedBytes;
|
|
private bool _UseDefaultInitializationVector;
|
|
|
|
private SymmetricEncryptor()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiates a new symmetric encryption object using the specified provider.
|
|
/// </summary>
|
|
internal SymmetricEncryptor(Provider provider, bool useDefaultInitializationVector = true)
|
|
{
|
|
_crypto = Aes.Create(); // Always use AES as it's the most secure option
|
|
|
|
// -- make sure key and IV are always set, no matter what
|
|
Key = RandomKey();
|
|
if (useDefaultInitializationVector)
|
|
{
|
|
IntializationVector = new Data(_DefaultIntializationVector);
|
|
}
|
|
else
|
|
{
|
|
IntializationVector = RandomInitializationVector();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Key size in bytes. We use the default key size for any given provider; if you
|
|
/// want to force a specific key size, set this property
|
|
/// </summary>
|
|
internal int KeySizeBytes
|
|
{
|
|
get
|
|
{
|
|
return _crypto.KeySize / 8;
|
|
}
|
|
set
|
|
{
|
|
_crypto.KeySize = value * 8;
|
|
_key.MaxBytes = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Key size in bits. We use the default key size for any given provider; if you
|
|
/// want to force a specific key size, set this property
|
|
/// </summary>
|
|
internal int KeySizeBits
|
|
{
|
|
get
|
|
{
|
|
return _crypto.KeySize;
|
|
}
|
|
set
|
|
{
|
|
_crypto.KeySize = value;
|
|
_key.MaxBits = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The key used to encrypt/decrypt data
|
|
/// </summary>
|
|
internal Data Key
|
|
{
|
|
get
|
|
{
|
|
return _key;
|
|
}
|
|
set
|
|
{
|
|
_key = value;
|
|
_key.MaxBytes = _crypto.LegalKeySizes[0].MaxSize / 8;
|
|
_key.MinBytes = _crypto.LegalKeySizes[0].MinSize / 8;
|
|
_key.StepBytes = _crypto.LegalKeySizes[0].SkipSize / 8;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Using the default Cipher Block Chaining (CBC) mode, all data blocks are processed using
|
|
/// the value derived from the previous block; the first data block has no previous data block
|
|
/// to use, so it needs an InitializationVector to feed the first block
|
|
/// </summary>
|
|
internal Data IntializationVector
|
|
{
|
|
get
|
|
{
|
|
return _iv;
|
|
}
|
|
set
|
|
{
|
|
_iv = value;
|
|
_iv.MaxBytes = _crypto.BlockSize / 8;
|
|
_iv.MinBytes = _crypto.BlockSize / 8;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// generates a random Initialization Vector, if one was not provided
|
|
/// </summary>
|
|
internal Data RandomInitializationVector()
|
|
{
|
|
_crypto.GenerateIV();
|
|
var d = new Data(_crypto.IV);
|
|
return d;
|
|
}
|
|
|
|
/// <summary>
|
|
/// generates a random Key, if one was not provided
|
|
/// </summary>
|
|
internal Data RandomKey()
|
|
{
|
|
_crypto.GenerateKey();
|
|
var d = new Data(_crypto.Key);
|
|
return d;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that _crypto object has valid Key and IV
|
|
/// prior to any attempt to encrypt/decrypt anything
|
|
/// </summary>
|
|
private void ValidateKeyAndIv(bool isEncrypting)
|
|
{
|
|
if (_key.IsEmpty)
|
|
{
|
|
if (isEncrypting)
|
|
{
|
|
_key = RandomKey();
|
|
}
|
|
else
|
|
{
|
|
throw new CryptographicException("No key was provided for the decryption operation!");
|
|
}
|
|
}
|
|
if (_iv.IsEmpty)
|
|
{
|
|
if (isEncrypting)
|
|
{
|
|
_iv = RandomInitializationVector();
|
|
}
|
|
else
|
|
{
|
|
throw new CryptographicException("No initialization vector was provided for the decryption operation!");
|
|
}
|
|
}
|
|
try
|
|
{
|
|
_crypto.Key = _key.Bytes;
|
|
_crypto.IV = _iv.Bytes;
|
|
}
|
|
catch (CryptographicException ex)
|
|
{
|
|
throw new CryptographicException("Invalid key or initialization vector.", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts the specified Data using provided key
|
|
/// </summary>
|
|
internal Data Encrypt(Data d, Data key)
|
|
{
|
|
Key = key;
|
|
return Encrypt(d);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts the specified Data using preset key and preset initialization vector
|
|
/// </summary>
|
|
internal Data Encrypt(Data d)
|
|
{
|
|
var ms = new MemoryStream();
|
|
|
|
ValidateKeyAndIv(true);
|
|
|
|
var cs = new CryptoStream(ms, _crypto.CreateEncryptor(), CryptoStreamMode.Write);
|
|
cs.Write(d.Bytes, 0, d.Bytes.Length);
|
|
cs.Close();
|
|
ms.Close();
|
|
|
|
return new Data(ms.ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts the stream to memory using provided key and provided initialization vector
|
|
/// </summary>
|
|
internal Data Encrypt(Stream s, Data key, Data iv)
|
|
{
|
|
IntializationVector = iv;
|
|
Key = key;
|
|
return Encrypt(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts the stream to memory using specified key
|
|
/// </summary>
|
|
internal Data Encrypt(Stream s, Data key)
|
|
{
|
|
Key = key;
|
|
return Encrypt(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts the specified stream to memory using preset key and preset initialization vector
|
|
/// </summary>
|
|
internal Data Encrypt(Stream s)
|
|
{
|
|
var ms = new MemoryStream();
|
|
var b = new byte[2049];
|
|
int i;
|
|
|
|
ValidateKeyAndIv(true);
|
|
|
|
var cs = new CryptoStream(ms, _crypto.CreateEncryptor(), CryptoStreamMode.Write);
|
|
i = s.Read(b, 0, _BufferSize);
|
|
while (i > 0)
|
|
{
|
|
cs.Write(b, 0, i);
|
|
i = s.Read(b, 0, _BufferSize);
|
|
}
|
|
|
|
cs.Close();
|
|
ms.Close();
|
|
|
|
return new Data(ms.ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypts the specified data using provided key and preset initialization vector
|
|
/// </summary>
|
|
internal Data Decrypt(Data encryptedData, Data key)
|
|
{
|
|
Key = key;
|
|
return Decrypt(encryptedData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypts the specified stream using provided key and preset initialization vector
|
|
/// </summary>
|
|
internal Data Decrypt(Stream encryptedStream, Data key)
|
|
{
|
|
Key = key;
|
|
return Decrypt(encryptedStream);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypts the specified stream using preset key and preset initialization vector
|
|
/// </summary>
|
|
internal Data Decrypt(Stream encryptedStream)
|
|
{
|
|
var ms = new MemoryStream();
|
|
var b = new byte[2049];
|
|
|
|
ValidateKeyAndIv(false);
|
|
var cs = new CryptoStream(encryptedStream, _crypto.CreateDecryptor(), CryptoStreamMode.Read);
|
|
|
|
int i;
|
|
i = cs.Read(b, 0, _BufferSize);
|
|
|
|
while (i > 0)
|
|
{
|
|
ms.Write(b, 0, i);
|
|
i = cs.Read(b, 0, _BufferSize);
|
|
}
|
|
cs.Close();
|
|
ms.Close();
|
|
|
|
return new Data(ms.ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypts the specified data using preset key and preset initialization vector
|
|
/// </summary>
|
|
internal Data Decrypt(Data encryptedData)
|
|
{
|
|
using (var ms = new MemoryStream(encryptedData.Bytes, 0, encryptedData.Bytes.Length))
|
|
{
|
|
ValidateKeyAndIv(false);
|
|
using (var cs = new CryptoStream(ms, _crypto.CreateDecryptor(), CryptoStreamMode.Read))
|
|
{
|
|
using (var outputMs = new MemoryStream())
|
|
{
|
|
try
|
|
{
|
|
cs.CopyTo(outputMs);
|
|
return new Data(outputMs.ToArray());
|
|
}
|
|
catch (CryptographicException ex)
|
|
{
|
|
throw new CryptographicException("Unable to decrypt data. The provided key may be invalid.", ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Data
|
|
|
|
/// <summary>
|
|
/// represents Hex, Byte, Base64, or String data to encrypt/decrypt;
|
|
/// use the .Text property to set/get a string representation
|
|
/// use the .Hex property to set/get a string-based Hexadecimal representation
|
|
/// </summary>
|
|
internal class Data
|
|
{
|
|
private byte[] _b = null;
|
|
private int _MaxBytes = 0;
|
|
private int _MinBytes = 0;
|
|
private int _StepBytes = 0;
|
|
|
|
/// <summary>
|
|
/// Determines the default text encoding for this Data instance
|
|
/// </summary>
|
|
private Encoding _encoding = Encoding.UTF8;
|
|
|
|
/// <summary>
|
|
/// Creates new, empty encryption data
|
|
/// </summary>
|
|
internal Data()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates new encryption data with the specified byte array
|
|
/// </summary>
|
|
internal Data(byte[] b)
|
|
{
|
|
_b = b;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates new encryption data with the specified string;
|
|
/// will be converted to byte array using UTF8 encoding
|
|
/// </summary>
|
|
internal Data(string s)
|
|
{
|
|
if (s is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(s));
|
|
}
|
|
Text = s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates new encryption data using the specified string and the
|
|
/// specified encoding to convert the string to a byte array.
|
|
/// </summary>
|
|
internal Data(string s, Encoding encoding)
|
|
{
|
|
if (s is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(s));
|
|
}
|
|
if (encoding is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(encoding));
|
|
}
|
|
_encoding = encoding;
|
|
Text = s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// returns true if no data is present
|
|
/// </summary>
|
|
internal bool IsEmpty
|
|
{
|
|
get
|
|
{
|
|
if (_b is null)
|
|
{
|
|
return true;
|
|
}
|
|
if (_b.Length == 0)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// allowed step interval, in bytes, for this data; if 0, no limit
|
|
/// </summary>
|
|
internal int StepBytes
|
|
{
|
|
get
|
|
{
|
|
return _StepBytes;
|
|
}
|
|
set
|
|
{
|
|
_StepBytes = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// allowed step interval, in bits, for this data; if 0, no limit
|
|
/// </summary>
|
|
internal int StepBits
|
|
{
|
|
get
|
|
{
|
|
return _StepBytes * 8;
|
|
}
|
|
set
|
|
{
|
|
_StepBytes = value / 8;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// minimum number of bytes allowed for this data; if 0, no limit
|
|
/// </summary>
|
|
internal int MinBytes
|
|
{
|
|
get
|
|
{
|
|
return _MinBytes;
|
|
}
|
|
set
|
|
{
|
|
_MinBytes = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// minimum number of bits allowed for this data; if 0, no limit
|
|
/// </summary>
|
|
internal int MinBits
|
|
{
|
|
get
|
|
{
|
|
return _MinBytes * 8;
|
|
}
|
|
set
|
|
{
|
|
_MinBytes = value / 8;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// maximum number of bytes allowed for this data; if 0, no limit
|
|
/// </summary>
|
|
internal int MaxBytes
|
|
{
|
|
get
|
|
{
|
|
return _MaxBytes;
|
|
}
|
|
set
|
|
{
|
|
_MaxBytes = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// maximum number of bits allowed for this data; if 0, no limit
|
|
/// </summary>
|
|
internal int MaxBits
|
|
{
|
|
get
|
|
{
|
|
return _MaxBytes * 8;
|
|
}
|
|
set
|
|
{
|
|
_MaxBytes = value / 8;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the byte representation of the data;
|
|
/// This will be padded to MinBytes and trimmed to MaxBytes as necessary!
|
|
/// </summary>
|
|
internal byte[] Bytes
|
|
{
|
|
get
|
|
{
|
|
if (_b is null)
|
|
{
|
|
return Array.Empty<byte>();
|
|
}
|
|
|
|
if (_MaxBytes > 0 && _b.Length > _MaxBytes)
|
|
{
|
|
var b = new byte[_MaxBytes];
|
|
Array.Copy(_b, b, b.Length);
|
|
_b = b;
|
|
}
|
|
|
|
if (_MinBytes > 0 && _b.Length < _MinBytes)
|
|
{
|
|
var b = new byte[_MinBytes];
|
|
Array.Copy(_b, b, _b.Length);
|
|
_b = b;
|
|
}
|
|
|
|
return _b;
|
|
}
|
|
set
|
|
{
|
|
_b = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets or returns text representation of bytes using UTF8 encoding
|
|
/// </summary>
|
|
internal string Text
|
|
{
|
|
get
|
|
{
|
|
if (_b is null || _b.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
try
|
|
{
|
|
return _encoding.GetString(_b);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// If there's an encoding error, try to salvage what we can
|
|
int i = Array.IndexOf(_b, (byte)0);
|
|
if (i >= 0)
|
|
{
|
|
return _encoding.GetString(_b, 0, i);
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
if (value is null)
|
|
{
|
|
_b = Array.Empty<byte>();
|
|
}
|
|
else
|
|
{
|
|
_b = _encoding.GetBytes(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets or returns Hex string representation of this data
|
|
/// </summary>
|
|
internal string Hex
|
|
{
|
|
get
|
|
{
|
|
return Utils.ToHex(_b);
|
|
}
|
|
set
|
|
{
|
|
_b = Utils.FromHex(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets or returns Base64 string representation of this data
|
|
/// </summary>
|
|
internal string Base64
|
|
{
|
|
get
|
|
{
|
|
return Utils.ToBase64(_b);
|
|
}
|
|
set
|
|
{
|
|
_b = Utils.FromBase64(value);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utils
|
|
|
|
/// <summary>
|
|
/// Friend class for shared utility methods used by multiple Encryption classes
|
|
/// </summary>
|
|
internal class Utils
|
|
{
|
|
|
|
/// <summary>
|
|
/// converts an array of bytes to a string Hex representation
|
|
/// </summary>
|
|
internal static string ToHex(byte[] ba)
|
|
{
|
|
if (ba is null || ba.Length == 0)
|
|
{
|
|
return "";
|
|
}
|
|
const string HexFormat = "{0:X2}";
|
|
var sb = new StringBuilder();
|
|
foreach (byte b in ba)
|
|
sb.Append(string.Format(HexFormat, b));
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// converts from a string Hex representation to an array of bytes
|
|
/// </summary>
|
|
internal static byte[] FromHex(string hexEncoded)
|
|
{
|
|
if (hexEncoded is null || hexEncoded.Length == 0)
|
|
{
|
|
return null;
|
|
}
|
|
try
|
|
{
|
|
int l = Convert.ToInt32(hexEncoded.Length / 2d);
|
|
var b = new byte[l];
|
|
for (int i = 0, loopTo = l - 1; i <= loopTo; i++)
|
|
b[i] = Convert.ToByte(hexEncoded.Substring(i * 2, 2), 16);
|
|
return b;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new FormatException("The provided string does not appear to be Hex encoded:" + Environment.NewLine + hexEncoded + Environment.NewLine, ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// converts from a string Base64 representation to an array of bytes
|
|
/// </summary>
|
|
internal static byte[] FromBase64(string base64Encoded)
|
|
{
|
|
if (base64Encoded is null || base64Encoded.Length == 0)
|
|
{
|
|
return null;
|
|
}
|
|
try
|
|
{
|
|
return Convert.FromBase64String(base64Encoded);
|
|
}
|
|
catch (FormatException ex)
|
|
{
|
|
throw new FormatException("The provided string does not appear to be Base64 encoded:" + Environment.NewLine + base64Encoded + Environment.NewLine, ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// converts from an array of bytes to a string Base64 representation
|
|
/// </summary>
|
|
internal static string ToBase64(byte[] b)
|
|
{
|
|
if (b is null || b.Length == 0)
|
|
{
|
|
return "";
|
|
}
|
|
return Convert.ToBase64String(b);
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
} |