如何在PHPunit testing中获得与C#相同的HMAC256结果?

我以为我会尝试将新的签名请求逻辑添加到我的Facebookcanvas应用程序中,以使我自己“轻松”我在GitHub上访问facebook PHP sdk并查看unit testing 。

我的实际问题是我无法获得请求中包含的哈希值,以匹配我使用应用程序机密计算的哈希值以及请求中发送的数据。

在Facebook的身份validation页面上描述了这是如何工作的 。

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

在计算HMAC之前,您不应该对有效负载进行64位解码。

使用此行:

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

它应该工作。

还有一些指示:

  • 而不是摆弄Substring()IndexOf()尝试使用String.Split()
  • 你已经改变了YAY和BOO评论
  • 如果遵循使用小写启动局部变量名称的通用规则,C#代码更具可读性(如下所示: var applicationSecret = "...";

谢谢詹姆斯! 你的代码给了我很多帮助。

cdpnet,像你的项目一样添加Newtonsoft.Json,然后是这样的:

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

-Kevin