如何在 C# 中获得与 PHP 单元测试相同的 HMAC256 结果?

时间:2023-03-28
本文介绍了如何在 C# 中获得与 PHP 单元测试相同的 HMAC256 结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我想我会尝试将新的签名请求逻辑添加到我的 facebook 画布应用程序中,以使这对我自己轻松"我去了 GitHub 上的 facebook PHP sdk 并查看了 单元测试.

我的实际问题是我无法让请求中包含的哈希与我使用应用程序秘密计算的哈希以及请求中发送的数据相匹配.

Facebook 的身份验证页面中描述了这是如何工作的.

<预> <代码>私人字符串VALID_SIGNED_REQUEST = ZcZocIFknCpcTLhwsRwwH5nL6oq7OmKWJx41xRTi59E.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOiIxMjczMzU5NjAwIiwib2F1dGhfdG9rZW4iOiIyNTQ3NTIwNzMxNTJ8Mi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODV8dUk3R3dybUJVZWQ4c2VaWjA1SmJkekdGVXBrLiIsInNlc3Npb25fa2V5IjoiMi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODUiLCJ1c2VyX2lkIjoiMTY3Nzg0NjM4NSJ9";私有字符串 NON_TOSSED_SIGNED_REQUEST = "laEjO-az9kzgFOULdy1G7EyaP6tMQEsbFIDrB1RUamE.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiJ9";公共无效签名请求示例(){var 编码 = 新的 UTF8Encoding();字符串 ApplicationSecret = "904270b68a2cc3d54485323652da4d14";字符串 SignedRequest = VALID_SIGNED_REQUEST;字符串 ExpectedSignature = SignedRequest.Substring(0, SignedRequest.IndexOf('.'));字符串有效负载 = SignedRequest.Substring(SignedRequest.IndexOf('.') + 1);//返回 &带签名的第四个byte[] ActualSignature = FromUrlBase64String(ExpectedSignature);字符串 TestSignature = ToUrlBase64String(ActualSignature);//返回 &数据byte[] ActualPayload = FromUrlBase64String(Payload);字符串 Json = Encoding.GetString(ActualPayload);字符串 TestPayload = ToUrlBase64String(ActualPayload);//尝试获取相同的哈希var Hmac = SignWithHMAC(ActualPayload, Encoding.GetBytes(ApplicationSecret));var HmacBase64 = ToUrlBase64String(Hmac);var HmacHex = BytesToHex(Hmac);if (HmacBase64 != ExpectedSignature){//耶}别的{//嘘}}私有静态字符串 BytesToHex(byte[] 输入){StringBuilder sb = new StringBuilder();foreach(输入中的字节 b){sb.Append(string.Format("{0:x2}", b));}返回 sb.ToString();}私有字符串 ToUrlBase64String(byte[] 输入){return Convert.ToBase64String(Input).Replace("=", String.Empty).Replace('+', '-').Replace('/', '_');}//http://tools.ietf.org/html/rfc4648#section-5私有字节 [] FromUrlBase64String(字符串 Base64UrlSafe){Base64UrlSafe = Base64UrlSafe.PadRight(Base64UrlSafe.Length + (4 - Base64UrlSafe.Length % 4) % 4, '=');Base64UrlSafe = Base64UrlSafe.Replace('-', '+').Replace('_', '/');返回 Convert.FromBase64String(Base64UrlSafe);}私有 byte[] SignWithHMAC(byte[] dataToSign, byte[] keyBody){使用 (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.关闭();byte[] hashResult = hmac.Hash;*/返回hmac.Hash;}}公共字符串 Base64ToHex(字符串输入){StringBuilder sb = new StringBuilder();byte[] inputBytes = Convert.FromBase64String(input);foreach(输入字节中的字节 b){sb.Append(string.Format("{0:x2}", b));}返回 sb.ToString();}

感谢下面的 Rasmus 的回答,为了帮助其他人,这里是更新的(清理代码):

///PHPSDK 单元测试中的签名请求变量示例私人字符串VALID_SIGNED_REQUEST = ZcZocIFknCpcTLhwsRwwH5nL6oq7OmKWJx41xRTi59E.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOiIxMjczMzU5NjAwIiwib2F1dGhfdG9rZW4iOiIyNTQ3NTIwNzMxNTJ8Mi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODV8dUk3R3dybUJVZWQ4c2VaWjA1SmJkekdGVXBrLiIsInNlc3Npb25fa2V5IjoiMi5JX2VURmtjVEtTelg1bm8zakk0cjFRX18uMzYwMC4xMjczMzU5NjAwLTE2Nzc4NDYzODUiLCJ1c2VyX2lkIjoiMTY3Nzg0NjM4NSJ9";公共布尔 ValidateSignedRequest(){字符串 applicationSecret = "904270b68a2cc3d54485323652da4d14";字符串[] 签名请求 = VALID_SIGNED_REQUEST.Split('.');字符串预期签名=签名请求[0];字符串有效负载 = 签名请求 [1];//尝试获取相同的哈希var Hmac = SignWithHmac(UTF8Encoding.UTF8.GetBytes(payload), UTF8Encoding.UTF8.GetBytes(applicationSecret));var HmacBase64 = ToUrlBase64String(Hmac);返回(HmacBase64 == 预期签名);}私有字符串 ToUrlBase64String(byte[] 输入){返回 Convert.ToBase64String(Input).Replace("=", String.Empty).替换('+', '-').代替('/', '_');}私有 byte[] SignWithHmac(byte[] dataToSign, byte[] keyBody){使用 (var hmacAlgorithm = new HMACSHA256(keyBody)){hmacAlgorithm.ComputeHash(dataToSign);返回 hmacAlgorithm.Hash;}}

解决方案

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

使用这一行:

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

它应该可以工作.

还有几点建议:

  • 不要摆弄 Substring()IndexOf() 尝试使用 String.Split()
  • 您已将 YAY 和 BOO 评论调换了
  • 如果您遵循以小写开头的局部变量名称的通用规则(例如:var applicationSecret = "...";),则 C# 代码更具可读性.

I thought I would try and get the new Signed Request logic added to my facebook canvas application, to make this "easy" on myself I went to the facebook PHP sdk over at GitHub and took a look at the unit tests.

My actual problem is that I cannot get the hash included in the request to match the hash I calculate using the application secret, and the data sent within the request.

How this is meant to work is described at 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();
}

Answer thanks to Rasmus below, to assist anyone else here is the updated (cleaned up code):

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

解决方案

You are not supposed to base64-decode the payload before calculating the HMAC.

Use this line:

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

and it should work.

A few more pointers:

  • Instead of fiddling with Substring() and IndexOf() try using String.Split()
  • You have switched the YAY and BOO comments around
  • C# code is more readable if you follow the common rule of starting the names of local variables with lowercase (like this: var applicationSecret = "...";)

这篇关于如何在 C# 中获得与 PHP 单元测试相同的 HMAC256 结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:.net 中的混合密码系统实现.错误 指定的密钥不是此算法的有效大小 下一篇:rsacryptoserviceprovider 使用 x509 证书 c#

相关文章