Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ported C# namespace to VB #99

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions PasswordStorage.vb
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
Imports System
Imports System.Text
Imports System.Security.Cryptography

Namespace PasswordSecurity

Class InvalidHashException
Inherits Exception

' Default constructor
Sub New()
End Sub

Sub New(message As String)
MyBase.New(message)
End Sub

Sub New(message As String, innerException As Exception)
MyBase.New(message, innerException)
End Sub

End Class

Class CannotPerformOperationException
Inherits Exception

' Default constructor
Sub New()
End Sub

Sub New(message As String)
MyBase.New(message)
End Sub

Sub New(message As String, innerException As Exception)
MyBase.New(message, innerException)
End Sub
End Class

Public Class PasswordStorage

'These constants may be changed without breaking existing hashes.
Public Const SALT_BYTES As Integer = 24
Public Const HASH_BYTES As Integer = 18
Public Const PBKDF2_ITERATIONS As Integer = 64000

' These constants define the encoding And may Not be changed.
Public Const HASH_SECTIONS As Integer = 5
Public Const HASH_ALGORITHM_INDEX As Integer = 0
Public Const ITERATION_INDEX As Integer = 1
Public Const HASH_SIZE_INDEX As Integer = 2
Public Const SALT_INDEX As Integer = 3
Public Const PBKDF2_INDEX As Integer = 4

Public Shared Function CreateHash(password As String) As String

Dim salt(SALT_BYTES) As Byte
Try

Using csprng = New RNGCryptoServiceProvider()
csprng.GetBytes(salt)
End Using

Catch ex As CryptographicException
Throw New CannotPerformOperationException("Random number generator not available", ex)
Catch ex As ArgumentException
Throw New CannotPerformOperationException("Invalid argument given to random number generator", ex)
End Try

Dim hash() As Byte = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES)

' format: algorithm:iteration:hashSize:salt:hash
Dim parts As String = "sha1:" + PBKDF2_ITERATIONS.ToString() + ":" + hash.Length.ToString() + ":" + Convert.ToBase64String(salt) + ":" + Convert.ToBase64String(hash)
Return parts

End Function

Public Shared Function VerifyPassword(password As String, goodHash As String) As Boolean
Dim delimter() As Char = {":"}
Dim split As String() = goodHash.Split(delimter)

If split.Length <> HASH_SECTIONS Then
Throw New InvalidHashException("Fields are missing from the password hash")
End If

' We only support SHA1 with VB.NET
If split(HASH_ALGORITHM_INDEX) <> "sha1" Then
Throw New CannotPerformOperationException("Unsupported hash type.")
End If

Dim iterations As Integer = 0
Try
iterations = Integer.Parse(split(ITERATION_INDEX))
Catch ex As ArgumentException
Throw New CannotPerformOperationException("Invalid argument given to Int32.Parse", ex)
Catch ex As FormatException
Throw New InvalidHashException("Could not parse the iteration count as an integer.", ex)
Catch ex As OverflowException
Throw New InvalidHashException("The iteration count is too large to be represented.", ex)
End Try

If iterations < 1 Then
Throw New InvalidHashException("Invalid number of interations. Must be >= 1.")
End If

Dim salt() As Byte = Nothing
Try
salt = Convert.FromBase64String(split(SALT_INDEX))
Catch ex As ArgumentException
Throw New CannotPerformOperationException("Invalid argument given to Convert.FromBase64String.", ex)
Catch ex As FormatException
Throw New InvalidHashException("Base64 decoding of salt failed.", ex)
End Try

Dim hash() As Byte = Nothing
Try
hash = Convert.FromBase64String(split(PBKDF2_INDEX))
Catch ex As ArgumentException
Throw New CannotPerformOperationException("Invalid argument given to Convert.FromBase64String.", ex)
Catch ex As FormatException
Throw New InvalidHashException("Base64 decoding of pbkdf2 output failed.", ex)
End Try

Dim storedHashSize As Integer = 0
Try
storedHashSize = Integer.Parse(split(HASH_SIZE_INDEX))
Catch ex As ArgumentException
Throw New CannotPerformOperationException("Invalid argument given to Integer.Parse.", ex)
Catch ex As FormatException
Throw New InvalidHashException("The hash size is too large to be represented.", ex)
End Try

If storedHashSize <> hash.Length Then
Throw New InvalidHashException("Hash length doesn't match stored hash length.")
End If

Dim testHash() As Byte = PBKDF2(password, salt, iterations, hash.Length)
Return SlowEquals(hash, testHash)
End Function

Private Shared Function SlowEquals(a() As Byte, b() As Byte) As Boolean
Dim diff As UInteger = CUInt(a.Length) Xor CUInt(b.Length)
Dim i = 0
While i < a.Length And i < b.Length
diff = diff Or CUInt(a(i) Xor b(i))
i = i + 1
End While
Return diff = 0
End Function

Private Shared Function PBKDF2(password As String, salt() As Byte, iterations As Integer, outputBytes As Integer)
Using pbkdf2DerivedBytes = New Rfc2898DeriveBytes(password, salt)
pbkdf2DerivedBytes.IterationCount = iterations
Return pbkdf2DerivedBytes.GetBytes(outputBytes)
End Using
End Function

End Class

End Namespace