Constructing and validating a Gigya signature
base64, encoding, gigya, java
Solution
Well I finally heard back from gigya yesterday regarding this issue, and it turns out their own server-side Java API exposes a method for handling this use case, `SigUtils.validateUserSignature`:
if (SigUtils.validateUserSignature(uid, timestamp, secretKey, signature)) { ... }
Today I was able to verify that this call is behaving correctly, so that solves the immediate issue and turns this whole post into a kind of a facepalm moment for me.
However:
I'm still interested in why my own home-rolled method doesn't work (and I have a bounty to award anyway). I'll examine it again this coming week and compare it with the `SigUtils` class file to try and figure out what went wrong.
Problem
I wrote a method to verify a gigya signature against a specified timestamp and UID, based on Gigya's instructions for constructing a signature. Here is Gigya's psuedo code for doing that: ``` string constructSignature(string timestamp, string UID, string secretKey) { // Construct a "base string" for signing baseString = timestamp + "_" + UID; // Convert the base string into a binary array binaryBaseString = ConvertUTF8ToBytes(baseString); // Convert secretKey from BASE64 to a binary array binaryKey = ConvertFromBase64ToBytes(secretKey); // Use the HMAC-SHA1 algorithm to calculate the signature binarySignature = hmacsha1(binaryKey, baseString); // Convert the signature to a BASE64 signature = ConvertToBase64(binarySignature); return signature; } ``` [sic] Here's my method (exception handling omitted): ``` public boolean verifyGigyaSig(String uid, String timestamp, String signature) { // Construct the "base string" String baseString = timestamp + "_" + uid; // Convert the base string into a binary array byte[] baseBytes = baseString.getBytes("UTF-8"); // Convert secretKey from BASE64 to a binary array String secretKey = MyConfig.getGigyaSecretKey(); byte[] secretKeyBytes = Base64.decodeBase64(secretKey); // Use the HMAC-SHA1 algorithm to calculate the signature Mac mac = Mac.getInstance("HmacSHA1"); mac.init(new SecretKeySpec(secretKeyBytes, "HmacSHA1")); byte[] signatureBytes = mac.doFinal(baseBytes); // Convert the signature to a BASE64 String calculatedSignature = Base64.encodeBase64String(signatureBytes); // Return true iff constructed signature equals specified signature return signature.equals(calculatedSignature); } ``` This method is returning `false` even when it shouldn't. Can anyone spot something wrong with my implementation? I'm wondering if there could be an issue with the caller or gigya itself - "Your method checks out" is a valid answer. I'm using Apache Commons' `Base64` class for encoding. Further (somewhat redundant) info on signatures is also found in Gigya's FAQ, in case that helps. To clarify this further: `uid`, `timestamp`, and `signature` are all being taken from cookies set by gigya. In order to verify these aren't being spoofed, I'm taking `uid` and `timestamp`, and making sure `signature` can be reconstructed using my secret key. The fact that it fails when it shouldn't indicates a bug/format issue at some point in the process, either with my method, in the front-end, or with gigya itself. The purpose of this question is essentially to rule out a bug in the above method. Note: I've also tried URL-encoding `uid`: ``` String baseString = timestamp + "_" + URLEncoder.encode(uid, "UTF-8"); ``` Though I wouldn't think this would matter since it's just an integer. The same goes for `timestamp`. Update: The underlying issue has been solved, however the question itself remains open. See my answer for more details. Update 2: It turns out I was confused about which of apache's `Base64` classes I was using - my code was not using the Commons Codec version but the Commons Net version. This confusion arose from my project's large amount of third-party libraries and my ignorance of the many `Base64` implementations over the years from Apache libraries - a situation I now realize Commons Codec was meant to address. Looks like I'm late to the party when it comes to encoding. After switching in Commons Codec's version, the method behaves correctly. I'm going to award the bounty to @erickson since his answer was spot on, but please upvote both answers for their excellent insight! I'll leave the bounty open for now so they get the attention they deserve.