Decrypt ".AspNetCore.Session" cookie in ASP.NET Core - c#

In Asp.Net core, a cookie is created when you configure your app to app.UseSession().
By default the cookie is called ".AspNetCore.Session". Its value identifies the session to be used. Currently, I'm saving my session data on a sql server. I need to know the decrypted value of ".AspNetCore.Session" so that I can lookup the session in the database.
Is there a way to decrypt this value? I know ASP.NET must do it behind the scenes somehow.

The session source has everything, but you should need to know it, ISessionStore and IDistributedSessionStore gives you a sessionkey to use.
Rather than make an assumption about the cookie format, what is stopping you from using the store APIs?

I had to extract the private Pad function from Microsoft.AspNetCore.Session, but I was able to get what I needed:
public class DiscussionController : Controller
{
private readonly IDataProtector _dataProtector;
public DiscussionController(IDataProtectionProvider dataProtectionProvider)
{
var protectorPurpose = "whatever purpose you want";
_dataProtector = dataProtectionProvider.CreateProtector(protectorPurpose);
}
public IActionResult Index()
{
HttpContext.Request.Cookies.TryGetValue(".AspNetCore.Session", out string cookieValue);
var protectedData = Convert.FromBase64String(Pad(cookieValue));
var unprotectedData = _dataProtector.Unprotect(protectedData);
var humanReadableData = System.Text.Encoding.UTF8.GetString(unprotectedData);
return Ok();
}
private string Pad(string text)
{
var padding = 3 - ((text.Length + 3) % 4);
if (padding == 0)
{
return text;
}
return text + new string('=', padding);
}
}
The Pad function was taken from: https://github.com/aspnet/AspNetCore/blob/87629bbad906e9507026692904b6bcb5021cdd33/src/Middleware/Session/src/CookieProtection.cs#L61-L69

Related

Asp.net Core IDataProtectionProvider is different for different application?

I am developing an application that consists of some core components like Core, Infrastructure, MVC, and Console Application. I have the following class in my application Core library
public class CustomIDataProtection
{
private readonly IDataProtector _protector;
public CustomIDataProtection(IDataProtectionProvider protectionProvider)
{
_protector = protectionProvider.CreateProtector("test123");
}
public string Encode(object currentObject)
{
var serilazedObject = JsonConvert.SerializeObject(currentObject);
var protectedData = _protector.Protect(serilazedObject);
return protectedData;
}
public string Decode(string data)
{
try
{
var unprotectedData = _protector.Unprotect(data);
var desearlizedObj = JsonConvert.DeserializeObject<string>(unprotectedData);
return desearlizedObj;
}
catch (Exception ex)
{
throw ex;
}
}
I inject the above class into a console application and encode some data like this
Registering Services
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<CustomIDataProtection>();
Injecting Services
private readonly CustomIDataProtection _protector;
_protector = scope.ServiceProvider.GetService<CustomIDataProtection>();
Encode Data:
var value = "test";
var password = _protector.Encode(value);
Now after encoding I save it in DB. After that, I switch to my MVC application and try decode that encoded data via console application but my decoding code fails. Gives me error
The payload was invalid
I want to know why it's giving me an error? while I encode in a console application and decode in the same application it works fine. But when I encode in a console application and try to decode in MVC application it gives me an error.
If anyone is looking for answer, this answer helped me solve this. It's because DataProtectionOptions.ApplicationDiscriminator property will be different when you try to decrypt from a different project or application. You can find more information on that here.
You can set the ApplicationDiscriminator property like this in your Startup or Program (based on .net version). If you set it to the same value in both project/application, it will work.
services.AddDataProtection(p =>
{
p.ApplicationDiscriminator = "SetValueHere";
});

How to cache in c#

I have a service method which is getting data from a URL based on the parameters news/blogs.
How to cache the data coming from the URL every 30 minutes?
The user details also get cached for 30 minutes.
Also how do i cache the news,blogs separately if they are coming from the same service URL
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class IITNews : IIITNews
{
System.Web.SessionState.HttpSessionState session = HttpContext.Current.Session;
private static DateTime cacheExpiry = DateTime.MinValue;
string loggedinUser = "";
private string cachedResponse = "";
public Stream getNewsBlogsZD(string type, string noOfItems, string devicetype)
{
try
{
if (cacheExpiry > DateTime.Now)
{
if (System.Web.HttpContext.Current.Cache[cachedResponse] != null)
{
logger.Debug(loggedinUser + "Getting the cached data");
}
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
}
else
{
string loggedInUserNum = GetEmployeeNumber();
string scenarioType = "";
string url = "";
if (type.Trim().ToUpper() == "NEWS")
{
scenarioType = "DisplayNews";
url = ServiceURL + "getWidgetDetails?WName=News&noOfItems=" + Convert.ToInt32(noOfItems.Trim());
}
else if (type.Trim().ToUpper() == "ZD")
{
scenarioType = "DisplayZD";
url = ServiceURL + "getWidgetDetails?WName=ZD&noOfItems=" + Convert.ToInt32(noOfItems.Trim());
}
else if (type.Trim().ToUpper() == "BLOGS")
{
scenarioType = "DisplayBlogs";
url = ServiceURL + "getWidgetDetails?WName=Blogs&noOfItems=" + Convert.ToInt32(noOfItems.Trim());
}
WebClient client = new WebClient();
client.UseDefaultCredentials = true;
client.Encoding = System.Text.Encoding.UTF8;
cachedResponse = client.DownloadString(url);
HttpContext.Current.Cache.Insert("cacheResponse",cachedResponse ,null);
cacheExpiry = DateTime.Now.AddMinutes(30);
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
}
}
catch (Exception ex)
{
string message=ex.message;
return new MemoryStream(Encoding.UTF8.GetBytes("[]"));
}
}
public stream userdertails
{
//method to fetch user details where no cache is used
}
}
If this is your service, you can cache it on the service and return same data for very same parameters.
If not, you can create some singleton class to hold data in. If the singleton has valid data, return them instead of calling WebService :)
Store the result somewhere, along with a time stamp. Where you store it is up to you. There are a variety of caching systems you can find, there's a database, there's the file system, in-memory static values, instance values, etc., etc.
Possibly the simplest approach would just be a class-level value. Something like this:
private string cachedResponse = "";
private DateTime cacheExpiry = DateTime.MinValue;
Then in your logic you would check for the cache before deciding to query the service. Something like this:
if (cacheExpiry > DateTime.Now)
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
// the rest of your method to query the service
// then...
cachedResponse = client.DownloadString(url);
cacheExpiry = DateTime.Now.AddMinutes(30);
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
This is just an example of the logic behind the concept of a cache, of course. If your object instance itself doesn't remain in memory then this object-level cache of course wouldn't do the job. The logic is always the same:
Check if the cache is still valid.
If so, return the cached value.
If not, query the service and store the new value and update the cache expiry and return the newly cached value.
Where you store the cache is up to you and depends on more than what's present in this question.
You can use the MemoryCache Class which enables you to cache objects and set time of them to live in cache.

How do I use a Facebook signed_request in .NET?

I'm using Facebook as a login provider for my web application (ASP.NET MVC).
My login works similar to another StackOverflow post How to securely authorize a user via Facebook's Javascript SDK. I also share the user's concerns.
The flow for my login is as Follows:
1. The user presses the login button.
2. The user must accept the app.
3. A javascript callback retrieves the response.
var authResponse = response.authResponse;
Object returned:
{
accessToken: "...",
expiresIn: 1234,
signedRequest: "...",
userID: "123456789"
}
I've heard that I can used the signed_request to validate the user's request, but all the examples online are for PHP. How do I do this in .NET?
To compile Rowan's answer into its final code:
public static string DecodeSignedRequest(string signed_request)
{
try
{
if (signed_request.Contains("."))
{
string[] split = signed_request.Split('.');
string signatureRaw = FixBase64String(split[0]);
string dataRaw = FixBase64String(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
string data = Encoding.UTF8.GetString(dataBuffer);
byte[] appSecretBytes = Encoding.UTF8.GetBytes(app_secret);
System.Security.Cryptography.HMAC hmac = new System.Security.Cryptography.HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(split[1]));
if (expectedHash.SequenceEqual(signature))
{
return data;
}
}
}
catch
{
// error
}
return "";
}
private static string FixBase64String(string str)
{
while (str.Length % 4 != 0)
{
str = str.PadRight(str.Length + 1, '=');
}
return str.Replace("-", "+").Replace("_", "/");
}
Thanks Rowan!
Yes, the signed_request can be used to verify that an incoming login request is genuine. If you're logging in a user with Javascript (via AJAX, for example) you can use the signed_request to ensure that the data isn't false.
According to Parsing the Signed Request, there are 3 major steps, however I'll be a little more specific.
Take the signed_request string and split it into two strings. There is a period character (full stop) which is a delimiter.
The first part of the string (the signature) is a hash of the second part.
The second part contains some information about the user and the request (user ID, timestamp).
The strings are in Base64, but cannot be decoded straight away.
They are Base64-URL-encoded which means that + and / characters have been replaced with URL-friendly - and _ characters. Replace - characters with + and _ characters with /.
The strings may not be fully Base64 padded. Base64 strings should be divisible by 4; pad the strings out as necessary.
Hash the signature using HMAC (SHA256) using your app secret as the key and compare the result to the signature that was provided.
Use the .NET class HMACSHA256.
1. Split and decode
Code
string response = ""; // the signed_request
string[] split = response.Split('.');
string signatureRaw = FixBase64String(split[0]);
string dataRaw = FixBase64String(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
string data = Encoding.UTF8.GetString(dataBuffer);
FixBase64String()
static string FixBase64String(string str)
{
string result = str;
while (result.Length % 4 != 0)
{
result = result.PadRight(result.Length + 1, '=');
}
result = result.Replace("-", "+").Replace("_", "/");
return result;
}
2. Compare the hashes
byte[] appSecretBytes = Encoding.UTF8.GetBytes("my_app_secret_here");
HMAC hmac = new HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(dataRaw));
bool areEqual = expectedHash.SequenceEqual(signature);
If areEqual is true then you can be sure that the signed request is valid and has not been tampered with (assuming your app secret is secure).
Remember to keep your app secret secure, otherwise malicious users can do bad things.

How to get the facebook signed request in c#

I'm new to Facebook apps. I'm trying to create an MVC 4 application with Facebook Application as my Project Template.
I'm trying to catch the page id on which the page tab is created and I've got it somehow.
My problem here is when someone visits my app, I want to know the page id through which they are viewing the page tab. I've searched a lot where I got to know that I've to use FacebookSignedRequest for this. But this class is not available to me.
Thanks in advance for any help.
If you are simply trying to parse the signed_request parameter from Facebook, you can do so using the following C# code.
This code also verifies the hash using your own app_secret param, to ensure the signed_request originated from Facebook.
public static string DecodeSignedRequest(string signed_request)
{
try
{
if (signed_request.Contains("."))
{
string[] split = signed_request.Split('.');
string signatureRaw = FixBase64String(split[0]);
string dataRaw = FixBase64String(split[1]);
// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);
byte[] dataBuffer = Convert.FromBase64String(dataRaw);
// JSON object
string data = Encoding.UTF8.GetString(dataBuffer);
byte[] appSecretBytes = Encoding.UTF8.GetBytes(app_secret);
System.Security.Cryptography.HMAC hmac = new System.Security.Cryptography.HMACSHA256(appSecretBytes);
byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(split[1]));
if (expectedHash.SequenceEqual(signature))
{
return data;
}
}
}
catch
{
// error
}
return "";
}
private static string FixBase64String(string str)
{
while (str.Length % 4 != 0)
{
str = str.PadRight(str.Length + 1, '=');
}
return str.Replace("-", "+").Replace("_", "/");
}
All I had to do was create a Facebook Client object and call the ParseSignedRequest method with the app secret.
var fb = new FacebookClient();
dynamic signedRequest = fb.ParseSignedRequest(appSecret, Request.Form["signed_request"]);
This returns a Json object which we have to parse using JObject.Parse

Safe way to store web api credentials?

My web application logs into a web api. This needs an email and password. I cannot hash these in my database because the api requires the password in plain text.
How can I store my web api credentials in a safer way than plain text, xor, or base64? Is there a 'proper' solution for this sort of thing?
Yes there is, the ProtectedData class, it lets you encrypt a object tied to a windows user acount, so if the user.config file is copied to another user/computer it will not work
In your Settings file, create two string properties named ApiUsername and ApiPassword, then click "View Code at the top and add the following functions
internal sealed partial class Settings {
private MD5 md5 = MD5.Create();
public global::System.Net.NetworkCredential ApiLogin
{
get
{
global::System.Net.NetworkCredential tmp = null;
if (ApiPassword != "")
{
tmp = new System.Net.NetworkCredential();
tmp.UserName = ApiUsername;
try
{
tmp.Password = System.Text.Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(ApiPassword), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(ApiUsername.ToUpper())), DataProtectionScope.CurrentUser));
}
catch
{
tmp.Password = "";
}
}
return tmp;
}
set
{
global::System.Net.NetworkCredential tmp2 = value;
ApiUsername = tmp2.UserName;
ApiPassword = Convert.ToBase64String(ProtectedData.Protect(System.Text.Encoding.UTF8.GetBytes(tmp2.Password), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tmp2.UserName.ToUpper())), DataProtectionScope.CurrentUser));
}
}
}
This will add a accessable property called ApiLogin which will contain a NetworkCredential with the decrpted password, when you save the credentials to the disk it stores it in that encrpted protected form that can't be copied to other users.
If the decryption fails it sets the password to blank in the returned credential. If you want the decrption to work on any useraccount on that single machine change the ProtectionScope to DataProtectionScope.LocalMachine.

Categories