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