Monday, October 25, 2010

Scala, Jax-RS, and GoGrid: Trouble w/calculating signature

Today's lesson: Not all time is created equally.

One important task for connecting to the GoGrid API is calculating your signature.  From their documentation, this is calculated by:

"generating an MD5 hash made up by concatenating the API key, the API user's shared secret, and a UNIX timestamp reflecting the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) when the request is made."

The keyword in this definition is: seconds since the Unix Epoch.

Unfortunately, I did not read this definition closely, and naively translated my already working Python implementation:
import md5
import time

class GoGridClient:
  """sample gogrid api client"""
  api_key = 'xxxxxxxxxxx'
  secret = 'someonesSecret'
  
  ....
  
  def getSignature(self,key,secret):
    """ create sig from md5 of key + secret + time """
    m = md5.new(key+secret+str(int(time.time())))
    return m.hexdigest()

to the following Scala implementation, with a little help from Code Redefined's post for calculating the MD5 sum in Scala:
import java.security.MessageDigest

object BrokenClient {
  val apiKey = "xxxxxxxxxxx"
  val apiSecret = "someonesSecret"
  val apiSignature: String = {
    //        m = md5.new(key+secret+str(int(time.time())))
    val signature = MessageDigest.getInstance("MD5")
    val timeStamp = System.currentTimeMillis().toString //<-- WRONG!!!!
    signature.update(apiKey.getBytes)
    signature.update(apiSecret.getBytes)
    signature.update(timeStamp.getBytes)
    signature.digest().map(0xFF & _).map { "%02x".format(_) }.foldLeft("") { _ + _ }
  }
}

And sadly kept getting a 403: Authentication Failed error. The correct way to implement this is:
import java.security.MessageDigest

object BrokenClient {
  val apiKey = "xxxxxxxxxxx"
  val apiSecret = "someonesSecret"
  val apiSignature: String = {
    //        m = md5.new(key+secret+str(int(time.time())))
    val signature = MessageDigest.getInstance("MD5")
    val timeStamp = ((java.util.Calendar.getInstance(TimeZone.getTimeZone("GMT-0:00")).getTimeInMillis()) / 1000).toString //<-- YAY ^_^
    signature.update(apiKey.getBytes)
    signature.update(apiSecret.getBytes)
    signature.update(timeStamp.getBytes)
    signature.digest().map(0xFF & _).map { "%02x".format(_) }.foldLeft("") { _ + _ }
  }
}

Note that there is some kinda weirdness going on, in that timeStamp works even if you don't pass in a time zone, or if you pass in a random time zone.

No comments:

Post a Comment