This is one of those problems that quickly sucked up a majority of my day... I wanted to use ColdFusion to make HTTP geocoding requests to the Google Maps API using my employer's business license for the service. Try as I might, using ColdFusion's encryption functions to mimic the
other examples kept returning garbage.
But then it dawned on me, the
Java example could be mimicked to the T in ColdFusion, since it runs on the Java platform!
<cffunction name="signGoogleURL">
<cfargument name="url" />
<!--- I stored the key in the request scope, you can remove the default attribute --->
<cfargument name="key" default="#request.GoogleAPIs.mapsV3.cryptoKey#" />
<cfset var local = {} />
<cfif Find(".googleapis.com", arguments.url) EQ 0>
<cfthrow message="Invalid Google URL." />
</cfif>
<!--- pull off the googleapis piece of the URL --->
<cfset local.urlToEncrypt =
Right(arguments.url,
Len(arguments.url)-(Find(".googleapis.com", arguments.url)+14)
)
/>
<!--- this is the part we encrypt and pass as signature --->
<cfset local.msg_digest = cfhmac(local.urlToEncrypt, webSafeToBase64(arguments.key)) />
<cfreturn arguments.url & "&signature="
& Base64ToWebSafe(local.msg_digest) />
</cffunction>
<!--- this function mimmicks the code in signRequest from the Java example --->
<cffunction name="CFHMAC" output="false">
<cfargument name="signMsg" type="string" required="true" />
<cfargument name="signKey" type="string" required="true" />
<cfset var local = {} />
<!--- get the key in binary --->
<cfset local.key = BinaryDecode(arguments.signKey, "base64") />
<!--- initialize the crypto object with the key and algorithm --->
<cfset local.keySpec = createObject("java", "javax.crypto.spec.SecretKeySpec").init(local.key, "HmacSHA1") />
<cfset local.mac = createObject("java", "javax.crypto.Mac").getInstance("HmacSHA1") />
<cfset local.mac.init(local.keySpec) />
<!--- pass the message's bytes into the crypto object. we return it in Base64 --->
<cfreturn ToBase64(
local.mac.doFinal(arguments.signMsg.getBytes())
) />
</cffunction>
<!--- simple switch from google's webSafe format to the actual Base64, in the Java example this is done in UrlSigner() --->
<cffunction name="webSafeToBase64">
<cfargument name="val" />
<cfset arguments.val = replace(
replace(arguments.val, "-", "+", "ALL")
, "_", "/", "ALL") />
<cfreturn arguments.val />
</cffunction>
<!--- switch back to google's format, before adding to URL. In the Java example this is done at the bottom of signRequest() --->
<cffunction name="Base64ToWebSafe">
<cfargument name="val" />
<cfset arguments.val = replace(
replace(arguments.val, "+", "-", "ALL")
, "/", "_", "ALL") />
<cfreturn arguments.val />
</cffunction>
I chose to include these in a component, and call it as follows:
<cfset variables.requestURL = Maps.signGoogleUrl([url], [API CryptoKey]) />
The url variable is the full googleapis address including the protocol, and URL parameters like the required client variable. You can then pass that into a cfhttp call and your requests will be validated by your client and cryptoKey values! Hope this saves somebody some time, let me know if you find it useful!