vb.nethashasp.net-membershippbkdf2sqlmembershipprovider

Implement pbkdf2 in aspnet membership provider - Non-negative number required


I have a requirement to implement Pbkdf2 to secure a very old application that uses aspnet membership provider. I am following this answer which is recent and useful, but unable to follow what is inside MyRfc2898DeriveBytes class and it is giving me following issue.. (also I am a c# dev but for this project working in vb.net) So the issue could be related to language conversion.

I am getting

Non-negative number required. Parameter name: srcOffset

at the following

Buffer.BlockCopy(m_buffer, m_startIndex, password, 0, size)

the m_startIndex here somehow becomes -1

Here is my code in vb

Imports System.IO
Imports System.Security.Cryptography

Namespace custom.hashing.keyderivation
Public Class PBKDF2Hash
    Inherits KeyedHashAlgorithm

    Private Const kHashBytes As Integer = 64
    Private _ms As System.IO.MemoryStream
    Public Property WorkFactor As Integer

    Public Sub New()
        MyBase.New()
        Me.WorkFactor = 128000
        Me.Key = New Byte(31) {}

        Using rngCsp = New RNGCryptoServiceProvider()
            rngCsp.GetBytes(Me.Key)
        End Using
    End Sub

    Public Overrides ReadOnly Property HashSize As Integer
        Get
            Return kHashBytes * 8
        End Get
    End Property

    Protected Overrides Sub HashCore(ByVal array As Byte(), ByVal ibStart As Integer, ByVal cbSize As Integer)
        If IsNothing(_ms) Then
            _ms = New MemoryStream()
        End If

        _ms.Write(array, ibStart, cbSize)

        '(CSharpImpl.__Assign(_ms, If(_ms, New System.IO.MemoryStream()))).Write(array, ibStart, cbSize)
    End Sub

    Protected Overrides Function HashFinal() As Byte()
        If Me.Key Is Nothing OrElse Me.Key.Length = 0 Then
            Throw New CryptographicException("Missing KeyedAlgorithm key")
        End If

        _ms.Flush()
        Dim arr = _ms.ToArray()
        _ms = Nothing

        Using hmac As HMACSHA512 = New HMACSHA512()
            Return New MyRfc2898DeriveBytes(arr, Me.Key, Me.WorkFactor, hmac).GetBytes(kHashBytes)
        End Using
    End Function

    Public Overrides Sub Initialize()
        _ms = Nothing
    End Sub


End Class
End Namespace


Imports System.Diagnostics.Contracts
Imports System.Security
Imports System.Security.Cryptography


Public Class MyRfc2898DeriveBytes
Inherits DeriveBytes

Private m_buffer As Byte()
Private m_salt As Byte()
Private m_hmac As HMAC
Private m_iterations As UInteger
Private m_block As UInteger
Private m_startIndex As Integer = 0
Private m_endIndex As Integer = 0
Private m_blockSize As Integer = 0

<SecuritySafeCritical>
Public Sub New(ByVal password As Byte(), ByVal salt As Byte(), ByVal iterations As Integer, ByVal hmac As HMAC)
    salt = salt
    IterationCount = iterations
    hmac.Key = password
    m_hmac = hmac

    m_blockSize = hmac.HashSize >> 3

    ''' 
    Initialize()
End Sub

Public Property IterationCount As Integer
    Get
        Return CInt(m_iterations)
    End Get
    Set(ByVal value As Integer)
        If value <= 0 Then Throw New ArgumentOutOfRangeException("value", "Error: Iteration count is zero or less")
        m_iterations = CUInt(value)
        Initialize()
    End Set
End Property

Public Property Salt As Byte()
    Get
        Return CType(m_salt.Clone(), Byte())
    End Get
    Set(ByVal value As Byte())
        If value Is Nothing Then Throw New ArgumentNullException("value")
        If value.Length < 8 Then Throw New ArgumentException("Error: Salt size is less than 8")
        m_salt = CType(value.Clone(), Byte())
        Initialize()
    End Set
End Property

Public Overrides Function GetBytes(ByVal cb As Integer) As Byte()
    If cb <= 0 Then
        Throw New ArgumentOutOfRangeException("cb", "Error: Hash size is zero or less")
    End If

    Contract.Assert(m_blockSize > 0)
    Dim password As Byte() = New Byte(cb - 1) {}
    Dim offset As Integer = 0
    Dim size As Integer = m_endIndex - m_startIndex

    If size > 0 Then

        If cb >= size Then
            Buffer.BlockCopy(m_buffer, m_startIndex, password, 0, size)
            m_startIndex = m_endIndex = 0
            offset += size
        Else
            Buffer.BlockCopy(m_buffer, m_startIndex, password, 0, cb)
            m_startIndex += cb
            Return password
        End If
    End If

    Contract.Assert(m_startIndex = 0 AndAlso m_endIndex = 0, "Invalid start or end index in the internal buffer.")

    While offset < cb
        Dim T_block As Byte() = Func()
        Dim remainder As Integer = cb - offset

        If remainder > m_blockSize Then
            Buffer.BlockCopy(T_block, 0, password, offset, m_blockSize)
            offset += m_blockSize
        Else
            Buffer.BlockCopy(T_block, 0, password, offset, remainder)
            offset += remainder
            Buffer.BlockCopy(T_block, remainder, m_buffer, m_startIndex, m_blockSize - remainder)
            m_endIndex += (m_blockSize - remainder)
            Return password
        End If
    End While

    Return password
End Function

Public Overrides Sub Reset()
    Initialize()
End Sub

Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    MyBase.Dispose(disposing)

    If disposing Then

        If m_hmac IsNot Nothing Then
            m_hmac.Dispose()
        End If

        If m_buffer IsNot Nothing Then
            Array.Clear(m_buffer, 0, m_buffer.Length)
        End If

        If m_salt IsNot Nothing Then
            Array.Clear(m_salt, 0, m_salt.Length)
        End If
    End If
End Sub

Private Sub Initialize()
    If m_buffer IsNot Nothing Then Array.Clear(m_buffer, 0, m_buffer.Length)
    m_buffer = New Byte(m_blockSize - 1) {}
    m_block = 1
    m_startIndex = m_endIndex = 0
End Sub

Friend Shared Function GetBytesFromInt(ByVal i As UInteger) As Byte()

End Function

Private Function Func() As Byte()
    Dim INT_block As Byte() = GetBytesFromInt(m_block)
    m_hmac.TransformBlock(m_salt, 0, m_salt.Length, Nothing, 0)
    m_hmac.TransformBlock(INT_block, 0, INT_block.Length, Nothing, 0)
    m_hmac.TransformFinalBlock(New Byte(-1) {}, 0, 0)
    Dim temp As Byte() = m_hmac.Hash
    m_hmac.Initialize()
    Dim ret As Byte() = temp

    For i As Integer = 2 To m_iterations
        m_hmac.TransformBlock(temp, 0, temp.Length, Nothing, 0)
        m_hmac.TransformFinalBlock(New Byte(-1) {}, 0, 0)
        temp = m_hmac.Hash

        For j As Integer = 0 To m_blockSize - 1
            ret(j) = ret(j) Xor temp(j)
        Next

        m_hmac.Initialize()
    Next

    If m_block = UInteger.MaxValue Then
        Throw New InvalidOperationException("Derived key too long.")
    End If

    m_block += 1
    Return ret
End Function



End Class

Solution

  • So yes it was c# to vb conversion issues:

    1. double assignment does not work in vb, so the following line was changed from

      m_startIndex = m_endIndex = 0
      

    to

     m_startIndex = 0
     m_endIndex = 0
    
    1. Other things that I found

    Property with same name as variable was not converted properly so

    Public Sub New(ByVal password As Byte(), ByVal salt As Byte(), ByVal iterations As Integer, ByVal hmac As HMAC)
    salt = salt
    

    was really supposed to be

    Public Sub New(ByVal password As Byte(), ByVal salt As Byte(), ByVal iterations As Integer, ByVal hmac As HMAC)
    Me.Salt = salt
    

    as Salt was a property

    1. GetBytesFromInt(m_block) was not properly converted to vb

       Friend Shared Function GetBytesFromInt(ByVal i As UInteger) As Byte()
       Dim vIn As UInteger = 0
       Dim vOut As Byte() = BitConverter.GetBytes(i)
       Return vOut
      
       'return unchecked(new byte[] { (byte)(i >> 24), (byte)(i >> 16), (byte)(i >> 8), (byte)i });
      End Function