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
So yes it was c# to vb conversion issues:
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
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
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