Подтвердить что ты не робот

Как я могу получить те же результаты HMAC256 на С#, что и в модульных тестах PHP?

Я подумал, что попробую и добавлю новую логику Signed Request в мое приложение для холдинга facebook, чтобы сделать это "легким" на себе. Я отправился на сайт PHP sdk в GitHub и посмотрел на модульные тесты.

Моя фактическая проблема заключается в том, что я не могу получить хеш, включенный в запрос, в соответствие с хешем, который я вычисляю, используя секрет приложения, и данные, отправленные в запросе.

Как это предназначено для работы, описано на странице Страница аутентификации Facebook.

private string VALID_SIGNED_REQUEST = "ZcZocIFknCpcTLhwsRwwH5nL6oq7OmKWJx41xRTi59E.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOiIxMjczMzU5NjAwIiwib2F1dGhfdG9rZW4iOiIyNTQ3NTIwNzMxNTJ8Mi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODV8dUk3R3dybUJVZWQ4c2VaWjA1SmJkekdGVXBrLiIsInNlc3Npb25fa2V5IjoiMi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODUiLCJ1c2VyX2lkIjoiMTY3Nzg0NjM4NSJ9";

private string NON_TOSSED_SIGNED_REQUEST = "laEjO-az9kzgFOUldy1G7EyaP6tMQEsbFIDrB1RUamE.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiJ9";

public void SignedRequestExample()
{
 var Encoding = new UTF8Encoding();

 string ApplicationSecret = "904270b68a2cc3d54485323652da4d14"; 

 string SignedRequest = VALID_SIGNED_REQUEST;
 string ExpectedSignature = SignedRequest.Substring(0, SignedRequest.IndexOf('.'));
 string Payload = SignedRequest.Substring(SignedRequest.IndexOf('.') + 1);

 // Back & Forth with Signature
 byte[] ActualSignature = FromUrlBase64String(ExpectedSignature);
 string TestSignature = ToUrlBase64String(ActualSignature);

 // Back & Forth With Data
 byte[] ActualPayload = FromUrlBase64String(Payload);
 string Json = Encoding.GetString(ActualPayload);
 string TestPayload = ToUrlBase64String(ActualPayload);

 // Attempt to get same hash
 var Hmac = SignWithHMAC(ActualPayload, Encoding.GetBytes(ApplicationSecret));
 var HmacBase64 = ToUrlBase64String(Hmac);            
 var HmacHex = BytesToHex(Hmac);

 if (HmacBase64 != ExpectedSignature)
 {
  // YAY
 }
 else
 {
  // BOO
 }
}

private static string BytesToHex(byte[] input)
{
 StringBuilder sb = new StringBuilder();

 foreach (byte b in input)
 {
  sb.Append(string.Format("{0:x2}", b));
 }
 return sb.ToString();
}
private string ToUrlBase64String(byte[] Input)
{
 return Convert.ToBase64String(Input).Replace("=", String.Empty).Replace('+', '-').Replace('/', '_');
}

// http://tools.ietf.org/html/rfc4648#section-5            
private byte[] FromUrlBase64String(string Base64UrlSafe)
{
 Base64UrlSafe = Base64UrlSafe.PadRight(Base64UrlSafe.Length + (4 - Base64UrlSafe.Length % 4) % 4, '=');
 Base64UrlSafe = Base64UrlSafe.Replace('-', '+').Replace('_', '/');
 return Convert.FromBase64String(Base64UrlSafe);
}

private byte[] SignWithHMAC(byte[] dataToSign, byte[] keyBody)
{
 using (var hmac = new HMACSHA256(keyBody))
 {
  hmac.ComputeHash(dataToSign);
  /*
  CryptoStream cs = new CryptoStream(System.IO.Stream.Null, hmac, CryptoStreamMode.Write);
  cs.Write(dataToSign, 0, dataToSign.Length);
  cs.Flush();
  cs.Close();
  byte[] hashResult = hmac.Hash;
  */
  return hmac.Hash;
 }
}

public string Base64ToHex(string input)
{
 StringBuilder sb = new StringBuilder();
 byte[] inputBytes = Convert.FromBase64String(input);
 foreach (byte b in inputBytes)
 {
  sb.Append(string.Format("{0:x2}", b));
 }
 return sb.ToString();
}

Отвечайте, спасибо Rasmus ниже, чтобы помочь кому-то еще здесь обновленный (очищенный код):

/// Example signed_request variable from PHPSDK Unit Testing
private string VALID_SIGNED_REQUEST = "ZcZocIFknCpcTLhwsRwwH5nL6oq7OmKWJx41xRTi59E.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOiIxMjczMzU5NjAwIiwib2F1dGhfdG9rZW4iOiIyNTQ3NTIwNzMxNTJ8Mi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODV8dUk3R3dybUJVZWQ4c2VaWjA1SmJkekdGVXBrLiIsInNlc3Npb25fa2V5IjoiMi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODUiLCJ1c2VyX2lkIjoiMTY3Nzg0NjM4NSJ9";

public bool ValidateSignedRequest()
{            
    string applicationSecret = "904270b68a2cc3d54485323652da4d14";
    string[] signedRequest = VALID_SIGNED_REQUEST.Split('.');            
    string expectedSignature = signedRequest[0];
    string payload = signedRequest[1];

    // Attempt to get same hash
    var Hmac = SignWithHmac(UTF8Encoding.UTF8.GetBytes(payload), UTF8Encoding.UTF8.GetBytes(applicationSecret));
    var HmacBase64 = ToUrlBase64String(Hmac);

    return (HmacBase64 == expectedSignature);           
}


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

private byte[] SignWithHmac(byte[] dataToSign, byte[] keyBody)
{
    using (var hmacAlgorithm = new HMACSHA256(keyBody))
    {
        hmacAlgorithm.ComputeHash(dataToSign);
        return hmacAlgorithm.Hash;
    }
}
4b9b3361

Ответ 1

Вы не должны base64-декодировать полезную нагрузку перед вычислением HMAC.

Используйте эту строку:

var Hmac = SignWithHMAC(Encoding.GetBytes(Payload), Encoding.GetBytes(ApplicationSecret));

и он должен работать.

Несколько указателей:

  • Вместо того, чтобы возиться с Substring() и IndexOf(), попробуйте использовать String.Split()
  • Вы переключили комментарии YAY и BOO вокруг
  • Код С# более читабельен, если вы следуете общему правилу для запуска имен локальных переменных с нижним регистром (например: var applicationSecret = "...";)

Ответ 2

Спасибо, Джеймс! Ваш код мне очень помог.

cdpnet, добавьте, как Newtonsoft.Json, к вашему проекту, а затем это:

        JObject UnencodedPayload = JObject.Parse(Encoding.GetString(ActualPayload));

Кевин