SHA2 Cryptographic Hash Algorithm for VBA and VBScript

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Rabbit
    Recognized Expert MVP
    • Jan 2007
    • 12517

    SHA2 Cryptographic Hash Algorithm for VBA and VBScript

    INTRODUCTION
    The Secure Hash Algorithm 2 is a series of cryptographic hash algorithms designed by the US National Security Agency (NSA) and published by the National Institute of Standards and Technology (NIST) as a government standard.

    There is currently a competition being held by NIST to find a new family of algorithms for what will be named SHA-3. These new functions may not necessarily be derived from the SHA-2 algorithms.

    One of the most widely used hash algorithms is the MD5 algorithm but substantial weaknesses have been found with it and it has been strongly recommended that MD5 be discontinued.

    WHAT IS SHA2
    SHA2 is a hash algorithm. A hash algorithm is basically a one-way encryption that outputs a fixed length. You can encrypt a text but you can not decrypt it. It is often used for password storage and message authentication.

    Instead of storing a plaintext password, what you do is precalculate the hash of the password. When they enter in the password, you hash their input and compare it to the hash that you have stored. This way, even if they know what the result of the hash is, they do not know the original password that created the hash. It would be infeasible for them to calculate a password that would result in the same hash.

    On a side note, this is how Linux based utilities reset Windows passwords. They overwrite the hash value that is stored with the user account. If security is an issue, what you should do is encrypt all the files of that user using the password. That way, even if they overwrite the hash and log in as that user, they can not view the files because they don't have the password to decrypt the data.

    Hashes are often used in internet communication to authenticate messages. The sender will hash the original data and send it along with the encrypted data. The receiver will then decrypt the data and recalculate the hash from the unencrypted data. If the hashes match, then he is assured that the data was received as intended. If you were to send just the encrypted data, someone could conceivably intercept the message, flip a bit in the message, and pass it along. The receiver would never know the message was changed.

    GENERAL WEAKNESSES OF HASH ALGORITHMS
    Because a hash algorithm is intended to return a fixed length regardless of the size of the input, there will invariably be collisions. Collisions are when two different texts produce the same hash. The longer the hash, the less likely the chance of a collision.

    When using hashes to store passwords, it does not prevent brute force cracking of a password. Also, since the same text hashed using an algorithm always produces the same hash, it is strongly recommended that a nonce is used to defeat rainbow table attacks. A nonce, initialization vector, or salt are basically random bits that are used with the key so that even though you are using the same key, each message is different because the random bits in effect change the key that is used. A rainbow table is a precomputed table of hashes of popular passwords. All the hacker would have to do is compare the precomputed hashes with the stored hashes to see if there's a match. A nonce defeats this by changing the actual text that is being hashed.

    HOW SHA2 WORKS
    SHA2 breaks messages into 64 byte chunks, does mathematical transformations on each chunk, and adds them to a hash value. It does this for every chunk and adds them to the same value. In the end, you get a fixed length output.

    SPECIFIC WEAKNESSES OF SHA2
    There are currently no known successful attacks that will break all rounds of the SHA2 256-bit algorithm.

    SAMPLE IMPLEMENTATION
    This is an implementation of the SHA2 256-bit algorithm. It even works in a Visual Basic Script and was, in fact, coded specifically for VBScript. But it should be directly portable to VBA. It takes a string, hashes it, and returns the result as a 32 item array containing the hashed value. I validated the output against official SHA2 hashes.

    For this to work, it has to use 32-bit unsigned integers but seeing as how that is not available as a datatype in VBA, we have to use doubles instead. However, the MOD, XOR, AND, and NOT operators will overflow when using doubles. In addition to that, there is no bit shift operators or functions in VBA. So I had to create those.

    Code:
    Function SHA(ByVal sMessage)
        Dim i, result(32), temp(8) As Double, fraccubeprimes, hashValues
        Dim done512, index512, words(64) As Double, index32, mask(4)
        Dim s0, s1, t1, t2, maj, ch, strLen
      
        mask(0) = 4294967296#
        mask(1) = 16777216
        mask(2) = 65536
        mask(3) = 256
      
        hashValues = Array( _
            1779033703, 3144134277#, 1013904242, 2773480762#, _
            1359893119, 2600822924#, 528734635, 1541459225)
      
        fraccubeprimes = Array( _
            1116352408, 1899447441, 3049323471#, 3921009573#, 961987163, 1508970993, 2453635748#, 2870763221#, _
            3624381080#, 310598401, 607225278, 1426881987, 1925078388, 2162078206#, 2614888103#, 3248222580#, _
            3835390401#, 4022224774#, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, _
            2554220882#, 2821834349#, 2952996808#, 3210313671#, 3336571891#, 3584528711#, 113926993, 338241895, _
            666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350#, 2456956037#, _
            2730485921#, 2820302411#, 3259730800#, 3345764771#, 3516065817#, 3600352804#, 4094571909#, 275423344, _
            430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, _
            1955562222, 2024104815, 2227730452#, 2361852424#, 2428436474#, 2756734187#, 3204031479#, 3329325298#)
      
        sMessage = Nz(sMessage, "")
        strLen = Len(sMessage) * 8
        sMessage = sMessage & Chr(128)
        done512 = False
        index512 = 0
      
        If (Len(sMessage) Mod 64) < 56 Then
            sMessage = sMessage & String(56 - (Len(sMessage) Mod 64), Chr(0))
        ElseIf (Len(sMessage) Mod 64) > 56 Then
            sMessage = sMessage & String(120 - (Len(sMessage) Mod 64), Chr(0))
        End If
        sMessage = sMessage & Chr(0) & Chr(0) & Chr(0) & Chr(0)
      
        sMessage = sMessage & Chr(Int((strLen / mask(0) - Int(strLen / mask(0))) * 256))
        sMessage = sMessage & Chr(Int((strLen / mask(1) - Int(strLen / mask(1))) * 256))
        sMessage = sMessage & Chr(Int((strLen / mask(2) - Int(strLen / mask(2))) * 256))
        sMessage = sMessage & Chr(Int((strLen / mask(3) - Int(strLen / mask(3))) * 256))
      
        Do Until done512
            For i = 0 To 15
                words(i) = Asc(Mid(sMessage, index512 * 64 + i * 4 + 1, 1)) * mask(1) + Asc(Mid(sMessage, index512 * 64 + i * 4 + 2, 1)) * mask(2) + Asc(Mid(sMessage, index512 * 64 + i * 4 + 3, 1)) * mask(3) + Asc(Mid(sMessage, index512 * 64 + i * 4 + 4, 1))
            Next
      
            For i = 16 To 63
                s0 = largeXor(largeXor(rightRotate(words(i - 15), 7, 32), rightRotate(words(i - 15), 18, 32), 32), Int(words(i - 15) / 8), 32)
                s1 = largeXor(largeXor(rightRotate(words(i - 2), 17, 32), rightRotate(words(i - 2), 19, 32), 32), Int(words(i - 2) / 1024), 32)
                words(i) = Mod32Bit(words(i - 16) + s0 + words(i - 7) + s1)
            Next
      
            For i = 0 To 7
                temp(i) = hashValues(i)
            Next
      
            For i = 0 To 63
                s0 = largeXor(largeXor(rightRotate(temp(0), 2, 32), rightRotate(temp(0), 13, 32), 32), rightRotate(temp(0), 22, 32), 32)
                maj = largeXor(largeXor(largeAnd(temp(0), temp(1), 32), largeAnd(temp(0), temp(2), 32), 32), largeAnd(temp(1), temp(2), 32), 32)
                t2 = Mod32Bit(s0 + maj)
                s1 = largeXor(largeXor(rightRotate(temp(4), 6, 32), rightRotate(temp(4), 11, 32), 32), rightRotate(temp(4), 25, 32), 32)
                ch = largeXor(largeAnd(temp(4), temp(5), 32), largeAnd(largeNot(temp(4), 32), temp(6), 32), 32)
                t1 = Mod32Bit(temp(7) + s1 + ch + fraccubeprimes(i) + words(i))
      
                temp(7) = temp(6)
                temp(6) = temp(5)
                temp(5) = temp(4)
                temp(4) = Mod32Bit(temp(3) + t1)
                temp(3) = temp(2)
                temp(2) = temp(1)
                temp(1) = temp(0)
                temp(0) = Mod32Bit(t1 + t2)
            Next
      
            For i = 0 To 7
                hashValues(i) = Mod32Bit(hashValues(i) + temp(i))
            Next
      
            If (index512 + 1) * 64 >= Len(sMessage) Then done512 = True
            index512 = index512 + 1
        Loop
      
        For i = 0 To 31
            result(i) = Int((hashValues(i \ 4) / mask(i Mod 4) - Int(hashValues(i \ 4) / mask(i Mod 4))) * 256)
        Next
      
        SHA = result
    End Function
      
    Function Mod32Bit(value)
        Mod32Bit = Int((value / 4294967296# - Int(value / 4294967296#)) * 4294967296#)
    End Function
      
    Function rightRotate(value, amount, totalBits)
        'To leftRotate, make amount = totalBits - amount
        Dim i
        rightRotate = 0
      
        For i = 0 To (totalBits - 1)
            If i >= amount Then
                rightRotate = rightRotate + (Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)) * 2 ^ (i - amount)
            Else
                rightRotate = rightRotate + (Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)) * 2 ^ (totalBits - amount + i)
            End If
        Next
    End Function
      
    Function largeXor(value, xorValue, totalBits)
        Dim i, a, b
        largeXor = 0
      
        For i = 0 To (totalBits - 1)
            a = (Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2))
            b = (Int((xorValue / (2 ^ (i + 1)) - Int(xorValue / (2 ^ (i + 1)))) * 2))
            If a <> b Then
                largeXor = largeXor + 2 ^ i
            End If
        Next
    End Function
      
    Function largeNot(value, totalBits)
        Dim i, a
        largeNot = 0
      
        For i = 0 To (totalBits - 1)
            a = Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)
            If a = 0 Then
                largeNot = largeNot + 2 ^ i
            End If
        Next
    End Function
      
    Function largeAnd(value, andValue, totalBits)
        Dim i, a, b
        largeAnd = 0
      
        For i = 0 To (totalBits - 1)
            a = Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)
            b = (Int((andValue / (2 ^ (i + 1)) - Int(andValue / (2 ^ (i + 1)))) * 2))
            If a = 1 And b = 1 Then
                largeAnd = largeAnd + 2 ^ i
            End If
        Next
    End Function
    Last edited by Rabbit; Jan 29 '16, 08:47 PM. Reason: Fixed code for messages larger than 55 characters.
  • paerison
    New Member
    • Sep 2012
    • 1

    #2
    slightly more efficient itteration in Function SHA

    SHA2 Cryptographic Hash Algorithm for VBA and VBScript
    Here's a bit that will save some CPU cycles and such
    Code:
    31:     If (Len(sMessage) Mod 60) <> 0 Then 
    32:        For i = (Len(sMessage) Mod 60) To 59 
    33:            sMessage = sMessage & Chr(0) 
    34:     Next 
    35:    End If
    replace with
    Code:
    31: sMessage = sMessage & String(60 - Len(sMessage) Mod 60, Chr(0))
    confirmed VBA 14 (Office 2010)

    Comment

    • NeoPa
      Recognized Expert Moderator MVP
      • Oct 2006
      • 32645

      #3
      Indeed. Good suggestion.

      Comment

      • gaultz
        New Member
        • Oct 2012
        • 1

        #4
        Hmmm...not sure what I am missing, but I get the same hash result for the following:
        SHA(string(64," X"))
        SHA(string(65," X"))
        SHA(string(66," X"))
        and so on

        Comment

        • Rabbit
          Recognized Expert MVP
          • Jan 2007
          • 12517

          #5
          I will fix the code for messages longer than 64 characters and repost.
          YEA!
          I was just trying to figure that out once I confirmed Gaultz's discovery
          -z
          Last edited by zmbd; Oct 10 '12, 11:53 PM.

          Comment

          • Rabbit
            Recognized Expert MVP
            • Jan 2007
            • 12517

            #6
            The code has been fixed. I also noticed a bug for messages between 61 and 64 characters that has also been fixed. Thanks for pointing this out for me.

            I can make no guarantee on my fix though as I have no test values to use that are 61+ characters long.
            Last edited by Rabbit; Oct 11 '12, 04:13 PM.

            Comment

            • zmbd
              Recognized Expert Moderator Expert
              • Mar 2012
              • 5501

              #7
              I caught the changes on lines 31 thru 35; however, I don't see any other changes.
              When I put these into the VBA version, the same issue with truncation past 64 is occuring.

              Comment

              • Rabbit
                Recognized Expert MVP
                • Jan 2007
                • 12517

                #8
                I'm seeing different results, can you post your input and output?

                Comment

                • Rabbit
                  Recognized Expert MVP
                  • Jan 2007
                  • 12517

                  #9
                  @z, that wasn't the only change. There's a small and vital change on line 44 that's the crux of the matter.

                  Comment

                  • Rabbit
                    Recognized Expert MVP
                    • Jan 2007
                    • 12517

                    #10
                    Lines 31-34 was to fix the issue for lengths greater than 60. Line 44 is the fix for lengths greater than 64.

                    Comment

                    • duffy
                      New Member
                      • Nov 2012
                      • 2

                      #11
                      Great work i really like it

                      Comment

                      • Rabbit
                        Recognized Expert MVP
                        • Jan 2007
                        • 12517

                        #12
                        The return value is an array of numbers. You'll want to first put it in a string format so you can store it. Which format you choose is up to you. If it doesn't need to be human readable, ASCII will work and use the least amount of space. Otherwise, hexadecimal and base64 are popular formats.

                        After that, you can call it the same way you would any other function.

                        Comment

                        • duffy
                          New Member
                          • Nov 2012
                          • 2

                          #13
                          "You'll want to first put it in a string format so you can store it." - You mean convert all array fields into strings?

                          Comment

                          • Rabbit
                            Recognized Expert MVP
                            • Jan 2007
                            • 12517

                            #14
                            I mean append each array element into one long string in the format of your choice.

                            Comment

                            • DimitriM
                              New Member
                              • Jan 2016
                              • 1

                              #15
                              I've noticed that hashes for 56-to-59-character-long messages don't match those calculates by other programs (e.g. http://www.xorbin.com/tools/sha256-hash-calculator). I believe this can be fixed if lines 31-36 are changed to the following:

                              Code:
                              31:    If (Len(sMessage) Mod 64) < 56 Then
                              32:        sMessage = sMessage & String(56 - (Len(sMessage) Mod 64), Chr(0))
                              33:    ElseIf (Len(sMessage) Mod 64) > 56 Then
                              34:        sMessage = sMessage & String(120 - (Len(sMessage) Mod 64), Chr(0))
                              35:    End If
                              36:    sMessage = sMessage & Chr(0) & Chr(0) & Chr(0) & Chr(0)

                              Comment

                              Working...