PBKDF2 Excel UDF and how to concatenate INT(i)

前端 未结 1 1407
清酒与你
清酒与你 2021-01-23 19:36

Recently I have been digging into cryptography and getting hashing and encryption functions working in Excel which I might use in a project I am working on.

I got simple

相关标签:
1条回答
  • 2021-01-23 20:06

    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!

    0 讨论(0)
提交回复
热议问题