Erik A
Erik A

Reputation: 32682

How do I perform unicode normalization for password storage in VBA?

I want to store and compare hashed passwords in VBA.

I've read How do I properly implement Unicode passwords?, but I have no clue about where to start.

How do I normalize a unicode string in VBA?

Preferably, I'd do this without downloading the ICU the linked post refers to, because I'd like my project not to be dependent on external code.

Upvotes: 1

Views: 287

Answers (1)

Erik A
Erik A

Reputation: 32682

Windows provides a built-in for normalizing strings, the NormalizeString function. However, it can be a bit tricky to use.

Here is an implementation, based on the C example in the docs provided above:

'Declare the function
Public Declare PtrSafe Function NormalizeString Lib "Normaliz.dll" (ByVal NormForm As Byte, ByVal lpSrcString As LongPtr, ByVal cwSrcLength As Long, ByVal lpDstString As LongPtr, ByVal cwDstLength As Long) As Long
'And a relevant error code
Const ERROR_INSUFFICIENT_BUFFER = 122
'And a helper enum
Public Enum NORM_FORM
  NormalizationC = &H1
  NormalizationD = &H2
  NormalizationKC = &H5
  NormalizationKD = &H6
End Enum

'Available normalization forms can be found under https://learn.microsoft.com/en-us/windows/win32/api/winnls/ne-winnls-norm_form
'KD normalization is preferred(https://stackoverflow.com/a/16173329/7296893)  when hashing characters
'If you already have hashes stored, C normalization is least likely to break them
Public Function UnicodeNormalizeString(str As String, Optional norm_form As Byte = NormalizationKD) As String
    If Len(str) = 0 Then 'Zero-length strings can't be normalized
        UnicodeNormalizeString = str
        Exit Function
    End If
    Dim outlenestimate As Long
    'Get an initial length estimate for the string
    outlenestimate = NormalizeString(norm_form, StrPtr(str), Len(str), 0, 0)
    
    Dim i As Long
    'Try 10 times
    For i = 1 To 10
        'Initialize buffer
        UnicodeNormalizeString = String(outlenestimate, vbNullChar)
        'Get either the normalized string, or a new length estimate
        outlenestimate = NormalizeString(norm_form, StrPtr(str), Len(str), StrPtr(UnicodeNormalizeString), outlenestimate)
        If outlenestimate > 0 Then 'We got the normalized string
            'Truncate off the unused characters
            UnicodeNormalizeString = Left(UnicodeNormalizeString, outlenestimate)
            Exit Function
        Else
            If Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then
                Exit For 'An unexpected error occurred
            End If
            outlenestimate = outlenestimate * -1 'Use the new length estimate, try again
        End If
    Next
    Err.Raise 5000, Description:="Failure to normalize unicode string"
End Function

Once you have declared the normalization function, always run your password through it before hashing:

If SomeHashFun(UnicodeNormalizeString(MyPassword)) = SomeHashedPassword Then
   'We are in!
End If

Upvotes: 4

Related Questions