Option Explicit On
Option Strict On

Imports System
Imports System.IO
Imports System.Text
Imports System.Security
Imports System.Security.Cryptography
Imports System.Runtime.InteropServices
Imports SSCrypto

Imports SSCommonLib.Text

Namespace Security

  Public Class Security

#Region "Const & Variable"
    Public Const COMP_KEY As String = "SSViewer@COMP"
    Public Const COMP_KEY_HASH As String = "SHA1"
    Public Const COMP_KEY_ITERATION As Integer = 2
    Public Const COMP_IV As String = "@COMP@20112031"
#End Region

#Region "SHA1 Hash"

    ''' <summary>
    ''' Generates a hash for the given plain text value and returns a base64-encoded result.
    ''' </summary>
    ''' <param name="plainText">Plaintext value to be hashed.The function does not check whether this parameter is null.</param>
    ''' <param name="saltBytes">Salt bytes. This parameter can be null, in which case a random salt value will be generated.</param>
    ''' <returns>Hash value formatted as a base64-encoded string.</returns>
    ''' <remarks></remarks>
    Public Shared Function ComputeSHA1Hash(ByVal plainText As String, Optional ByVal saltBytes() As Byte = Nothing) As String
      If (saltBytes Is Nothing) Then

        ' Define min and max salt sizes.
        Dim minSaltSize As Integer
        Dim maxSaltSize As Integer

        minSaltSize = 4
        maxSaltSize = 8

        ' Generate a random number for the size of the salt.
        Dim random As Random
        random = New Random()

        Dim saltSize As Integer
        saltSize = random.Next(minSaltSize, maxSaltSize)

        ' Allocate a byte array, which will hold the salt.
        saltBytes = New Byte(saltSize - 1) {}

        ' Initialize a random number generator.
        Dim rng As RNGCryptoServiceProvider
        rng = New RNGCryptoServiceProvider()

        ' Fill the salt with cryptographically strong byte values.
        rng.GetNonZeroBytes(saltBytes)
      End If

      ' Convert plain text into a byte array.
      Dim plainTextBytes As Byte()
      plainTextBytes = Encoding.ASCII.GetBytes(plainText)

      ' Allocate array, which will hold plain text and salt.
      Dim plainTextWithSaltBytes() As Byte = New Byte(plainTextBytes.Length + saltBytes.Length - 1) {}
      Dim I As Integer
      ' Copy plain text bytes into resulting array.
      For I = 0 To plainTextBytes.Length - 1
        plainTextWithSaltBytes(I) = plainTextBytes(I)
      Next I

      ' Append salt bytes to the resulting array.
      For I = 0 To saltBytes.Length - 1
        plainTextWithSaltBytes(plainTextBytes.Length + I) = saltBytes(I)
      Next I

      ' Because we support multiple hashing algorithms, we must define
      ' hash object as a common (abstract) base class. We will specify the
      ' actual hashing algorithm class later during object creation.
      Dim hash As HashAlgorithm = New SHA1Managed()

      ' Compute hash value of our plain text with appended salt.
      Dim hashBytes As Byte() = hash.ComputeHash(plainTextWithSaltBytes)

      ' Create array which will hold hash and original salt bytes.
      Dim hashWithSaltBytes() As Byte = New Byte(hashBytes.Length + saltBytes.Length - 1) {}

      ' Copy hash bytes into resulting array.
      For I = 0 To hashBytes.Length - 1
        hashWithSaltBytes(I) = hashBytes(I)
      Next I

      ' Append salt bytes to the result.
      For I = 0 To saltBytes.Length - 1
        hashWithSaltBytes(hashBytes.Length + I) = saltBytes(I)
      Next I

      ' Convert result into a base64-encoded string.
      Dim hashValue As String
      hashValue = Base64Converter.ConvertToBase64(hashWithSaltBytes)

      ' Return the result.
      Return hashValue
    End Function

    ''' <summary>
    ''' Compares a hash of the specified plain text value to a given hash  value. Plain text is hashed with the same salt value as the original hash.
    ''' </summary>
    ''' <param name="plainText">Plain text to be verified against the specified hash. The function does not check whether this parameter is null.</param>
    ''' <param name="hashValue">Base64-encoded hash value produced by ComputeHash function. This value includes the original salt appended to it.</param>
    ''' <returns>Computed hash mathes the specified hash is true, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Shared Function VerifySHA1Hash(ByVal plainText As String, ByVal hashValue As String) As Boolean

      ' Convert base64-encoded hash value into a byte array.
      Dim hashWithSaltBytes As Byte() = Base64Converter.ConvertBytesFromBase64(hashValue)

      ' We must know size of hash (without salt).
      Dim hashSizeInBits As Integer
      Dim hashSizeInBytes As Integer

      hashSizeInBits = 160

      ' Convert size of hash from bits to bytes.
      hashSizeInBytes = CInt(hashSizeInBits / 8)

      ' Make sure that the specified hash value is long enough.
      If (hashWithSaltBytes.Length < hashSizeInBytes) Then
        VerifySHA1Hash = False
      End If

      ' Allocate array to hold original salt bytes retrieved from hash.
      Dim saltBytes() As Byte = New Byte(hashWithSaltBytes.Length - hashSizeInBytes - 1) {}

      ' Copy salt from the end of the hash to the new array.
      For I As Integer = 0 To saltBytes.Length - 1
        saltBytes(I) = hashWithSaltBytes(hashSizeInBytes + I)
      Next I

      ' Compute a new hash string.
      Dim expectedHashString As String = ComputeSHA1Hash(plainText, saltBytes)

      ' If the computed hash matches the specified hash,
      ' the plain text value must be correct.
      Return (hashValue = expectedHashString)
    End Function

#End Region

#Region "AES256 Encrypt/Decypt"

    ''' <summary>
    ''' Encrypts specified plaintext using Rijndael(AES) symmetric key algorithm and returns a base64-encoded result.
    ''' </summary>
    ''' <returns>Encrypted value formatted as a base64-encoded string.</returns>
    ''' <remarks></remarks>
    Public Shared Function AES256Encrypt(ByVal plainText As String, Optional ByVal key As String = Nothing, Optional ByVal IV As String = Nothing, Optional ByVal salt As String = Nothing) As String

      ' Convert our plaintext into a byte array.
      Dim plainTextBytes As Byte() = Encoding.ASCII.GetBytes(plainText)

      Return AES256Encrypt(plainTextBytes, key, IV, salt)
    End Function

    Public Shared Function AES256Encrypt(ByVal plainTextBytes As Byte(), Optional ByVal key As String = Nothing, Optional ByVal IV As String = Nothing, Optional ByVal salt As String = Nothing) As String

      Dim CryptKey As System.Security.Cryptography.PasswordDeriveBytes

      If salt Is Nothing Then
        CryptKey = GetCryptKey(key)
      Else
        CryptKey = GetCryptKey(key, Encoding.ASCII.GetBytes(salt))
      End If

      ' Use the password to generate pseudo-random bytes for the encryption
      ' key. Specify the size of the key in bytes (instead of bits).
      Dim keyBytes As Byte() = CryptKey.GetBytes(CInt(256 / 8))

      ' Create uninitialized Rijndael encryption object.
      Dim symmetricKey As New RijndaelManaged()

      ' It is reasonable to set encryption mode to Cipher Block Chaining
      ' (CBC). Use default options for other symmetric key parameters.
      symmetricKey.Mode = CipherMode.CBC

      ' Generate encryptor from the existing key bytes and initialization 
      ' vector. Key size will be defined based on the number of the key 
      ' bytes.
      If String.IsNullOrEmpty(IV) Then
        IV = COMP_KEY
      Else
        If IV.Length < 16 Then
          IV = IV & COMP_KEY.Substring(0, 16 - IV.Length)
        ElseIf IV.Length > 16 Then
          IV = IV.Substring(0, 16)
        End If
      End If
      Dim encryptor As ICryptoTransform = symmetricKey.CreateEncryptor(keyBytes, Encoding.ASCII.GetBytes(IV))

      ' Define memory stream which will be used to hold encrypted data.
      Dim memoryStream As New MemoryStream()

      ' Define cryptographic stream (always use Write mode for encryption).
      Dim cryptoStream As New CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)
      ' Start encrypting.
      cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)

      ' Finish encrypting.
      cryptoStream.FlushFinalBlock()

      ' Convert our encrypted data from a memory stream into a byte array.
      Dim cipherTextBytes As Byte() = memoryStream.ToArray()

      ' Close both streams.
      memoryStream.Close()
      cryptoStream.Close()

      ' Convert encrypted data into a base64-encoded string.
      Dim cipherText As String
      cipherText = Base64Converter.ConvertToBase64(cipherTextBytes)

      ' Return encrypted string.
      Return cipherText
    End Function


    ''' <summary>
    ''' Decrypts specified ciphertext using Rijndael(AES) symmetric key algorithm.
    ''' </summary>
    ''' <returns>Decrypted string value.</returns>
    ''' <remarks></remarks>
    Public Shared Function AES256Decrypt(ByVal cipherText As String, Optional ByVal key As String = Nothing, Optional ByVal IV As String = Nothing, Optional ByVal salt As String = Nothing) As String

      ' Convert our ciphertext into a byte array.
      Dim cipherTextBytes As Byte() = Base64Converter.ConvertBytesFromBase64(cipherText)

      Dim CryptKey As System.Security.Cryptography.PasswordDeriveBytes

      If salt Is Nothing Then
        CryptKey = GetCryptKey(key)
      Else
        CryptKey = GetCryptKey(Nothing, Encoding.ASCII.GetBytes(salt))
      End If

      ' Use the password to generate pseudo-random bytes for the encryption
      ' key. Specify the size of the key in bytes (instead of bits).
      Dim keyBytes As Byte() = CryptKey.GetBytes(CInt(256 / 8))

      ' Create uninitialized Rijndael encryption object.
      Dim symmetricKey As New RijndaelManaged()

      ' It is reasonable to set encryption mode to Cipher Block Chaining
      ' (CBC). Use default options for other symmetric key parameters.
      symmetricKey.Mode = CipherMode.CBC

      ' Generate decryptor from the existing key bytes and initialization 
      ' vector. Key size will be defined based on the number of the key 
      ' bytes.

      If String.IsNullOrEmpty(IV) Then
        IV = COMP_KEY
      Else
        If IV.Length < 16 Then
          IV = IV & COMP_KEY.Substring(0, 16 - IV.Length)
        ElseIf IV.Length > 16 Then
          IV = IV.Substring(0, 16)
        End If
      End If

      Dim decryptor As ICryptoTransform = symmetricKey.CreateDecryptor(keyBytes, Encoding.ASCII.GetBytes(COMP_KEY))

      ' Define memory stream which will be used to hold encrypted data.
      Dim memoryStream As New MemoryStream(cipherTextBytes)

      ' Define memory stream which will be used to hold encrypted data.
      Dim cryptoStream As New CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)

      ' Since at this point we don't know what the size of decrypted data
      ' will be, allocate the buffer long enough to hold ciphertext;
      ' plaintext is never longer than ciphertext.
      Dim plainTextBytes As Byte()
      ReDim plainTextBytes(cipherTextBytes.Length)

      ' Start decrypting.
      Dim decryptedByteCount As Integer = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)

      ' Close both streams.
      memoryStream.Close()
      cryptoStream.Close()

      ' Convert decrypted data into a string. 
      ' Let us assume that the original plaintext string was UTF8-encoded.
      Dim plainText As String = Encoding.ASCII.GetString(plainTextBytes, 0, decryptedByteCount)

      ' Return decrypted string.
      Return plainText
    End Function

    Private Shared Function GetCryptKey(Optional ByVal key As String = Nothing, Optional ByVal salt As Byte() = Nothing) As System.Security.Cryptography.PasswordDeriveBytes
      If String.IsNullOrEmpty(key) Then
        key = COMP_KEY
      Else
        If key.Length < 16 Then key = key & COMP_KEY.Substring(0, 16 - key.Length)
      End If

      If salt Is Nothing Then salt = System.Text.Encoding.ASCII.GetBytes(COMP_IV)
      Return New System.Security.Cryptography.PasswordDeriveBytes(key, salt, COMP_KEY_HASH, COMP_KEY_ITERATION)
    End Function

#End Region

#Region "TripleDES Decyption"

        Public Shared Function TripleDESDecrypt(ByVal cipertext As String) As String

            Try
                Dim keyFileLocation As String = ConfigReaderHelper.GetSetting("KeyFile")
                Dim cm As SSCrypto.CryptoModule = New SSCrypto.CryptoModule()
                Dim keyByte As Byte() = File.ReadAllBytes(keyFileLocation)
                Dim key As String = cm.UnMassageKey(keyByte)
                Dim data As Byte() = Base64Converter.ConvertBytesFromBase64(cipertext)
                Dim result As Byte() = cm.DecryptTripleDES(key, data)
                Return Encoding.UTF8.GetString(result)

            Catch ex As Exception
                'LogFile.WriteError(">Viewer Unable to Decrypt data. Except = " + ex.ToString())
                Return String.Empty
            End Try

        End Function

#End Region

  End Class

End Namespace