rc-migration-tests/vb-migration/Strata.Base.Internal/Security/Encryption.cs

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
}