PBKDF2 Excel UDF以及如何连接INT(i)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PBKDF2 Excel UDF以及如何连接INT(i)相关的知识,希望对你有一定的参考价值。

最近我一直在深入研究加密技术,并在Excel中使用散列和加密函数,我可能会在我正在研究的项目中使用它。

我使用简单的哈希函数,例如:

Function Hash(ByVal plainText As String)

    Dim utf8Encoding As Object
    Dim hashManager As Object
    Dim hashBytes() As Byte

    Set utf8Encoding = CreateObject("System.Text.UTF8Encoding")
    Set hashManager = CreateObject("System.Security.Cryptography.SHA512Managed")
    hashBytes = utf8Encoding.GetBytes_4(plainText)
    hashBytes = hashManager.ComputeHash_2(hashBytes)

    Hash = Encode(hashBytes, edHex)

    Set utf8Encoding = Nothing
    Set hashManager = Nothing

End Function

要对结果进行编码,我创建了一个函数:

Function Encode(ByRef arrData() As Byte, ByVal dataType As endecodeDataType) As String

    Dim domDoc As Object
    Set domDoc = CreateObject("MSXML2.DOMDocument")

    With domDoc
        .LoadXML "<root />"
        Select Case dataType
            Case edBase64
                .DocumentElement.dataType = "bin.base64"
            Case edHex
                .DocumentElement.dataType = "bin.hex"
        End Select   
        .DocumentElement.nodeTypedValue = arrData
    End With

    Encode = domDoc.DocumentElement.Text

    Set domDoc = Nothing

End Function

这些结合起来给了我完全可验证的结果。经过更多的研究,我现在正致力于PBKDF2功能:

我的第一次尝试是如下调查'Rfc2898DeriveBytes':

Dim hashManager As Object
Set hashManager = CreateObject("System.Security.Cryptography.Rfc2898DeriveBytes")

但是,这会给出一个错误,指出无法创建ActiveX组件。

除了错误,并且为了尝试理解PBKDF2的基础知识,并学习使用位/字节,我创建了以下函数:

编辑:现在我只专注于dkLen <= hLen

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

    Dim utf8Encoding As Object
    Dim hashManager As Object

    Dim hmacKeyBytes() As Byte
    Dim saltBytes() As Byte

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

    Dim i As Long

    'Create encoding and crypto objects
    Set utf8Encoding = CreateObject("System.Text.UTF8Encoding")
    Set hashManager = CreateObject("System.Security.Cryptography.HMACSHA1")

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

    'Concatenate salt and INT(i) - INT (i) is a four-octet encoding of the integer i, most significant octet first.

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

    'Compute HMAC from salt
    hmacBytes = hashManager.ComputeHash_2(saltBytes)
    tempBytes = hmacBytes

    'HMAC iterations
    For i = 1 To hashIterations
        tempBytes = hashManager.ComputeHash_2(tempBytes)
        hmacBytes = XorBytes(tempBytes, hmacBytes)
    Next i

    'ToDo: extract the first dkLen octets to produce a derived key DK

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

    Set hashManager = Nothing
    Set utf8Encoding = Nothing

End Function

我将XorBytes定义为:

Function XorBytes(ByRef byte1() As Byte, ByRef byte2() As Byte) As Byte()

    Dim tempBytes() As Byte
    Dim len1 As Long
    Dim i As Long

    len1 = UBound(byte1)
    ReDim tempBytes(len1)

    For i = 0 To len1
        tempBytes(i) = byte1(i) Xor byte2(i)
    Next i

    XorBytes = tempBytes

End Function

我相信我的基础是正确的。我不知道如何解决的一件事是如何将INT(i)连接到salt。规格说明:

U_1 = PRF(P,S || INT(i))

这里,INT(i)是整数i的四个八位字节编码,最重要的是八位字节。

如何在我的VBA代码中实现这一点?我希望这让我更接近这个测试向量:

  • 输入 P =“密码”(8个八位字节) S =“盐”(4个八位字节) c = 1 dkLen = 20
  • 产量 DK = 0c 60 c8 0f 96 1f 0e 71 f3 a9 b5 24 of 60 12 06 2f e0 37 a6(20 octets)
答案

经过一些更多的摆弄后,下面的函数返回我可以验证的输出:

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

枚举

Enum hmacAlgorithm
    HMAC_MD5
    HMAC_SHA1
    HMAC_SHA256
    HMAC_SHA384
    HMAC_SHA512
End Enum

Enum hashEncoding
    heBase64
    heHex
    heNone_Bytes
End Enum

PBKDF2功能

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

测试子程序:

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

我猜不是最漂亮的代码,但它是向前迈出的一步。随意改进!

以上是关于PBKDF2 Excel UDF以及如何连接INT(i)的主要内容,如果未能解决你的问题,请参考以下文章

如何从标准 Excel 表达式创建 Excel-UDF?

如何使用 VSTO 插件项目轻松创建 Excel UDF

如何将 Excel 数组公式传递给 VBA UDF?

如何以编程方式删除 Excel UDF

Excel UDF 过滤器范围

如何根据单元格颜色获取 UDF 以在 Excel 中自动更新