Reputation: 674
So, I'm working on validating Facebook's signed_request using Java. Unfortunately, I keep running into issues with the validation process. I've looked at this documentation, and emulated their algorithm, to no success. I've also followed this tutorial, and continued to come up with my calculated signature being different from the one that Facebook sent.
Or at least, that was what String.equals() was telling me.
So I decided to poke at it some more.
I set it up to iterate over the bytes in my calculated signature and the provided ones. Low and behold, the first 32 bytes of my signature matched theirs exactly. It was just missing another 400+ bytes of data.
At that point, I decided I should probably get a better notion of what the heck was going on. I looked up SHA-256, and found that, indeed, it only creates 32 bytes of information. So then I'm left with over 400 bytes of data which Facebook claims was generated using the HMAC SHA-256 algorithm. I thought I should compare the maximum length of SHA-256 with the length of the data I'm hashing, but that just showed that there was way, way, way a lot of room to spare (Message size: 575 bytes; Maximum Size: 2.305843009213694 x 10^18 bytes).
Is Facebook making shit up? Or am I missing something?
edit
This is the function I use to hash the data. I pass in my facebook secret code (for key), and the base64url encoded JSON object (for data). It consistently returns a byte array of length 32 with data that matches the first 32 bytes of the signature provided by facebook.
private byte[] hmacSHA256(String data, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
mac.update(data.getBytes("UTF-8"));
byte[] hmacData = mac.doFinal();
return hmacData;
}
Upvotes: 2
Views: 5833
Reputation: 96
Since I also came in here looking for an answer to a similar problem. This is the code that works for me:
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
private JSONObject parseFBSignedRequest(String signedRequest, String secret) throws UnsupportedEncodingException, Exception {
//split request into signature and data
String[] signedRequests = signedRequest.split("\\.", 2);
//parse signature
String sig = signedRequests[0];
//parse data and convert to json object
String data = signedRequests[1];
//I assumed it is UTF8
JSONObject jsonData = new JSONObject(new String(Base64.decodeBase64(data), "UTF-8"));
//check signature algorithm
if(!jsonData.getString("algorithm").equals("HMAC-SHA256")) {
//unknown algorithm is used
return null;
}
//check if data is signed correctly
if(!hmacSHA256(signedRequests[1], secret).equals(sig)) {
//signature is not correct, possibly the data was tampered with
return null;
}
return jsonData;
}
//HmacSHA256 implementation
private String hmacSHA256(String data, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] hmacData = mac.doFinal(data.getBytes("UTF-8"));
return new String(Base64.encodeBase64URLSafe(hmacData), "UTF-8");
}
Upvotes: 6
Reputation: 96407
I looked up SHA-256, and found that, indeed, it only creates 32 bytes of information. So then I'm left with over 400 bytes of data which Facebook claims was generated using the HMAC SHA-256 algorithm.
The data of the signed request is not „created” using HMAC SHA-256 – it’s signed with it.
The first part of the signed request, before the dot, is the signature – the rest is the data payload. You have to hash that payload data and compare the hash your getting with the signature – these two should match, to prove the signed request is genuine.
If that doesn’t help you see clearer, show us some code please.
Upvotes: 0