# VBForums CodeBank > CodeBank - Visual Basic .NET >  Password Encryption - PBKDF2

## dday9

Here is an example of leveraging the System.Security.Cryptography class to encrypt password then check an incoming password against an encrypted one:


```
Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Security.Cryptography

Public Class Authentication

    ''' <summary>
    ''' Gets the length of the salt and hash
    ''' </summary>
    Private Shared ReadOnly _byteLength As Integer = 32

    ''' <summary>
    ''' Gets the number of iterations to derive the key using Rfc2898
    ''' </summary>
    Private Shared ReadOnly _rcfIterations As Integer = 100000

    ''' <summary>
    ''' Compares a plaintext <see cref="String"/> against a previously encrypted plaintext value
    ''' </summary>
    ''' <param name="plaintext">The <see cref="String"/> to encrypt</param>
    ''' <param name="encryptedText">A collection of <see cref="Byte"/> representing the previously encrypted plaintext value</param>
    ''' <returns><see cref="Boolean"/></returns>
    ''' <remarks>This method takes steps to prevent timing attacks by not immediately returning a False value if the password does not authenticate. For more information visit: https://en.wikipedia.org/wiki/Timing_attack</remarks>
    Public Shared Function Authenticate(plaintext As String, encryptedText As Byte()) As Boolean
        Dim authenticated = True

        Dim salt(_byteLength - 1) As Byte
        If (encryptedText.Length > _byteLength) Then
            For index = 0 To _byteLength - 1
                salt(index) = encryptedText(index)
            Next
        Else
            For index = 0 To _byteLength - 1
                salt(index) = [Byte].MinValue
            Next
            authenticated = False
        End If

        Dim hash() As Byte = {}
        Try
            hash = Authentication.GenerateHash(plaintext, salt)
        Catch
            For index = 0 To _byteLength - 1
                hash(index) = [Byte].MinValue
            Next
            authenticated = False
        End Try

        If (encryptedText.Length - _byteLength = hash.Length) Then
            For index = 0 To hash.Length - 1
                If (authenticated) Then
                    authenticated = hash(index) = encryptedText(_byteLength + index)
                Else
                    authenticated = [Byte].MinValue = [Byte].MaxValue
                End If
            Next
        Else
            For index = 0 To hash.Length - 1
                authenticated = [Byte].MinValue = [Byte].MaxValue
            Next
        End If

        Return authenticated
    End Function

    ''' <summary>
    ''' Prepends one <see cref="Byte"/> array representing the salt to another <see cref="Byte"/> array representing the hash
    ''' </summary>
    ''' <param name="salt">A collection of <see cref="Byte"/> representing the salt</param>
    ''' <param name="hash">A collection of <see cref="Byte"/> representing the hash</param>
    ''' <returns><see cref="IEnumerable(Of Byte)"/></returns>
    ''' <exception cref="ArgumentOutOfRangeException"><see cref="_byteLength"/> cannot be less than 1</exception>
    ''' <exception cref="ArgumentOutOfRangeException"><param name="salt"/> is invalid because the length does not exactly match <see cref="_byteLength"/></exception>
    ''' <exception cref="ArgumentOutOfRangeException"><param name="hash"/> is invalid because the length does not exactly match <see cref="_byteLength"/></exception>
    Public Shared Function CombineSaltAndHash(salt As Byte(), hash As Byte()) As IEnumerable(Of Byte)
        If (_byteLength < 1) Then
            Throw New ArgumentOutOfRangeException($"{NameOf(_byteLength)} cannot be less than 1")
        End If
        If (salt.Length <> _byteLength) Then
            Throw New ArgumentOutOfRangeException(NameOf(salt), "The incoming salt is invalid")
        End If
        If (hash.Length <> _byteLength) Then
            Throw New ArgumentOutOfRangeException(NameOf(hash), "The incoming hash is invalid")
        End If

        Return salt.Concat(hash).ToArray()
    End Function

    ''' <summary>
    ''' Encrypts a plaintext <see cref="String"/> to a collection of <see cref="Byte"/>
    ''' </summary>
    ''' <param name="plaintext">The <see cref="String"/> to encrypt</param>
    ''' <returns><see cref="IEnumerable(Of Byte)"/></returns>
    Public Shared Function EncryptPlainText(plaintext As String) As IEnumerable(Of Byte)
        Dim salt = Authentication.GenerateSalt()
        Dim hash = Authentication.GenerateHash(plaintext, salt)
        Dim combinedHash = Authentication.CombineSaltAndHash(salt, hash)
        Return combinedHash
    End Function

    ''' <summary>
    ''' Uses <see cref="Rfc2898DeriveBytes"/> to generate a hash
    ''' </summary>
    ''' <param name="plaintext">The <see cref="String"/> representing the password used to derive the key</param>
    ''' <param name="salt">The collection of <see cref="Byte"/> representing the salt used to derive the key</param>
    ''' <returns>Collection of <see cref="Byte"/></returns>
    ''' <exception cref="ArgumentOutOfRangeException"><see cref="_byteLength"/> cannot be less than 1</exception>
    ''' <exception cref="ArgumentOutOfRangeException"><see cref="_rcfIterations"/> cannot be less than 1</exception>
    ''' <exception cref="ArgumentNullException"><paramref name="plaintext"/> cannot be null</exception>
    ''' <exception cref="ArgumentOutOfRangeException"><paramref name="salt"/> is invalid because the length does not exactly match <see cref="_byteLength"/></exception>
    Public Shared Function GenerateHash(plaintext As String, salt As Byte()) As Byte()
        If (_byteLength < 1) Then
            Throw New ArgumentOutOfRangeException($"{NameOf(_byteLength)} cannot be less than 1")
        End If
        If (_rcfIterations < 1) Then
            Throw New ArgumentOutOfRangeException($"{NameOf(_rcfIterations)} cannot be less than 1")
        End If
        If (String.IsNullOrWhiteSpace(plaintext)) Then
            Throw New ArgumentNullException(NameOf(plaintext))
        End If
        If (salt.Length <> _byteLength) Then
            Throw New ArgumentOutOfRangeException(NameOf(salt), "The incoming salt is invalid")
        End If

        Dim hash() As Byte = {}
        Using rcf = New Rfc2898DeriveBytes(plaintext, salt, _rcfIterations)
            hash = rcf.GetBytes(_byteLength)
        End Using

        Return hash
    End Function

    ''' <summary>
    ''' Uses <see cref="RandomNumberGenerator"/> to generate a salt
    ''' </summary>
    ''' <returns>Collection of <see cref="Byte"/></returns>
    ''' <exception cref="ArgumentOutOfRangeException"><see cref="_byteLength"/> cannot be less than 1</exception>
    Public Shared Function GenerateSalt() As Byte()
        If (_byteLength < 1) Then
            Throw New ArgumentOutOfRangeException($"{NameOf(_byteLength)} cannot be less than 1")
        End If

        Dim salt(_byteLength - 1) As Byte
        Using provider = RandomNumberGenerator.Create()
            provider.GetBytes(salt)
        End Using
        Return salt
    End Function

End Class
```

The basic idea is that you would follow these steps to encrypt a password:
Generate a salt using the GenerateSalt methodGenerate a hash using the GenerateHash method with the salt that was generated in step 1Generate the combined salt and hash from steps 1 and two using the CombineSaltAndHash method

Alternatively the Authentication.EncryptPlainText does this for you.

Here is an example of using it:


```
Public Module Module1
    Public Sub Main()
        Dim existingPassword = Authentication.EncryptPlainText("Password123456789")
        Dim incomingPassword = Console.ReadLine()
        Dim authenticated = Authentication.Authenticate(incomingPassword, existingPassword)
    End Sub
	
End Module
```

Fiddle: https://dotnetfiddle.net/sVvYeS

GitHub: https://github.com/dday9/.NET-Authen...hentication.vb

----------


## wqweto

For base64 encoding (not encryption) you can have several different valid outputs for a single input which is not obvious. For instance A can be encoded to B or C so that decode(B) and decode(C) return the same A.

In this regard your final equality comparison should better be on decoded byta-arrays instead of encoded strings because its theorethically possible to have different base64 strings which encode the same byte-arrays.

----------


## dday9

Thank you for the feedback. I've updated the code to compare the individual bytes in the comparison method.

Also, small stylistic change in that I converted the Module to a Class.

----------


## wqweto

Btw, the popular name for key derivation construct in RFC 2898 is PBKDF2 which might be a good keyword for the submission to include in the title.

Also anything less that 100k iterations for PBKDF2 is consider medium to low security on modern h/w so a conservative default should be north of 10k (if 100k straight seems outrageous).

cheers,
</wqw>

----------


## dday9

So I don't fully understand the correlation between the number of iterations and performance, could you elaborate a bit on that?

----------


## peterst

> So I don't fully understand the correlation between the number of iterations and performance, could you elaborate a bit on that?


Number of iterations is related how long it will take to calculate the final value. More iterations = more time = longer and harder to bruteforce.

----------


## dday9

No, I completely understand that. I just don't understand, on a practical level, at what point is the number of iterations just too high.

----------


## wqweto

I guess you don't want end-users to wait 10+ seconds on every logon so you refrain from using million of iterations for this reason.

Another problem is that if you have 1000s of logons per second (not facebook but stackoverflow scale) you might need multiple CPUs/hosts just for the PBKDF2 computation not to bottleneck the site.

cheers,
</wqw>

----------


## dday9

I have updated the original code with the following changes:
 I replaced creating a new instance of the RNGCryptoServiceProvider with the RandomNumberGenerator.Create method I refactored the IsPasswordValid method so that the first argument is a byte array instead of a String I have rescoped my private shared variables to be ReadOnly

----------


## dday9

I have updated the code with the following changes:
Created the Authentication.Authenticate methodCreated the Authentication.EncryptPlainText method

----------

