1

When Facebook sends real-time updates, they include a X-Hub-Signature in the HTTP header. According to their documentation (http://developers.facebook.com/docs/api/realtime), they're using SHA1 and the application secret as the key. I tried to verify the signature like this:

public void MyAction() {
  string signature = request.Headers["X-Hub-Signature"];
  request.InputStream.Position = 0;
  StreamReader reader = new StreamReader(request.InputStream);
  string json = reader.ReadToEnd();

  var hmac = SignWithHmac(UTF8Encoding.UTF8.GetBytes(json), UTF8Encoding.UTF8.GetBytes("MySecret"));
  var hmacBase64 = ToUrlBase64String(hmac);

  bool isValid = signature.Split('=')[1] == hmacBase64;

}


    private static byte[] SignWithHmac(byte[] dataToSign, byte[] keyBody) {
        using (var hmacAlgorithm = new System.Security.Cryptography.HMACSHA1(keyBody)) {
            hmacAlgorithm.ComputeHash(dataToSign);
            return hmacAlgorithm.Hash;
        }
    }

    private static string ToUrlBase64String(byte[] Input) {
        return Convert.ToBase64String(Input).Replace("=", String.Empty)
                                            .Replace('+', '-')
                                            .Replace('/', '_');
    }

But I can't seem to get this to ever validate. Any thoughts on what I'm doing wrong?

Thanks in advance.

Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275
  • Have you tried calculating the hash of the response bytes directly, instead of decoding the bytes to a string and encoding the string to bytes again? – dtb Dec 08 '10 at 17:51
  • 1
    Why do you take the `X-Hub-Signature` header from the `request` (which is a HttpWebRequest I assume) and not the `response` (HttpWebResponse)? – dtb Dec 08 '10 at 17:52
  • Do you mean take the request bytes directory from the body (i.e. request.InputStream)? I just tried it and I get the exact same result. I'm taking the X-Hub-Signature from the request because I'm Facebook is sending it to me in the request. – Johnny Oshika Dec 08 '10 at 20:04

2 Answers2

10

In case someone will need this information:

What Kelvin offered might work, but it seems very cumbersome. All you need is instead of using the ToUrlBase64String function just use the ConvertToHexadecimal function.

See fully updated code below:

public void MyAction() {
    string signature = request.Headers["X-Hub-Signature"];
    request.InputStream.Position = 0;
    StreamReader reader = new StreamReader(request.InputStream);
    string json = reader.ReadToEnd();

    var hmac = SignWithHmac(UTF8Encoding.UTF8.GetBytes(json), UTF8Encoding.UTF8.GetBytes("MySecret"));
    var hmacHex = ConvertToHexadecimal(hmac);

    bool isValid = signature.Split('=')[1] == hmacHex ;

}


private static byte[] SignWithHmac(byte[] dataToSign, byte[] keyBody) {
    using (var hmacAlgorithm = new System.Security.Cryptography.HMACSHA1(keyBody)) {
        return hmacAlgorithm.ComputeHash(dataToSign);
    }
}

private static string ConvertToHexadecimal(IEnumerable<byte> bytes)
{
    var builder = new StringBuilder();
    foreach (var b in bytes)
    {
        builder.Append(b.ToString("x2"));
    }

    return builder.ToString();
 }
Tomer Peled
  • 3,571
  • 5
  • 35
  • 57
  • The same code as above worked for me. However, I don't understand how Facebook's "Please note that the calculation is made on the escaped unicode version of the payload, with lower case hex digits. For example, the string äöå will be escaped to \u00e4\u00f6\u00e5" (https://developers.facebook.com/docs/messenger-platform/webhook) has anything to do with the correctly working computation above! Does anyone know? – Anthony McGrath Mar 09 '18 at 08:04
1

The code below will resolve the problem for you:

     public String hmacSha1(String keyString, byte[] in) throws GeneralSecurityException {
        Mac hmac = Mac.getInstance("HmacSHA1");
        int keySize = keyString.length();
        byte[] keyBytes = new byte[keySize];

        for (int i = 0; i < keyString.length(); i++) {
          keyBytes[i] = (byte) keyString.charAt(i);
        }

        Key key = new SecretKeySpec(keyBytes, "HmacSHA1");
        hmac.init(key);
        hmac.update(in);
        byte[] bb = hmac.doFinal();

        StringBuilder v = new StringBuilder();
        for (int i = 0; i < bb.length; i++) {
          int ch = bb[i];
          int d1 = (ch >> 4) & 0xf;
          int d2 = (ch) & 0xf;

          if (d1 < 10) {
            v.append((char) ('0' + d1));
          } else {
            v.append((char) ('a' + d1 - 10));
          }

          if (d2 < 10) {
            v.append((char) ('0' + d2));
          } else {
            v.append((char) ('a' + d2 - 10));
          }
        }
        return v.toString();
      }

  public String callback(HttpServletRequest request) throws IOException {
    InputStream in = request.getInputStream();
    StringBuilder builder = new StringBuilder();
    byte[] buffer = new byte[1024];
    while (in.read(buffer) > 0) {
      builder.append(new String(buffer));
    }
    String signature = request.getHeader("X-Hub-Signature");
    try {
      String signed = subscriptionService.hmacSha1("YOUR_SECRET", builder.toString().getBytes());
      if (signature.startsWith("sha1=") && signature.substring(4).equals(signed)) {
        // process the update
        ....
      }
    } catch (GeneralSecurityException ex) {
      log.warn(ex.getMessage());
    }

    return null;
  }
Kelvin
  • 11
  • 1