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

681 lines
23 KiB
VB.net

Imports System.IO
Imports System.Security.Cryptography
Imports System.Text
Namespace 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>
Friend 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
End Enum
Private _Hash As HashAlgorithm
Private _HashValue As New Data
Friend Sub New()
End Sub
''' <summary>
''' Instantiate a new hash of the specified type
''' </summary>
Friend Sub New(ByVal p As Provider)
Select Case p
Case Provider.SHA256
_Hash = SHA256.Create()
Case Provider.SHA384
_Hash = SHA384.Create()
Case Provider.SHA512
_Hash = SHA512.Create()
Case Else
_Hash = SHA256.Create() ' Default to SHA256 for unknown providers
End Select
End Sub
''' <summary>
''' Returns the previously calculated hash
''' </summary>
Friend ReadOnly Property Value() As Data
Get
Return _HashValue
End Get
End Property
''' <summary>
''' Calculates hash on a stream of arbitrary length
''' </summary>
Friend Function Calculate(ByRef s As System.IO.Stream) As Data
_HashValue.Bytes = _Hash.ComputeHash(s)
Return _HashValue
End Function
''' <summary>
''' Calculates hash for fixed length <see cref="Data"/>
''' </summary>
Friend Function Calculate(ByVal d As Data) As Data
Return CalculatePrivate(d.Bytes)
End Function
''' <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>
Friend Function Calculate(ByVal d As Data, ByVal salt As Data) As Data
Dim nb(d.Bytes.Length + salt.Bytes.Length - 1) As Byte
salt.Bytes.CopyTo(nb, 0)
d.Bytes.CopyTo(nb, salt.Bytes.Length)
Return CalculatePrivate(nb)
End Function
''' <summary>
''' Calculates hash for an array of bytes
''' </summary>
Private Function CalculatePrivate(ByVal b() As Byte) As Data
_HashValue.Bytes = _Hash.ComputeHash(b)
Return _HashValue
End Function
End Class
#End Region
#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>
Friend Class SymmetricEncryptor
Private Const _DefaultIntializationVector As String = "%1Az=-@qT"
Private Const _BufferSize As Integer = 2048
Friend Enum Provider
''' <summary>
''' Advanced Encryption Standard (AES) provider
''' </summary>
AES
End Enum
Private _data As Data
Private _key As Data
Private _iv As Data
Private _crypto As SymmetricAlgorithm
Private _EncryptedBytes As Byte()
Private _UseDefaultInitializationVector As Boolean
Private Sub New()
End Sub
''' <summary>
''' Instantiates a new symmetric encryption object using the specified provider.
''' </summary>
Friend Sub New(ByVal provider As Provider, Optional ByVal useDefaultInitializationVector As Boolean = 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
Me.Key = RandomKey()
If useDefaultInitializationVector Then
Me.IntializationVector = New Data(_DefaultIntializationVector)
Else
Me.IntializationVector = RandomInitializationVector()
End If
End Sub
''' <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>
Friend Property KeySizeBytes() As Integer
Get
Return _crypto.KeySize \ 8
End Get
Set(ByVal Value As Integer)
_crypto.KeySize = Value * 8
_key.MaxBytes = Value
End Set
End Property
''' <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>
Friend Property KeySizeBits() As Integer
Get
Return _crypto.KeySize
End Get
Set(ByVal Value As Integer)
_crypto.KeySize = Value
_key.MaxBits = Value
End Set
End Property
''' <summary>
''' The key used to encrypt/decrypt data
''' </summary>
Friend Property Key() As Data
Get
Return _key
End Get
Set(ByVal Value As Data)
_key = Value
_key.MaxBytes = _crypto.LegalKeySizes(0).MaxSize \ 8
_key.MinBytes = _crypto.LegalKeySizes(0).MinSize \ 8
_key.StepBytes = _crypto.LegalKeySizes(0).SkipSize \ 8
End Set
End Property
''' <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>
Friend Property IntializationVector() As Data
Get
Return _iv
End Get
Set(ByVal Value As Data)
_iv = Value
_iv.MaxBytes = _crypto.BlockSize \ 8
_iv.MinBytes = _crypto.BlockSize \ 8
End Set
End Property
''' <summary>
''' generates a random Initialization Vector, if one was not provided
''' </summary>
Friend Function RandomInitializationVector() As Data
_crypto.GenerateIV()
Dim d As New Data(_crypto.IV)
Return d
End Function
''' <summary>
''' generates a random Key, if one was not provided
''' </summary>
Friend Function RandomKey() As Data
_crypto.GenerateKey()
Dim d As New Data(_crypto.Key)
Return d
End Function
''' <summary>
''' Ensures that _crypto object has valid Key and IV
''' prior to any attempt to encrypt/decrypt anything
''' </summary>
Private Sub ValidateKeyAndIv(ByVal isEncrypting As Boolean)
If _key.IsEmpty Then
If isEncrypting Then
_key = RandomKey()
Else
Throw New CryptographicException("No key was provided for the decryption operation!")
End If
End If
If _iv.IsEmpty Then
If isEncrypting Then
_iv = RandomInitializationVector()
Else
Throw New CryptographicException("No initialization vector was provided for the decryption operation!")
End If
End If
Try
_crypto.Key = _key.Bytes
_crypto.IV = _iv.Bytes
Catch ex As CryptographicException
Throw New CryptographicException("Invalid key or initialization vector.", ex)
End Try
End Sub
''' <summary>
''' Encrypts the specified Data using provided key
''' </summary>
Friend Function Encrypt(ByVal d As Data, ByVal key As Data) As Data
Me.Key = key
Return Encrypt(d)
End Function
''' <summary>
''' Encrypts the specified Data using preset key and preset initialization vector
''' </summary>
Friend Function Encrypt(ByVal d As Data) As Data
Dim ms As New IO.MemoryStream
ValidateKeyAndIv(True)
Dim cs As New CryptoStream(ms, _crypto.CreateEncryptor(), CryptoStreamMode.Write)
cs.Write(d.Bytes, 0, d.Bytes.Length)
cs.Close()
ms.Close()
Return New Data(ms.ToArray)
End Function
''' <summary>
''' Encrypts the stream to memory using provided key and provided initialization vector
''' </summary>
Friend Function Encrypt(ByVal s As Stream, ByVal key As Data, ByVal iv As Data) As Data
Me.IntializationVector = iv
Me.Key = key
Return Encrypt(s)
End Function
''' <summary>
''' Encrypts the stream to memory using specified key
''' </summary>
Friend Function Encrypt(ByVal s As Stream, ByVal key As Data) As Data
Me.Key = key
Return Encrypt(s)
End Function
''' <summary>
''' Encrypts the specified stream to memory using preset key and preset initialization vector
''' </summary>
Friend Function Encrypt(ByVal s As Stream) As Data
Dim ms As New IO.MemoryStream
Dim b(_BufferSize) As Byte
Dim i As Integer
ValidateKeyAndIv(True)
Dim cs As New CryptoStream(ms, _crypto.CreateEncryptor(), CryptoStreamMode.Write)
i = s.Read(b, 0, _BufferSize)
Do While i > 0
cs.Write(b, 0, i)
i = s.Read(b, 0, _BufferSize)
Loop
cs.Close()
ms.Close()
Return New Data(ms.ToArray)
End Function
''' <summary>
''' Decrypts the specified data using provided key and preset initialization vector
''' </summary>
Friend Function Decrypt(ByVal encryptedData As Data, ByVal key As Data) As Data
Me.Key = key
Return Decrypt(encryptedData)
End Function
''' <summary>
''' Decrypts the specified stream using provided key and preset initialization vector
''' </summary>
Friend Function Decrypt(ByVal encryptedStream As Stream, ByVal key As Data) As Data
Me.Key = key
Return Decrypt(encryptedStream)
End Function
''' <summary>
''' Decrypts the specified stream using preset key and preset initialization vector
''' </summary>
Friend Function Decrypt(ByVal encryptedStream As Stream) As Data
Dim ms As New System.IO.MemoryStream
Dim b(_BufferSize) As Byte
ValidateKeyAndIv(False)
Dim cs As New CryptoStream(encryptedStream,
_crypto.CreateDecryptor(), CryptoStreamMode.Read)
Dim i As Integer
i = cs.Read(b, 0, _BufferSize)
Do While i > 0
ms.Write(b, 0, i)
i = cs.Read(b, 0, _BufferSize)
Loop
cs.Close()
ms.Close()
Return New Data(ms.ToArray)
End Function
''' <summary>
''' Decrypts the specified data using preset key and preset initialization vector
''' </summary>
Friend Function Decrypt(ByVal encryptedData As Data) As Data
Using ms As New System.IO.MemoryStream(encryptedData.Bytes, 0, encryptedData.Bytes.Length)
ValidateKeyAndIv(False)
Using cs As New CryptoStream(ms, _crypto.CreateDecryptor(), CryptoStreamMode.Read)
Using outputMs As New MemoryStream()
Try
cs.CopyTo(outputMs)
Return New Data(outputMs.ToArray())
Catch ex As CryptographicException
Throw New CryptographicException("Unable to decrypt data. The provided key may be invalid.", ex)
End Try
End Using
End Using
End Using
End Function
End Class
#End Region
#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>
Friend Class Data
Private _b As Byte() = Nothing
Private _MaxBytes As Integer = 0
Private _MinBytes As Integer = 0
Private _StepBytes As Integer = 0
''' <summary>
''' Determines the default text encoding for this Data instance
''' </summary>
Private _encoding As System.Text.Encoding = System.Text.Encoding.UTF8
''' <summary>
''' Creates new, empty encryption data
''' </summary>
Friend Sub New()
End Sub
''' <summary>
''' Creates new encryption data with the specified byte array
''' </summary>
Friend Sub New(ByVal b As Byte())
_b = b
End Sub
''' <summary>
''' Creates new encryption data with the specified string;
''' will be converted to byte array using UTF8 encoding
''' </summary>
Friend Sub New(ByVal s As String)
If s Is Nothing Then
Throw New ArgumentNullException(NameOf(s))
End If
Me.Text = s
End Sub
''' <summary>
''' Creates new encryption data using the specified string and the
''' specified encoding to convert the string to a byte array.
''' </summary>
Friend Sub New(ByVal s As String, ByVal encoding As System.Text.Encoding)
If s Is Nothing Then
Throw New ArgumentNullException(NameOf(s))
End If
If encoding Is Nothing Then
Throw New ArgumentNullException(NameOf(encoding))
End If
_encoding = encoding
Me.Text = s
End Sub
''' <summary>
''' returns true if no data is present
''' </summary>
Friend ReadOnly Property IsEmpty() As Boolean
Get
If _b Is Nothing Then
Return True
End If
If _b.Length = 0 Then
Return True
End If
Return False
End Get
End Property
''' <summary>
''' allowed step interval, in bytes, for this data; if 0, no limit
''' </summary>
Friend Property StepBytes() As Integer
Get
Return _StepBytes
End Get
Set(ByVal Value As Integer)
_StepBytes = Value
End Set
End Property
''' <summary>
''' allowed step interval, in bits, for this data; if 0, no limit
''' </summary>
Friend Property StepBits() As Integer
Get
Return _StepBytes * 8
End Get
Set(ByVal Value As Integer)
_StepBytes = Value \ 8
End Set
End Property
''' <summary>
''' minimum number of bytes allowed for this data; if 0, no limit
''' </summary>
Friend Property MinBytes() As Integer
Get
Return _MinBytes
End Get
Set(ByVal Value As Integer)
_MinBytes = Value
End Set
End Property
''' <summary>
''' minimum number of bits allowed for this data; if 0, no limit
''' </summary>
Friend Property MinBits() As Integer
Get
Return _MinBytes * 8
End Get
Set(ByVal Value As Integer)
_MinBytes = Value \ 8
End Set
End Property
''' <summary>
''' maximum number of bytes allowed for this data; if 0, no limit
''' </summary>
Friend Property MaxBytes() As Integer
Get
Return _MaxBytes
End Get
Set(ByVal Value As Integer)
_MaxBytes = Value
End Set
End Property
''' <summary>
''' maximum number of bits allowed for this data; if 0, no limit
''' </summary>
Friend Property MaxBits() As Integer
Get
Return _MaxBytes * 8
End Get
Set(ByVal Value As Integer)
_MaxBytes = Value \ 8
End Set
End Property
''' <summary>
''' Returns the byte representation of the data;
''' This will be padded to MinBytes and trimmed to MaxBytes as necessary!
''' </summary>
Friend Property Bytes() As Byte()
Get
If _b Is Nothing Then
Return Array.Empty(Of Byte)()
End If
If _MaxBytes > 0 AndAlso _b.Length > _MaxBytes Then
Dim b(_MaxBytes - 1) As Byte
Array.Copy(_b, b, b.Length)
_b = b
End If
If _MinBytes > 0 AndAlso _b.Length < _MinBytes Then
Dim b(_MinBytes - 1) As Byte
Array.Copy(_b, b, _b.Length)
_b = b
End If
Return _b
End Get
Set(ByVal Value As Byte())
_b = Value
End Set
End Property
''' <summary>
''' Sets or returns text representation of bytes using UTF8 encoding
''' </summary>
Friend Property Text() As String
Get
If _b Is Nothing OrElse _b.Length = 0 Then
Return String.Empty
End If
Try
Return _encoding.GetString(_b)
Catch ex As Exception
' If there's an encoding error, try to salvage what we can
Dim i As Integer = Array.IndexOf(_b, CType(0, Byte))
If i >= 0 Then
Return _encoding.GetString(_b, 0, i)
End If
Throw
End Try
End Get
Set(ByVal Value As String)
If Value Is Nothing Then
_b = Array.Empty(Of Byte)()
Else
_b = _encoding.GetBytes(Value)
End If
End Set
End Property
''' <summary>
''' Sets or returns Hex string representation of this data
''' </summary>
Friend Property Hex() As String
Get
Return Utils.ToHex(_b)
End Get
Set(ByVal Value As String)
_b = Utils.FromHex(Value)
End Set
End Property
''' <summary>
''' Sets or returns Base64 string representation of this data
''' </summary>
Friend Property Base64() As String
Get
Return Utils.ToBase64(_b)
End Get
Set(ByVal Value As String)
_b = Utils.FromBase64(Value)
End Set
End Property
End Class
#End Region
#Region " Utils "
''' <summary>
''' Friend class for shared utility methods used by multiple Encryption classes
''' </summary>
Friend Class Utils
''' <summary>
''' converts an array of bytes to a string Hex representation
''' </summary>
Friend Shared Function ToHex(ByVal ba() As Byte) As String
If ba Is Nothing OrElse ba.Length = 0 Then
Return ""
End If
Const HexFormat As String = "{0:X2}"
Dim sb As New StringBuilder
For Each b As Byte In ba
sb.Append(String.Format(HexFormat, b))
Next
Return sb.ToString
End Function
''' <summary>
''' converts from a string Hex representation to an array of bytes
''' </summary>
Friend Shared Function FromHex(ByVal hexEncoded As String) As Byte()
If hexEncoded Is Nothing OrElse hexEncoded.Length = 0 Then
Return Nothing
End If
Try
Dim l As Integer = Convert.ToInt32(hexEncoded.Length / 2)
Dim b(l - 1) As Byte
For i As Integer = 0 To l - 1
b(i) = Convert.ToByte(hexEncoded.Substring(i * 2, 2), 16)
Next
Return b
Catch ex As Exception
Throw New System.FormatException("The provided string does not appear to be Hex encoded:" &
Environment.NewLine & hexEncoded & Environment.NewLine, ex)
End Try
End Function
''' <summary>
''' converts from a string Base64 representation to an array of bytes
''' </summary>
Friend Shared Function FromBase64(ByVal base64Encoded As String) As Byte()
If base64Encoded Is Nothing OrElse base64Encoded.Length = 0 Then
Return Nothing
End If
Try
Return Convert.FromBase64String(base64Encoded)
Catch ex As System.FormatException
Throw New System.FormatException("The provided string does not appear to be Base64 encoded:" &
Environment.NewLine & base64Encoded & Environment.NewLine, ex)
End Try
End Function
''' <summary>
''' converts from an array of bytes to a string Base64 representation
''' </summary>
Friend Shared Function ToBase64(ByVal b() As Byte) As String
If b Is Nothing OrElse b.Length = 0 Then
Return ""
End If
Return Convert.ToBase64String(b)
End Function
End Class
#End Region
End Namespace