PBKDF2 Excel UDF and how to concatenate INT(i)

不打扰是莪最后的温柔 提交于 2019-12-02 10:50:58

After some more fiddling the function below returns output that I can verify with:

https://tools.ietf.org/html/rfc6070

Enums

Enum hmacAlgorithm
    HMAC_MD5
    HMAC_SHA1
    HMAC_SHA256
    HMAC_SHA384
    HMAC_SHA512
End Enum

Enum hashEncoding
    heBase64
    heHex
    heNone_Bytes
End Enum

PBKDF2 Function

Function PBKDF2(ByVal password As String, _
    ByVal salt As String, _
    ByVal hashIterations As Long, _
    ByVal algoritm As hmacAlgorithm, _
    Optional ByVal dkLen As Long, _
    Optional ByVal encodeHash As hashEncoding = heBase64) As Variant

'https://tools.ietf.org/html/rfc2898 - PKCS #5: Password-Based Cryptography Specification Version 2.0
'https://tools.ietf.org/html/rfc6070 - PKCS #5: Password-Based Key Derivation Function 2 (PBKDF2) Test Vectors
'https://en.wikipedia.org/wiki/PBKDF2

'DK = T1 || T2 || ... || Tdklen/hlen
'Ti = F(password, salt, c, i)
'
'F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
'
'U_1 = PRF (P, S || INT (i)) (INT (i) is a four-octet encoding of the integer i, most significant octet first.)
'U_2 = PRF (P, U_1)
'...
'U_c = PRF (P, U_{c-1})

Dim utf8Encoding As Object
Dim hashManager As Object

Dim hLen As Long
Dim noBlocks As Long
Dim noBlock As Long

Dim hmacKeyBytes() As Byte
Dim saltBytes() As Byte
Dim uboundSaltBytes As Long

Dim hmacBytes() As Byte
Dim tempBytes() As Byte
Dim outputBytes() As Byte

Dim i As Long
Dim j As Long

'Create utf8-encoding object
Set utf8Encoding = CreateObject("System.Text.UTF8Encoding")

'Create hmac object
Select Case algoritm
    Case HMAC_MD5
        Set hashManager = CreateObject("System.Security.Cryptography.HMACMD5")
    Case HMAC_SHA1
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA1")
    Case HMAC_SHA256
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA256")
    Case HMAC_SHA384
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA384")
    Case HMAC_SHA512
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA512")
End Select

'Check the length of the blocks to be generated
hLen = hashManager.HashSize / 8

'Calculate amount of blocks 'T'
If dkLen = 0 Then dkLen = hLen
noBlocks = Application.WorksheetFunction.Ceiling(dkLen / hLen, 1)

'Encode the key and salt to bytes
hmacKeyBytes = utf8Encoding.GetBytes_4(password)
saltBytes = utf8Encoding.GetBytes_4(salt)

'Set the key in the crypto class
hashManager.key = hmacKeyBytes

'Get the length of the salt, add 4 to concatenate INT(I)
uboundSaltBytes = UBound(saltBytes) + 4

'Loop T1 || T2 || ... || Tdklen/hlen
For i = 1 To noBlocks

    'Salt || INT(i)
    'INT (i) is a four-octet encoding of the integer i, most significant octet first.
    tempBytes = saltBytes
    ReDim Preserve tempBytes(uboundSaltBytes)
    noBlock = i

    'Calculate INT(i) of Salt || INT(i)
    For j = 3 To 0 Step -1
        tempBytes(uboundSaltBytes - j) = Int(noBlock / (255 ^ j))
        noBlock = noBlock - Int(noBlock / (255 ^ j)) * 255 ^ j
    Next j

    'Hash U1: Salt || INT(i)
    hmacBytes = hashManager.ComputeHash_2(tempBytes)
    tempBytes = hmacBytes

    'Hash, Xor: U1 ^ U2 ^ ... ^ Uc
    For j = 1 To hashIterations - 1
        hmacBytes = hashManager.ComputeHash_2(hmacBytes)
        tempBytes = XorBytes(tempBytes, hmacBytes)
    Next j

    'For the first block outputBytes() is empty
    If i = 1 Then
        outputBytes = tempBytes
    Else
        ConcatenateArrayInPlace outputBytes, tempBytes
    End If

Next i

'Extract the first dkLen octets to produce a derived key DK:
ReDim Preserve outputBytes(dkLen - 1)

'Base64, Hex, or Byte() output
If encodeHash = heBase64 Then
    PBKDF2 = Encode(outputBytes, edBase64)
ElseIf encodeHash = heHex Then
    PBKDF2 = Encode(outputBytes, edHex)
Else
    PBKDF2 = outputBytes
End If

Set hashManager = Nothing
Set utf8Encoding = Nothing

End Function

HMAC function

Function HMAC(ByVal plainText As String, _
    ByVal algoritm As hmacAlgorithm, _
    Optional ByVal key As String, _
    Optional ByVal decodeKey As keyDecoding = kdNone_String, _
    Optional ByVal encodeHash As hashEncoding = heBase64) As Variant

Dim hashManager As Object

Dim hashBytes() As Byte
Dim hmacKeyBytes() As Byte

'Create the specific hash manager based on the hash algoritm
Select Case algoritm
    Case HMAC_MD5
        Set hashManager = CreateObject("System.Security.Cryptography.HMACMD5") 'Returns 128 bits, 16 bytes
    Case HMAC_SHA1
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA1") 'Returns 160 bits, 20 bytes
    Case HMAC_SHA256
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA256") 'Returns 256 bits, 32 bytes
    Case HMAC_SHA384
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA384") 'Returns 384 bits, 48 bytes
    Case HMAC_SHA512
        Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA512") 'Returns 512 bits, 64 bytes
End Select

'Encode the plaintText to bytes
hashBytes = UTF8_GetBytes(plainText)

If key = vbNullString Then

    'Get the key generated by the hashManager
    hmacKeyBytes = hashManager.key

    'Calculate the hash
    hashBytes = hashManager.ComputeHash_2(hashBytes)

    'Return encoded result
    If encodeHash = heBase64 Then
        HMAC = "<Key>" & Encode(hmacKeyBytes, edBase64) & "<Key>" & vbCrLf & Encode(hashBytes, edBase64)
    ElseIf encodeHash = heHex Then
        HMAC = "<Key>" & Encode(hmacKeyBytes, edHex) & "<Key>" & vbCrLf & Encode(hashBytes, edHex)
    End If

Else

    'Decode and set the key
    Select Case decodeKey
    Case kdBase64
        hashManager.key = Decode(key, edBase64)
    Case kdHex
        hashManager.key = Decode(key, edHex)
    Case Else
        hashManager.key = UTF8_GetBytes(key)
    End Select

    'Calculate the hash
    hashBytes = hashManager.ComputeHash_2(hashBytes)

    'Return encoded result
    If encodeHash = heBase64 Then
        HMAC = Encode(hashBytes, edBase64)
    ElseIf encodeHash = heHex Then
        HMAC = Encode(hashBytes, edHex)
    End If

End If

Set hashManager = Nothing

End Function

Test subroutine:

Sub PBKDF2_Test()

Dim testvector As String
Dim pbkdf2_result As String

pbkdf2_result = PBKDF2("password", "salt", 1, HMAC_SHA1, 20, heHex)
testvector = "0c60c80f961f0e71f3a9b524af6012062fe037a6"
If pbkdf2_result = testvector Then Debug.Print "TV1: OK" Else Debug.Print "TV1: FAULT"

pbkdf2_result = PBKDF2("password", "salt", 2, HMAC_SHA1, 20, heHex)
testvector = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"
If pbkdf2_result = testvector Then Debug.Print "TV2: OK" Else Debug.Print "TV2: FAULT"

pbkdf2_result = PBKDF2("password", "salt", 4096, HMAC_SHA1, 20, heHex)
testvector = "4b007901b765489abead49d926f721d065a429c1"
If pbkdf2_result = testvector Then Debug.Print "TV3: OK" Else Debug.Print "TV3: FAULT"

pbkdf2_result = PBKDF2("passwordPASSWORDpassword", "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, HMAC_SHA1, 25, heHex)
testvector = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"
If pbkdf2_result = testvector Then Debug.Print "TV4: OK" Else Debug.Print "TV4: FAULT"

End Sub

I guess not the prettiest code, but it's a step forward. Feel free to improve!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!