Search code examples
javaencodingbase64gigya

Constructing and validating a Gigya signature


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.


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.