Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HMAC-SHA1 URL encryption in VBA produces incorrect output

For Google Maps Business API queries I need to digitally sign every query using HMAC-SHA1. We use an Excel file with VBA macro to send the queries and parse the output, so I would like to create the signature in VBA. I found this question + answer: Base64 HMAC SHA1 String in VBA
However, the string signature by this code is not valid when the query is sent to the Google API.

Google provides a few sample scripts. I tested the Python sample script with identical input that I used to test the VBA-code above, and the Python code did return a valid signature. So it seems the provided VBA code does not construct a proper HMAC-SHA1 signature, but I can't find the problem (I have no experience with encryption, and only basic VBA knowledge).

I created a HMAC-SHA1 key for testing purposes: 1412SxPev45oMMRQSXazwQp789yM=
When run with "abc" as the string input I get the following returns:

VBA code: Fsu0z3i6Ma5HCrP3eXucrdssJLc=
Python code: IFxkS7B_ePtZrvU8sGmiaipTHio=

Does anyone have any idea how to compute a correct HMAC-SHA1 in VBA which equals the Python output?

Edit 03/04/2014:
Per Alex K.'s suggestion, I made sure to decode SharedSecretKey to Base64, using code from http://thydzik.com. I added the function DecodeBase64 to the VBA code below.
Since this output was correct, but not yet URL-safe (so not identical to the Python output), I used the VBA Replace() function to replace + with - and / with _
These solutions together produce the correct output, which is accepted by the Google servers.

VBA script used:

Public Function Base64_HMACSHA1(ByVal sTextToHash As String, ByVal sSharedSecretKey As String)

Dim asc As Object, enc As Object
Dim TextToHash() As Byte
Dim SharedSecretKey() As Byte
Set asc = CreateObject("System.Text.UTF8Encoding")
Set enc = CreateObject("System.Security.Cryptography.HMACSHA1")

TextToHash = asc.Getbytes_4(sTextToHash)
SharedSecretKey = asc.Getbytes_4(sSharedSecretKey)
enc.Key = SharedSecretKey

Dim bytes() As Byte
bytes = enc.ComputeHash_2((TextToHash))
Base64_HMACSHA1 = EncodeBase64(bytes)
Set asc = Nothing
Set enc = Nothing

End Function

Private Function EncodeBase64(ByRef arrData() As Byte) As String

Dim objXML As MSXML2.DOMDocument
Dim objNode As MSXML2.IXMLDOMElement

Set objXML = New MSXML2.DOMDocument

' byte array to base64
Set objNode = objXML.createElement("b64")
objNode.DataType = "bin.base64"
objNode.nodeTypedValue = arrData
EncodeBase64 = objNode.Text

Set objNode = Nothing
Set objXML = Nothing

End Function

Added code to decode in Base64:

Private Function decodeBase64(ByVal strData As String) As Byte()
Dim objXML As MSXML2.DOMDocument
Dim objNode As MSXML2.IXMLDOMElement

Set objXML = New MSXML2.DOMDocument
Set objNode = objXML.createElement("b64")
objNode.DataType = "bin.base64"
objNode.Text = strData
decodeBase64 = objNode.nodeTypedValue


Set objNode = Nothing
Set objXML = Nothing
End Function

Python script used:

#!/usr/bin/python
# coding: utf8

import sys
import hashlib
import urllib
import hmac
import base64
import urlparse

print("")
print("URL Signer 1.0")
print("")

# Convert the URL string to a URL, which we can parse
# using the urlparse() function into path and query
# Note that this URL should already be URL-encoded
url = urlparse.urlparse("YOUR_URL_TO_SIGN")

privateKey = "YOUR_PRIVATE_KEY"

# We only need to sign the path+query part of the string
urlToSign = url.path + "?" + url.query

# Decode the private key into its binary format
decodedKey = base64.urlsafe_b64decode(privateKey)

# Create a signature using the private key and the URL-encoded
# string using HMAC SHA1. This signature will be binary.
signature = hmac.new(decodedKey, urlToSign, hashlib.sha1)

# Encode the binary signature into base64 for use within a URL
encodedSignature = base64.urlsafe_b64encode(signature.digest())
originalUrl = url.scheme + "://" + url.netloc + url.path + "?" + url.query
print("Full URL: " + originalUrl + "&signature=" + encodedSignature)
like image 469
user3351744 Avatar asked Oct 01 '22 11:10

user3351744


1 Answers

Here you acquire the key from the input string:

SharedSecretKey = asc.Getbytes_4(sSharedSecretKey)

But here you acquire if from Base64 decoding the input string:

decodedKey = base64.urlsafe_b64decode(privateKey)

.Net's Getbytes will not Base64 decode so the inputs are very different.

If you decode SharedSecretKey to a byte array you will get the correct output:

IFxkS7B/ePtZrvU8sGmiaipTHio=

Although note the different Base64 re-encode semantics due to urlsafe_b64encode.

(If you decode in .net via Converter you will need to lose the trailing padding = from the key)

like image 96
Alex K. Avatar answered Oct 05 '22 01:10

Alex K.