2010-08-02 6 views
18

Pomyślałem, że spróbuję dodać nową logikę Żądania Podpisanego do mojej aplikacji na facebooku, żeby to było "łatwe" dla siebie Poszedłem na facebookowy sdk PHP na GitHub i przyjrzałem się unit tests.Jak mogę uzyskać takie same wyniki HMAC256 w języku C#, jak w testach jednostkowych PHP?

Moim rzeczywistym problemem jest to, że nie mogę uzyskać wartości mieszania zawartej w żądaniu, aby dopasować hasz obliczany przy użyciu tajnego klucza aplikacji i danych wysłanych w ramach żądania.

Sposób działania tej funkcji opisano pod adresem Facebook's authentication page.

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(); 
} 

odpowiedzi dzięki Rasmus poniżej, aby pomóc ktoś tu jest aktualizowane (oczyścić kod):

/// 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; 
    } 
} 
+1

mam zamiar iść w opałach tutaj i powiedzieć, że jesteś C/C++, który ledwo zna dowolny C#. –

+0

Witam Steven, nie tak nowy, właśnie dzisiaj wpadłem w złe miejsce. Myśl, że zbiorowe umysły przepełnienia stosu mogą mnie wydostać. – CameraSchoolDropout

+0

Czy nie masz na myśli 'return (HmacBase64 == expectedSignature);'? Spodziewam się, że metoda zwróci true, jeśli podpis był poprawny. –

Odpowiedz

17

Nie mają base64-dekodowania ładunek przed obliczeniem HMAC.

Użyj tej linii:

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

i powinno działać.

Jeszcze kilka wskazówek:

  • Zamiast błahy z Substring() i IndexOf() spróbować wykorzystać String.Split()
  • włączeniu się yay i Boo komentarzy całym
  • kod C# jest bardziej czytelny, jeśli się wspólna zasada uruchamiania nazw zmiennych lokalnych małymi literami (jak poniżej: var applicationSecret = "...";)
+0

Dzięki Rasmus - doceń swoją pomoc! – CameraSchoolDropout

+0

Jeśli nie chcesz robić tego samemu, po prostu użyj Facebooka.Net SDK na Codeplex. Przekaże podpisaną prośbę. http://facebooksdk.codeplex.com –

+0

@NathanTotten Czy mógłbyś mi powiedzieć, który class.method użyć do generowania skrótu, mam sekret i klucz i potrzebuję wygenerowanego skrótu? – Omu

0

Dziękuję, James! Twój kod bardzo mi pomógł.

cdpnet, jak Newtonsoft.Json dodać do projektu, a potem to tak:

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

-Kevin

+0

Bez problemów Kevin - cieszę się, że było użyteczne. – CameraSchoolDropout