﻿Imports System.Collections
Imports System.Collections.Generic
Imports System
Imports System.Text
Imports System.Security.Cryptography
Imports System.IO
Imports System.Linq

Namespace HashLibrary
    Public Module Hasher

        Private Const Keysize As Integer = 256
        Private Const DerivationIterations As Integer = 1000

        Function Encrypt(ByVal plainText As String, ByVal passPhrase As String) As String
            Dim saltStringBytes = Generate256BitsOfRandomEntropy()
            Dim ivStringBytes = Generate256BitsOfRandomEntropy()
            Dim plainTextBytes = Encoding.UTF8.GetBytes(plainText)
            Dim password = New Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)
            Dim keyBytes = password.GetBytes(Keysize / 8)

            Using symmetricKey = New RijndaelManaged()
                symmetricKey.BlockSize = 256
                symmetricKey.Mode = CipherMode.CBC
                symmetricKey.Padding = PaddingMode.PKCS7

                Using encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)

                    Using memoryStream = New MemoryStream()

                        Using cryptoStream = New CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)
                            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)
                            cryptoStream.FlushFinalBlock()
                            Dim cipherTextBytes = saltStringBytes
                            cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray()
                            cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray()
                            memoryStream.Close()
                            cryptoStream.Close()
                            password.Reset()
                            Return Convert.ToBase64String(cipherTextBytes)
                        End Using
                    End Using
                End Using
            End Using
        End Function

        Function Decrypt(ByVal cipherText As String, ByVal passPhrase As String) As String
            Dim cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText)
            Dim saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray()
            Dim ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray()
            Dim cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray()
            Dim password = New Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)
            Dim keyBytes = password.GetBytes(Keysize / 8)

            Using symmetricKey = New RijndaelManaged()
                symmetricKey.BlockSize = 256
                symmetricKey.Mode = CipherMode.CBC
                symmetricKey.Padding = PaddingMode.PKCS7

                Using decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)

                    Using memoryStream = New MemoryStream(cipherTextBytes)

                        Using cryptoStream = New CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)
                            Dim plainTextBytes = New Byte(cipherTextBytes.Length - 1) {}
                            Dim decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
                            memoryStream.Close()
                            cryptoStream.Close()
                            password.Reset()
                            Return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount)
                        End Using
                    End Using
                End Using
            End Using
        End Function

        Private Function Generate256BitsOfRandomEntropy() As Byte()
            Dim randomBytes = New Byte(31) {}
            Dim rngCsp = New RNGCryptoServiceProvider()
            rngCsp.GetBytes(randomBytes)
            Return randomBytes
        End Function

        Function CreateHash(ByVal plainText As String, ByVal saltBytes As Byte(), ByVal Optional hashAlgorithm As String = "SHA512") As String
            If (saltBytes Is Nothing) Then
                Dim minSaltSize As Integer
                Dim maxSaltSize As Integer
                minSaltSize = 4
                maxSaltSize = 8
                Dim random As System.Random
                random = New System.Random()
                Dim saltSize As Integer
                saltSize = random.[Next](minSaltSize, maxSaltSize)
                saltBytes = New Byte(saltSize - 1 + 1 - 1) {}
                Dim rng As RNGCryptoServiceProvider
                rng = New RNGCryptoServiceProvider()
                rng.GetNonZeroBytes(saltBytes)
            End If

            Dim plainTextBytes As Byte()
            plainTextBytes = Encoding.UTF8.GetBytes(plainText)
            Dim plainTextWithSaltBytes As Byte() = New Byte(plainTextBytes.Length + saltBytes.Length - 1 + 1 - 1) {}
            Dim I As Integer

            For I = 0 To plainTextBytes.Length - 1
                plainTextWithSaltBytes(I) = plainTextBytes(I)
            Next

            For I = 0 To saltBytes.Length - 1
                plainTextWithSaltBytes(plainTextBytes.Length + I) = saltBytes(I)
            Next

            Dim hash As HashAlgorithm
            If (hashAlgorithm Is Nothing) Then hashAlgorithm = ""

            Select Case hashAlgorithm.ToUpper()
                Case "SHA1"
                    hash = New SHA1Managed()
                    Exit Select
                Case "SHA256"
                    hash = New SHA256Managed()
                    Exit Select
                Case "SHA384"
                    hash = New SHA384Managed()
                    Exit Select
                Case "SHA512"
                    hash = New SHA512Managed()
                    Exit Select
                Case Else
                    hash = New MD5CryptoServiceProvider()
                    Exit Select
            End Select

            Dim hashBytes As Byte()
            hashBytes = hash.ComputeHash(plainTextWithSaltBytes)
            Dim hashWithSaltBytes As Byte() = New Byte(hashBytes.Length + saltBytes.Length - 1 + 1 - 1) {}

            For I = 0 To hashBytes.Length - 1
                hashWithSaltBytes(I) = hashBytes(I)
            Next

            For I = 0 To saltBytes.Length - 1
                hashWithSaltBytes(hashBytes.Length + I) = saltBytes(I)
            Next

            Dim hashValue As String
            hashValue = Convert.ToBase64String(hashWithSaltBytes)
            Return hashValue
        End Function

        Function VerifyHash(ByVal plainText As String, ByVal hashValue As String, ByVal Optional hashAlgorithm As String = "SHA512") As Boolean
            Dim hashWithSaltBytes As Byte()
            hashWithSaltBytes = Convert.FromBase64String(hashValue)
            Dim hashSizeInBits As Integer
            Dim hashSizeInBytes As Integer
            If (hashAlgorithm Is Nothing) Then hashAlgorithm = ""

            Select Case hashAlgorithm.ToUpper()
                Case "SHA1"
                    hashSizeInBits = 160
                    Exit Select
                Case "SHA256"
                    hashSizeInBits = 256
                    Exit Select
                Case "SHA384"
                    hashSizeInBits = 384
                    Exit Select
                Case "SHA512"
                    hashSizeInBits = 512
                    Exit Select
                Case Else
                    hashSizeInBits = 128
                    Exit Select
            End Select

            hashSizeInBytes = hashSizeInBits / 8
            If (hashWithSaltBytes.Length < hashSizeInBytes) Then Return False
            Dim saltBytes As Byte() = New Byte(hashWithSaltBytes.Length - hashSizeInBytes - 1 + 1 - 1) {}
            Dim I As Integer

            For I = 0 To saltBytes.Length - 1
                saltBytes(I) = hashWithSaltBytes(hashSizeInBytes + I)
            Next

            Dim expectedHashString As String
            expectedHashString = CreateHash(plainText, saltBytes, hashAlgorithm)
            Return (hashValue = expectedHashString)
        End Function
    End Module


End Namespace
