I'm trying to convert this PHP cookie parsing snippet into C#, but my PHP is a bit rusty. It's taken from a facebook SDK sample.
<?php
define('FACEBOOK_APP_ID', 'your application id');
define('FACEBOOK_SECRET', 'your application secret');
function get_facebook_cookie($app_id, $application_secret) {
$args = array();
parse_str(trim($_COOKIE['fbs_' . $app_id], '\\"'), $args);
ksort($args);
$payload = '';
foreach ($args as $key => $value) {
if ($key != 'sig') {
$payload .= $key . '=' . $value;
}
}
if (md5($payload . $application_secret) != $args['sig']) {
return null;
}
return $args;
}
$cookie = get_facebook_cookie(FACEBOOK_APP_ID, FACEBOOK_SECRET);
echo 'The ID of the current user is ' . $cookie['uid'];
?>
This is what I have so far, but its not quite right:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
HttpCookie cookie = GetCookie();
IsLoggedIn = cookie != null;
}
private HttpCookie GetCookie()
{
// based on the php example at http://developers.facebook.com/docs/authentication/
HttpCookie cookie = Request.Cookies["fbs_" + FacebookClientId];
StringBuilder payload = new StringBuilder();
if (cookie != null)
{
foreach (string key in cookie.Values.Keys)
{
if (key != "sig")
{
payload.Append(key + "=" + cookie.Values[key]);
}
}
string sig = cookie.Values["sig"];
if (sig == GetMD5Hash(payload.ToString()))
{
return cookie;
}
}
return null;
}
public string GetMD5Hash(string input)
{
MD5CryptoServiceProvider cryptoServiceProvider = new MD5CryptoServiceProvider();
byte[] bytes = Encoding.UTF8.GetBytes(input);
bytes = cryptoServiceProvider.ComputeHash(bytes);
StringBuilder s = new StringBuilder();
foreach (byte b in bytes)
{
s.Append(b.ToString("x2").ToLower());
}
return s.ToString();
}
The one part I'm not sure about is parse_str(trim($_COOKIE['fbs_' . $app_id], '\\"'), $args);. From what I can tell its creating an array out of the trimmed cookie value. Can anyone provide some assistance?
If I'm reading it correctly:
trim($_COOKIE['fbs_' . $app_id], '\\"')
Will trim \ and " from the beginning and end of the value stored in the cookie named fbs_FACEBOOK_APP_ID (The double back-slashes escape the back-slash in a single quoted string. And trim can be told what characters to trim from the string.)
Meanwhile, parse_str then parses that as if it were a query string, into an associative array. So, I'd assume that the value of that cookie should look like a query string.
Hope this helps.
There were a few issues in my original C# version.
I forgot to include the FacebookSecret as salt in the MD5 hash.
The cookie.Value needs to be trimmed and parsed as a query string as George Marian and Alex JL explained.
The parsed cookie values need to be UrlDecoded. I guess ASP.NET UrlEncodes them when creating the Cookie object.
The default encoding should be used to create the hash, not UTF8.
Here's the working solution:
private HttpCookie GetCookie()
{
// based on the php example at http://developers.facebook.com/docs/guides/canvas/#canvas
HttpCookie cookie = Request.Cookies["fbs_" + FacebookClientId];
if (cookie != null)
{
var pairs = from pair in cookie.Value.Trim('"', '\\').Split('&')
let indexOfEquals = pair.IndexOf('=')
orderby pair
select new
{
Key = pair.Substring(0, indexOfEquals).Trim(),
Value = pair.Substring(indexOfEquals + 1).Trim()
};
IDictionary<string, string> cookieValues =
pairs.ToDictionary(pair => pair.Key, pair => Server.UrlDecode(pair.Value));
StringBuilder payload = new StringBuilder();
foreach (KeyValuePair<string, string> pair in cookieValues)
{
Response.Write(pair.Key + ": " + pair.Value + "<br/>\n");
if (pair.Key != "sig")
{
payload.Append(pair.Key + "=" + pair.Value);
}
}
string sig = cookieValues["sig"];
string hash = GetMd5Hash(payload + FacebookSecret);
if (sig == hash)
{
return cookie;
}
}
return null;
}
private static string GetMd5Hash(string input)
{
MD5CryptoServiceProvider cryptoServiceProvider = new MD5CryptoServiceProvider();
byte[] bytes = Encoding.Default.GetBytes(input);
byte[] hash = cryptoServiceProvider.ComputeHash(bytes);
StringBuilder s = new StringBuilder();
foreach (byte b in hash)
{
s.Append(b.ToString("x2"));
}
return s.ToString();
}
Related
I have to rewrite this php code to c#
$sign_params = ['name'=>"John", 'age'=>18];
$client_secret = 'test secret key';
ksort($sign_params); // Sort array by keys
$sign_params_query = http_build_query($sign_params); // Forming string like "param_name1=value¶m_name2=value"
$sign = rtrim(strtr(base64_encode(hash_hmac('sha256', $sign_params_query, $client_secret, true)), '+/', '-_'), '='); // get hash code
return $sign;
Here what I try:
public class apiHelper : MonoBehaviour
{
const string client_secret = "test secret key";
public static string checkSign(Dictionary<string, dynamic> fields)
{
//* sort by keys
var list = fields.Keys.ToList();
list.Sort();
string sign_params_query = "";
//* forming string like "param_name1=value¶m_name2=value"
foreach (var key in list)
{
sign_params_query = sign_params_query + key + "=" + fields[key];
if (key != list.Last()) sign_params_query = sign_params_query + "&";
}
//* get hash code
string sign = System.Convert.ToBase64String(GetHash(sign_params_query, client_secret));
char[] charsToTrim = { '=' };
return sign.Replace("+", "-").TrimEnd(charsToTrim);
}
static byte[] GetHash(string url, string key)
{
using (HMACSHA256 hmac = new HMACSHA256(Encoding.ASCII.GetBytes(key)))
{
byte[] data = hmac.ComputeHash(Encoding.UTF8.GetBytes(url));
return data;
}
}
}
Well, finally I get different hash than in php example ._. What did I wrong? Its my 1st time with cryptho or smth like that
Well, problem wath with strings which have special chars. I have to use url_encode c# equivalent to solve it.
I got "The account name or password that you have entered is incorrect" error when trying login using this api endpoint: https://steamcommunity.com/login/dologin/
I am using the credentials I use when logging in via Steam app or Steam web, so I don't think I have a problem with my credentials.
Here code which code I use:
public bool DoLogin(string username, string password)
{
var data = new NameValueCollection { { "username", username } };
// First get the RSA key with which we will encrypt our password.
string response = Fetch("https://steamcommunity.com/login/getrsakey", "POST", data, false);
GetRsaKey rsaJson = JsonConvert.DeserializeObject<GetRsaKey>(response);
// Validate, if we could get the rsa key.
if (!rsaJson.success)
{
return false;
}
// RSA Encryption.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaParameters = new RSAParameters
{
Exponent = HexToByte(rsaJson.publickey_exp),
Modulus = HexToByte(rsaJson.publickey_mod)
};
rsa.ImportParameters(rsaParameters);
// Encrypt the password and convert it.
byte[] bytePassword = Encoding.ASCII.GetBytes(password);
byte[] encodedPassword = rsa.Encrypt(bytePassword, false);
string encryptedBase64Password = Convert.ToBase64String(encodedPassword);
SteamResult loginJson = null;
CookieCollection cookieCollection;
string steamGuardText = "";
string steamGuardId = "";
// Do this while we need a captcha or need email authentification. Probably you have misstyped the captcha or the SteamGaurd code if this comes multiple times.
do
{
Console.WriteLine("SteamWeb: Logging In...");
bool captcha = loginJson != null && loginJson.captcha_needed;
bool steamGuard = loginJson != null && loginJson.emailauth_needed;
string time = Uri.EscapeDataString(rsaJson.timestamp);
string capGid = string.Empty;
// Response does not need to send if captcha is needed or not.
// ReSharper disable once MergeSequentialChecks
if (loginJson != null && loginJson.captcha_gid != null)
{
capGid = Uri.EscapeDataString(loginJson.captcha_gid);
}
data = new NameValueCollection { { "password", encryptedBase64Password }, { "username", username } };
// Captcha Check.
string capText = "";
if (captcha)
{
Console.WriteLine("SteamWeb: Captcha is needed.");
System.Diagnostics.Process.Start("https://steamcommunity.com/public/captcha.php?gid=" + loginJson.captcha_gid);
Console.WriteLine("SteamWeb: Type the captcha:");
string consoleText = Console.ReadLine();
if (!string.IsNullOrEmpty(consoleText))
{
capText = Uri.EscapeDataString(consoleText);
}
}
data.Add("captchagid", captcha ? capGid : "-1");
data.Add("captcha_text", captcha ? capText : "");
// Captcha end.
// Added Header for two factor code.
data.Add("twofactorcode", "");
// Added Header for remember login. It can also set to true.
data.Add("remember_login", "false");
// SteamGuard check. If SteamGuard is enabled you need to enter it. Care probably you need to wait 7 days to trade.
// For further information about SteamGuard see: https://support.steampowered.com/kb_article.php?ref=4020-ALZM-5519&l=english.
if (steamGuard)
{
Console.WriteLine("SteamWeb: SteamGuard is needed.");
Console.WriteLine("SteamWeb: Type the code:");
string consoleText = Console.ReadLine();
if (!string.IsNullOrEmpty(consoleText))
{
steamGuardText = Uri.EscapeDataString(consoleText);
}
steamGuardId = loginJson.emailsteamid;
// Adding the machine name to the NameValueCollection, because it is requested by steam.
Console.WriteLine("SteamWeb: Type your machine name:");
consoleText = Console.ReadLine();
var machineName = string.IsNullOrEmpty(consoleText) ? "" : Uri.EscapeDataString(consoleText);
data.Add("loginfriendlyname", machineName != "" ? machineName : "defaultSteamBotMachine");
}
data.Add("emailauth", steamGuardText);
data.Add("emailsteamid", steamGuardId);
// SteamGuard end.
// Added unixTimestamp. It is included in the request normally.
var unixTimestamp = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
// Added three "0"'s because Steam has a weird unix timestamp interpretation.
data.Add("donotcache", unixTimestamp + "000");
data.Add("rsatimestamp", time);
// Sending the actual login.
using (HttpWebResponse webResponse = Request("https://steamcommunity.com/login/dologin/", "POST", data, false))
{
var stream = webResponse.GetResponseStream();
if (stream == null)
{
return false;
}
using (StreamReader reader = new StreamReader(stream))
{
string json = reader.ReadToEnd();
loginJson = JsonConvert.DeserializeObject<SteamResult>(json);
cookieCollection = webResponse.Cookies;
}
}
} while (loginJson.captcha_needed || loginJson.emailauth_needed);
// If the login was successful, we need to enter the cookies to steam.
if (loginJson.success)
{
_cookies = new CookieContainer();
foreach (Cookie cookie in cookieCollection)
{
_cookies.Add(cookie);
}
SubmitCookies(_cookies);
return true;
}
else
{
Console.WriteLine("SteamWeb Error: " + loginJson.message);
return false;
}
}
enter image description here
Is there another solution how to login to steam and get html?
Ok, so I checked your encrypting and it looks fine. It is a little bit of chaos in your code so i will explain it with my:
I prefer to use RestSharp and Newton Soft JSON to do this, so a little mandatory stuff in Class body:
private IRestClient restClientTemporary;
private string getKeysURL = "/login/getrsakey/";
private string loginWithKey = "/login/dologin/";
Create RestClient, and RestRequests:
restClientTemporary = new RestClient("https://steamcommunity.com");
var request = new RestRequest(getKeysURL, Method.POST);
request.AddParameter("username", "YourSteamLogin");
var resp = restClientTemporary.Execute(request);
GetRsaResult response = Newtonsoft.Json.JsonConvert.DeserializeObject<GetRsaResult>(resp.Content);
Then i used your code as method to encrypt:
public static string EncryptionSof(string password, GetRsaResult response)
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
RSAParameters rsaParameters = new RSAParameters
{
Exponent = HexToByte(response.publickey_exp),
Modulus = HexToByte(response.publickey_mod)
};
rsa.ImportParameters(rsaParameters);
// Encrypt the password and convert it.
byte[] bytePassword = Encoding.ASCII.GetBytes(password);
byte[] encodedPassword = rsa.Encrypt(bytePassword, false);
return Convert.ToBase64String(encodedPassword);
}
And then used this method:
string password = EncryptionSof("admin123/*its your steam password i think*/", response);
Next step is make request to get login data:
var loginRequest = new RestRequest(loginWithKey);
loginRequest.AddParameter("username", "YourSteamLogin");
loginRequest.AddParameter("password", password);
loginRequest.AddParameter("rsatimestamp", response.timestamp);
loginRequest.AddParameter("remember_login", false);
//Captcha stuff if needed:
loginRequest.AddParameter("captchagid", 3086601225255895896);
loginRequest.AddParameter("captcha_text", "LHYJ2P");
And finally execute it:
var responseFinal = restClientTemporary.Execute(loginRequest);
In response i received everything i need in responseFinal.Content:
{
"success":true,
"requires_twofactor":false,
"login_complete":true,
"transfer_urls":[
"https:\\/\\/store.steampowered.com\\/login\\/transfer",
"https:\\/\\/help.steampowered.com\\/login\\/transfer"
],
"transfer_parameters":{
"steamid":"12344567898765432",
"token_secure":"xDDDDDDDDD",
"auth":"LOOOOOL",
"remember_login":false
}
}
GetRsaResult model looks like:
public class GetRsaResult
{
public bool success { get; set; }
public string publickey_mod { get; set; }
public string publickey_exp { get; set; }
public string timestamp { get; set; }
public string token_gid { get; set; }
}
Ah, and i forget about changing 16-string to byte[], its method found on Stackoverflow:
public static byte[] HexToByte(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
And you have to remember one thing You never want to send plaintext password to steam. First request /login/getrsakey/ is just only for get keys to encrypt password. Steam gives you key to encrypt your password. So, you will use your plaintext password (in my sample its admin123 ) in your program to encrypt it with keys you received from Steam.
In second request /login/dologin/ you must send encrypted password (result of EncryptionSoF method)
i have a method that i need to remake in C# based on this python code.
def _generateHash(self, password, time_stamp, nonce):
import hashlib
shaPw = hashlib.sha1()
shaPw.update( password )
m = hashlib.sha1()
m.update(str(time_stamp))
m.update(nonce)
m.update(shaPw.hexdigest())
m.update(self.api_key_secret)
return m.hexdigest()
hashing in C# is allot different compared to python. also my hashing experience is not that great. is there anybody that can help me?
this is wat i have right now.
private string GenerateHash(string password, double timeStamp, string nonce)
{
using (SHA1Managed sha1 = new SHA1Managed())
{
var pwHash = sha1.ComputeHash(Encoding.UTF8.GetBytes(password));
using (SHA1Managed sha1total = new SHA1Managed())
{
sha1total.ComputeHash(Encoding.UTF8.GetBytes(timeStamp.ToString()));
sha1total.ComputeHash(Encoding.UTF8.GetBytes(nonce));
string hexaHashPW = "";
foreach (byte b in pwHash)
{
hexaHashPW += String.Format("{0:x2}", b);
}
sha1total.ComputeHash(Encoding.UTF8.GetBytes(hexaHashPW));
sha1total.ComputeHash(Encoding.UTF8.GetBytes(_SecretApiKey));
var hmac = new HMACSHA1();
//string hexaHashTotal = "";
//foreach (byte b in sha1total.Hash)
//{
// hexaHashTotal += String.Format("{0:x2}", b);
//}
hmac.ComputeHash(sha1total.Hash);
var hexaHashTotal = hmac.Hash;
var endhash = BitConverter.ToString(hexaHashTotal).Replace("-", "");
return endhash;
}
}
}
after even more research and trail and error i found the way to produce the same hash as the python code.
this is the answer for others that have problems with this.
private string GenerateHash(string password, double timeStamp, string nonce)
{
using (SHA1Managed sha1 = new SHA1Managed())
{
var pwHash = sha1.ComputeHash(Encoding.UTF8.GetBytes(password));
using (SHA1Managed sha1total = new SHA1Managed())
{
string hexaHashPW = "";
foreach (byte b in pwHash)
{
hexaHashPW += String.Format("{0:x2}", b);
}
var hmacPW = new HMACSHA1();
hmacPW.ComputeHash(pwHash);
sha1total.ComputeHash(Encoding.UTF8.GetBytes(timeStamp.ToString() + nonce + hexaHashPW + _SecretApiKey));
var hmac = new HMACSHA1();
string hexaHashTotal = "";
foreach (byte b in sha1total.Hash)
{
hexaHashTotal += String.Format("{0:x2}", b);
}
hmac.ComputeHash(sha1total.Hash);
return hexaHashTotal.ToLower();
}
}
}
I want to add multiple query string key value pairs to an url by formatting a regular string and then appending this to the current query string. Because, in my knowledge, there is no way to change the actual Request.QueryString. Therefore I try to append the kvp:s to the query string as per below. I have searched StackOverflow, but I couldn't find an answer that matches my problem.
protected void ServiceSelectionChanged(object sender, EventArgs e)
{
var regNr = registrationNumber.Text;
var selectedServiceType = SelectedServiceType.ToString("D");
string url = string.Empty;
BookingHelpers.FormatQueryStringUrl(this.Request, "serviceType", selectedServiceType, ref url);
BookingHelpers.FormatQueryStringUrl(this.Request, "regNr", regNr, ref url);
Server.Transfer(url, false);
}
public static void FormatQueryStringUrl(HttpRequest request, string key, string value, ref string url)
{
if (string.IsNullOrEmpty(url))
{
url = request.Url.PathAndQuery;
}
if (url.Contains("?"))
{
if (url.Contains("&" + key))
{
string currentValue = request.QueryString[key];
url = url.Replace("&" + key + "=" + currentValue, "&" + key + "=" + value);
}
else
{
url = String.Format(request.Url.PathAndQuery + "&" + key + "={0}", value);
}
}
else url = String.Format(request.Url.PathAndQuery + "?" + key + "={0}", value);
}
This however uses the Request.QueryString each time, so the first kvp is overwritten. So my question is: How can I make this work so that i can append both of the key value pairs to the query string?
I found an answer on stack overflow that helped me:
How to build a query string for a URL in C#?
Although, I also needed to check if the url already had a query string in it, so i modified it to something like this.
public static string ToQueryString(string url, NameValueCollection nvc)
{
StringBuilder sb;
if (url.Contains("?"))
sb = new StringBuilder("&");
else
sb = new StringBuilder("?");
bool first = true;
foreach (string key in nvc.AllKeys)
{
foreach (string value in nvc.GetValues(key))
{
if (!first)
{
sb.Append("&");
}
sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value));
first = false;
}
}
return url + sb.ToString();
}
And the usage instead became:
var queryParams = new NameValueCollection()
{
{ "isaServiceType", selectedIsaServiceType },
{ "regNr", regNr }
};
var url = ToQueryString(Request.Url.PathAndQuery, queryParams);
in c#
public static string HashToString(string message, byte[] key)
{
byte[] b=new HMACSHA512(key).ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(b);
}
client.DefaultRequestHeaders.Add("X-Hash", hash);
var encryptedContent = DataMotion.Security.Encrypt(key, Convert.FromBase64String(iv), serializedModel);
var request = client.PostAsync(ApiUrlTextBox.Text,encryptedContent,new JsonMediaTypeFormatter());
in java:
protected String hashToString(String serializedModel, byte[] key) {
String result = null;
Mac sha512_HMAC;
try {
sha512_HMAC = Mac.getInstance("HmacSHA512");
SecretKeySpec secretkey = new SecretKeySpec(key, "HmacSHA512");
sha512_HMAC.init(secretkey);
byte[] mac_data = sha512_HMAC.doFinal(serializedModel.getBytes("UTF-8"));
result = Base64.encodeBase64String(mac_data);
}catch(Exception e){
}
}
o/p: ye+AZPqaKrU14pui4U5gBCiAbegNvLVjzVdGK3rwG9QVzqKfIgyWBDTncORkNND3DA8jPba5xmC7B5OUwZEKlQ==
i have written hashtostring method in java based on c# code. is this currect? (output is different because every time process is dynamic in both cases.)
With different C# encoding
public static string SHA512_ComputeHash(string text, string secretKey)
{
var hash = new StringBuilder(); ;
byte[] secretkeyBytes = Encoding.UTF8.GetBytes(secretKey);
byte[] inputBytes = Encoding.UTF8.GetBytes(text);
using (var hmac = new HMACSHA512(secretkeyBytes))
{
byte[] hashValue = hmac.ComputeHash(inputBytes);
foreach (var theByte in hashValue)
{
hash.Append(theByte.ToString("x2"));
}
}
return hash.ToString();
}
Both java and C# code are giving same result(same hash code). You should check again.
Replace following line in java code at end
result = Base64.getEncoder().encodeToString(mac_data);
In c#
public static string HMACSHA512(this string Key, string TextToHash)
{
string HmacHashed = "";
if (string.IsNullOrEmpty(Key))
throw new ArgumentNullException("HMACSHA512: Key", "Parameter cannot be empty.");
if (string.IsNullOrEmpty(TextToHash))
throw new ArgumentNullException("HMACSHA512: TextToHash", "Parameter cannot be empty.");
if (Key.Length % 2 != 0 || Key.Trim().Length < 2)
{
throw new ArgumentNullException("HMACSHA512: Key", "Parameter cannot be odd or less than 2 characters.");
}
try
{
using (var HMACSHA512 = new HMACSHA512(Encoding.ASCII.GetBytes(Key)))
{
HmacHashed = BitConverter.ToString(HMACSHA512.ComputeHash(Encoding.ASCII.GetBytes(TextToHash))).Replace("-", string.Empty);
}
return HmacHashed;
}
catch (Exception ex)
{
throw new Exception("HMACSHA512: " + ex.Message);
}
}