Facebook has finally fixed a rather persistent bug with the New SDK migration option and now provides us with a signed_request parameter that is described here. This parameter provides us with all the parameters we need for building tab apps and accessing the OpenGraph API.
However, I spend quite some time until I was able to check the signature correctly. The example uses PHP, and their are some slight discrepancies to be solved. Here is the code for the parsing, without the usual error checking:
/* split the string into signature and payload */
int idx = sigreq.indexOf(".");
byte[] sig = new Base64(true).decode(sigreq.substring(0, idx).getBytes());
String rawpayload = sigreq.substring(idx+1);
String payload = new String(new Base64(true).decode(rawpayload));
/* parse the JSON payload and do the signature check */
FacebookRequest ret = new Gson().fromJson(payload, FacebookRequest.class);
/* check if it is HMAC-SHA256 */
if (!ret.getAlgorithm().equals("HMAC-SHA256")) {
/* note that this follows facebooks example, as published on 2010-07-21 (I wonder when this will break) */
throw new SocialNetworkingException("Unexpected hash algorithm " + ret.getAlgorithm());
}
/* then check the signature */
checkSignature(rawpayload, sig);
return ret;
Base64 is from the commons-codec package (you need version 1.4), and Gson is Google’s JSON-to-Java converter.
Note that the signature check is done on the base64-encoded string (rather than on the decoded payload). The check for the algorithm follows Facebook’s example code: we will see that we require this check in the signature checking part:
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), SIGN_ALGORITHM);
Mac mac = Mac.getInstance(SIGN_ALGORITHM);
mac.init(secretKeySpec);
byte[] mysig = mac.doFinal(rawpayload.getBytes());
if (!Arrays.equals(mysig, sig)) {
throw new SocialNetworkingException("Non-matching signature for request");
}
} catch (NoSuchAlgorithmException e) {
throw new SocialNetworkingException("Unknown hash algorithm " + SIGN_ALGORITHM, e);
} catch (InvalidKeyException e) {
throw new SocialNetworkingException("Wrong key for " + SIGN_ALGORITHM, e);
}
(Edit: In a previous version of this post, I messed up the signature checking part, writing “payload” instead of “rawpayload” as the input to the signature check. This happened because I just copy&pasted the code from my IDE, where it sits in two different methods. Thanks to John Hay for pointing that out, and sorry to those that lost time because of this.)
The SIGN_ALGORITHM is defined as
private static final String SIGN_ALGORITHM = "HMACSHA256";
Of course, Facebook provides us with a different string, so we cannot reuse this one (maybe we could just remove the “-”?).
The key insight of this code is that the secret, as it is provided to you by Facebook on the Application overview page (a hex string) is used verbatim, and not decoded.
All this is not hard, but cryptography is designed to either work correctly or to be as undebugable as possible, hence it is easy to loose quite some time. Hope this code helps to avoid that.
I’ve tried following the code above with no luck. I have the JSON object in the payload correctly parsed, I just can’t get the signatures to match. Any chance that the cryptographic APIs are platform specific? Not sure if java is making any native calls here.
The code above is incorrect as written. You should not decode the payload before hashing. In other words, you simply want to use the secret application key to hash the given payload string.
The hash line should read:
byte[] mysig = mac.doFinal( rawpayload.getBytes() );
I’m having problems recreating the JSON object, would you be able to provide source for the FacebookRequest, and give anymore info on how it was constructed?
FacebookRequest ret = new Gson().fromJson(payload, FacebookRequest.class);
particularly the getAlgorithm() method, where does that come from?
Thanks D
From what package did you get FacebookRequest?
Self-written… just a bean with the properties of the JSON
what does the method getAlgorithm() from FacebookRequest ?
ok i understood that algorithm comes from signed_request.
Now the checkSignature never matches for me
Just wanted to say thank you for your explanation! Saved me a bit of head-scratching!
Cheers!
Luke
Just wanted to say thanks, this works great.
Line 3 in the first code snippet should fix the typo “.getBytes()” instead of “.getBytes[]“. You can remove this comment if you fix it.
Really helpful post, thank you.