You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在ASP.NET Membership Provider中实现PBKDF2遇非负参数错误

解决PBKDF2加密转换VB.NET后的srcOffset负数错误

问题背景

为使用ASP.NET Membership Provider的老旧应用实现PBKDF2加密,参考相关方案将C#代码转换为VB.NET后,执行Buffer.BlockCopy(m_buffer, m_startIndex, password, 0, size)时抛出错误:

Non-negative number required. Parameter name: srcOffset

排查发现m_startIndex的值变为-1,原VB.NET代码如下:

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)
    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

错误原因分析

  1. 构造函数未正确赋值salt:MyRfc2898DeriveBytes构造函数中salt = salt是无效的自我赋值,导致m_salt始终为Nothing,后续Func方法中访问m_salt.Length会引发未处理的空引用异常,间接导致索引混乱。
  2. GetBytesFromInt方法为空实现:该方法负责将UInt32类型的块编号转换为字节数组,空实现会返回Nothing,导致Func方法中调用m_hmac.TransformBlock时传入无效数组,破坏内部状态。
  3. m_blockSize可能为0:如果hmac.HashSize为0(虽然HMACSHA512不会出现,但代码未验证),m_blockSize = hmac.HashSize >> 3会得到0,此时Initialize方法中m_buffer = New Byte(m_blockSize - 1) {}会创建长度为-1的数组(即空数组),后续Buffer.BlockCopy操作会引发索引异常。

修复方案

1. 修正构造函数的salt赋值

将构造函数中的salt = salt替换为:

m_salt = CType(salt.Clone(), Byte())

2. 实现GetBytesFromInt方法

补充该方法的逻辑,将UInt32转换为大端字节序的4字节数组:

Friend Shared Function GetBytesFromInt(ByVal i As UInteger) As Byte()
    Dim bytes As Byte() = BitConverter.GetBytes(i)
    ' PBKDF2要求使用大端字节序
    If BitConverter.IsLittleEndian Then
        Array.Reverse(bytes)
    End If
    Return bytes
End Function

3. 增加参数验证

在构造函数中添加对salt和hmac的有效性验证:

<SecuritySafeCritical>
Public Sub New(ByVal password As Byte(), ByVal salt As Byte(), ByVal iterations As Integer, ByVal hmac As HMAC)
    If salt Is Nothing Then Throw New ArgumentNullException(NameOf(salt))
    If salt.Length < 8 Then Throw New ArgumentException("Salt must be at least 8 bytes long.", NameOf(salt))
    If hmac Is Nothing Then Throw New ArgumentNullException(NameOf(hmac))
    
    m_salt = CType(salt.Clone(), Byte())
    IterationCount = iterations
    m_hmac = hmac
    m_hmac.Key = password.Clone() ' 避免外部修改影响内部状态

    m_blockSize = hmac.HashSize >> 3
    If m_blockSize <= 0 Then
        Throw New ArgumentException("Invalid HMAC hash size.", NameOf(hmac))
    End If
    
    Initialize()
End Sub

修正后的完整代码

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)
    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)
    If salt Is Nothing Then Throw New ArgumentNullException(NameOf(salt))
    If salt.Length < 8 Then Throw New ArgumentException("Salt must be at least 8 bytes long.", NameOf(salt))
    If hmac Is Nothing Then Throw New ArgumentNullException(NameOf(hmac))
    
    m_salt = CType(salt.Clone(), Byte())
    IterationCount = iterations
    m_hmac = hmac
    m_hmac.Key = password.Clone()

    m_blockSize = hmac.HashSize >> 3
    If m_blockSize <= 0 Then
        Throw New ArgumentException("Invalid HMAC hash size.", NameOf(hmac))
    End If
    
    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(NameOf(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(NameOf(value))
        If value.Length < 8 Then Throw New ArgumentException("Error: Salt size is less than 8", NameOf(value))
        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(NameOf(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()
    Dim bytes As Byte() = BitConverter.GetBytes(i)
    ' PBKDF2规范要求使用大端字节序
    If BitConverter.IsLittleEndian Then
        Array.Reverse(bytes)
    End If
    Return bytes
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_block
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 11:25:58