Predicting length of UserManager.GenerateUserTokenAsync tokens (via DataProtectorTokenProvider) - c#

I'm using ASP.NET Core 3.1, with some custom logic that reuses ASP.NET Core Identity's UserManager<TUser> class. I want to reuse its ability to GenerateUserTokenAsync(...), the same type of token also used in e.g. e-mail confirmations. For my application's purpose, I need to know the maximum length for generated tokens.
I've done the following research:
UserManager<TUser>.GenerateUserTokenAsync(...) calls GenerateAsync on the injected provider
in my case that is the DataProtectorTokenProvider<TUser> which will generate tokens like this:
var ms = new MemoryStream();
var userId = await manager.GetUserIdAsync(user);
using (var writer = ms.CreateWriter())
{
writer.Write(DateTimeOffset.UtcNow);
writer.Write(userId);
writer.Write(purpose ?? "");
string stamp = null;
if (manager.SupportsUserSecurityStamp)
{
stamp = await manager.GetSecurityStampAsync(user);
}
writer.Write(stamp ?? "");
}
var protectedBytes = Protector.Protect(ms.ToArray());
return Convert.ToBase64String(protectedBytes);
So the basic calculation for length of the memory stream would be:
27 characters for DateTimeOffset.UtcNow
36 characters for the user id (string representation of a GUID)
20 characters for my specific "purpose" string
36 characters for the security stamp (string representation of a GUID)
---- +
119 characters in total
In the code snippet this gets Protected and then converted ToBase64String
When I casually tested this on my local machine, I got an encrypted string of 352 characters. How could I predict that the 119 input characters would become 352 characters when encrypted? And worse, my app runs actually in production on an Azure App Service where Azure's DPAPI should kick in, possibly with different encryption than on localhost?
Can I in any way predict what the maximum length of the generated User Tokens will be on Azure App Services? I'm happy to include a margin of error, but am clueless what that would need to be.

By default DPAPI uses AES-256-CBC as encryption algorithm unless you change it via UseCryptographicAlgorithms. As per default algo, the calculation would go like this for your case:
Since it's AES 256, it would work with 32 bytes block. So with CBC padding, you output becomes ((119/32) + 1) * 32 + 16 (IV) = 144. After base64, it becomes 192.
So, having it 352 brings up the question is the stamp really 36 in your case?
36 characters for the security stamp (string representation of a GUID)
Also, in deployed environment, make sure to store data protection key outside app since each instance of the app service needs to point to the same key.

Related

OtpSharp not working with google authenticator

I've been trying to use OtpSharp along with Google Authenticator in an application I'm developing. However, I don't understand why the code produced by OtpSharp does not match that of Google Authenticator. I've even tried to correct the time input to OtpSharp according to my local OS without any luck. On another note, the pyotp library from python works just fine without any special effort.
Here is the code I'm using:
var bSharedKey = System.Text.Encoding.Unicode.GetBytes("TESTTESTTESTTEST");
//var correction = new TimeCorrection(DateTime.UtcNow.ToLocalTime());
//var totp = new Totp(bSharedKey, timeCorrection: correction);
var totp = new Totp(bSharedKey);
var realOtp = totp.ComputeTotp();
long timestep = 0;
var OTPmatch = totp.VerifyTotp(passwords[1], out timestep);
The problem was that instead of providing an arbitrary unicode key to the pyotp library (as well as to Google Authenticator), a Base32 string was needed as the input, which I assume was later decoded to a byte array and used by the library.
So I provided OtpSharp with the byte string representation of an arbitrary unicode string and used an online website to decode the unicode string to a base32 string, and used the base32 string in Google Authenticator.
To put it simply, Otpsharp requires a byte array to initialize a totp object, while pyotp needs you to provide it with a base32 string.
I used the following solution:
First
Choose a secret key.
e.g: 32 Chars
private const string SecretKey = "hfBhdVsbAWXmkdWrcnwezQqVLubqeRdq";
Second
Use an approach to generate QR code with your secret.
e.g:
https://stefansundin.github.io/2fa-qr/
How to generate a QR Code for Google Authenticator that correctly shows Issuer displayed above the OTP?
Point Key: You have to convert your secret key to Base32 to generate QR Code.
You can use https://emn178.github.io/online-tools/base32_encode.html to convert a string to base32 online
Third
Install OtpSharp nuget package.
Validate entered token as follows:
XXX: Valid Period In Seconds
private static bool Validate(string token)
{
var totp = new Totp(Encoding.UTF8.GetBytes(SecretKey));
return totp.VerifyTotp(DateTime.UtcNow.AddSeconds(XXX), token, out _, new VerificationWindow(2, 2));
}

Why is encoding different when run on Windows 8 and Windows Server 2012

I have a wcf service which basically allows people to post information to my Microsoft sql database using a moble app. I'm getting very frustrated when trying to post French content. i.e. "Poignée", I'm making use of a mulitpart/form-data parser ( https://github.com/Vodurden/Http-Multipart-Data-Parser/tree/master/HttpMultipartParser, however the moment the parser tries to process a character dec > 128 ( i.e character é in the word: Poignée in ( BinaryReader.Read() line 283 of BinaryStreamStacks.cs ... shown below ) it blows up. with an error: ("The output char buffer is too small to contain the decoded characters, encoding 'Unicode (UTF-8)' fallback 'System.Text.DecoderReplacementFallback'.\r\nParameter name: chars")
public byte[] ReadByteLine(out bool hitStreamEnd)
{
hitStreamEnd = false;
if (!this.HasData())
{
// No streams, no data!
return null;
}
// This is horribly inefficient, consider profiling here if
// it becomes an issue.
BinaryReader top = this.streams.Peek();
byte[] ignore = this.CurrentEncoding.GetBytes(new[] { '\r' });
byte[] search = this.CurrentEncoding.GetBytes(new[] { '\n' });
int searchPos = 0;
var builder = new MemoryStream();
while (true)
{
// First we need to read a byte from one of the streams
int b = top.Read(); // error occurs here...
.....
If I set the encoding within the multipart/parser on the wcf end to use Encoding.GetEncoding(1252));then I manage to make things work out and the string "Poignée" gets processed correctly, however when I try to save it to the sql database I first need to convert the string out to unicode before writing it to my database. Anyway every works using this approach on my development machine, ( Windows 8 ), the problem is that when I publish my wcf service to my Server 2013 machine Is starts to save Poignée as Poignée. why is it working differently on my windows server 2013 machine then it does on my Windows 8 machine and what can I do to fix it..
P.S. in case you haven't; figured it out I'm finding saving French characters very frustrating..
Thanks,

Can't find certificate by serial number

I've imported my certificates to Personal -> Certificates.
I use the following lines of code to find my certificate by serial number but I can't:
public X509Certificate2Collection FindCerts(string serialNumber)
{
var searchType = X509FindType.FindBySerialNumber;
var storeName = "MY";
var certificatesStore = new X509Store(storeName, StoreLocation.LocalMachine);
certificatesStore.Open(OpenFlags.OpenExistingOnly);
var matchingCertificates = certificatesStore.Certificates.Find(searchType, serialNumber, true);
certificatesStore.Close();
return matchingCertificates;
}
Could you please tell me why I can't find my cert even though it is in certificatesStore.Certificates list?
Note: my certs were created by Go Daddy
I've fixed this problem by entering the serial number instead copying from the property window. I don't know why when copying from this window, it contains a strange character on the beginning of the serial number.
Since I came across this issue too, I tried to make a workaround to be able to copy paste the value from the certmgr.msc
A summary of what I did :
// The value below is pasted from certmgr.msc
var sslCertificateSerialNumber="‎47 9f da c4 ad d7 33 a6 4c ad 54 d3 d9 95 67 1c";
// Remove all non allowed characters that entered the value while copy/paste
var rgx = new Regex("[^a-fA-F0-9]");
var serial = rgx.Replace(sslCertificateSerialNumber, string.Empty).ToUpper();
Now I found the correct certificate with a copy/pasted value.
If you are sure about the presence of certificate in machine store, can you try giving the third parameter to Find() as 'false'?
The certificate serial number is a binary data sequence which denotes a big int of unlimited length. If you saw the text somewhere, this can mean that either the text was written as serial OR (which is more likely) that you are seeing either Base64- or Base16- encoded binary serial. In the latter case you won't find it in straightforward way - you need to decode the serial and try that way.
If what you are trying to extract are the certificate/s issued by GoDaddy,you may use this:
var certificateStore= new X509Store(StoreLocation.LocalMachine);
certificateStore.Open(OpenFlags.ReadOnly);
var certificates = certificateStore.Certificates;
foreach (var certificate in certificateStore)
{
if (certificate.Issuer.Contains("GoDaddy"))
{
Make sure that serialNumber is uppercase string. I had some problems with finding certificates when sn was in lowercase. Also remove whitespaces if you copy sn from the certificate's details window
If you copy from the windows certificate property window, you may accidentally copy some extra invisible characters across, which will break your serial number search.
See this question for more information:
X509 store can not find certificate by SerialNumber
I ran into a similar issue yesterday and spent a number of hours trying to find out why on earth the existing certificate was not being found. It seems that the Certificates.Find method executes a case-sensitive search. I took the recursive approach using String.Compare.
X509Certificate2 storedCert = null;
for (int i = 0; i < store.Certificates.Count; i++)
{
if (String.Compare(store.Certificates[i].SerialNumber, MySerialNum, true) == 0)
{
storedCert = store.Certificates[i];
break;
}
}
To find by serial number, starting from the serial as reported by the mmc snap-in, remove all whitespace and uppercase all alphas. Worked for me. This will give you something like...
008CC59B72BE954F93F1435F6B86227600
As others have said, pay super attention to the "invisible special character" at the start of the string. Copy into and out of notepad to strip it. The same goes for all fields copied out of the cert property window.

C# MD5CryptoServiceProvider

I am currently converting a legacy ASP.NET 1.1 application into a .NET 4 MVC 3 application.
I am looking at the password encryption and a routine was written in the old code to use the MD5CryptoServiceProvider.
private string EncryptText(string szText)
{
try
{
UTF8Encoding objEncoder = new UTF8Encoding();
MD5CryptoServiceProvider objMD5Hasher = new MD5CryptoServiceProvider();
Byte[] btHashedDataBytes = objMD5Hasher.ComputeHash(objEncoder.GetBytes(szText));
string szReturn = objEncoder.GetString(btHashedDataBytes);
objEncoder = null;
objMD5Hasher = null;
return szReturn;
}
catch
{
return "";
}
}
I have written a quick .NET 4 console application and copied this function so I can do a comparison against the current passwords in the database (to make sure the MD5 function still gives me the same output)
string encTxt = encryptor.EncryptText("fbloggsPass12345");
using (SqlConnection conn = new SqlConnection("Server=server;Database=db;User Id=sa;Password=1111;"))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandType = System.Data.CommandType.Text;
cmd.CommandText = "UPDATE SiteUsers SET Token = '" + encTxt + "' WHERE PKey = 10";
if (cmd.ExecuteNonQuery() > 0)
Console.WriteLine("Updated");
else
Console.WriteLine("Failed");
}
conn.Close();
}
Console.ReadLine();
However the password in the database is currently !?MGF+&> and the output I am getting is ���!?��MGF�+&��> which when I store in the database converts to ???!???MGF?+&??>
Which I can see is almost the same, but why am I getting the ? characters
This is the first problem, at least:
string szReturn = objEncoder.GetString(btHashedDataBytes);
You're trying to use the hash as if it were UTF-8-encoded text. It's not - it's just arbitrary binary data.
If you need to convert arbitrary binary data to text, you should use something like Base64 (e.g. Convert.ToBase64String) or hex.
(Additionally, I would strongly advise you not to "handle" exceptions in the way you're doing so at the moment. Why would you want to hide problems like that? And why are you setting variables to null just before they go out of scope anyway?)
Oh, and please don't include values directly in your SQL like that - use parameterized SQL instead.
Finally, I would use a different hashing algorithm these days, particularly for passwords. Can you not use an off-the-shelf system for authentication, which is actually developed by security experts? Security is difficult: we'd all be better off leaving it to the relatively few people who know how to do it right :) See comments for more suggestions.
The standard technique for low impact upgrading is using the old hash as input for the new hashing scheme. This works pretty well with normal MD5 hashes.
Unfortunately for you, you were sending the binary hash through a non binary safe encoding (UTF8). This replaced every second character by 0xFFFD, effectively halving the output size to 64 bits. This weakens an upgraded scheme considerably but not fatally.
I'd upgrade the existing hashes to PBKDF2(legacyHash, salt), then on user login replace the hash with a new hash PBKDF2(password, salt) that doesn't depend on the legacy scheme. After a few months trigger a password reset for all users who did not login yet, getting rid of the legacy hash based passwords.
For the new scheme, I'd go with PBKDF2-SHA-1 which is implemented in the Rfc2898DeriveBytes Class. Use sufficient iterations, at least 10000.

Difference in SHA hashes between ruby and C#

I'm developing an application, that makes use of some REST web services.
It's technical documentation says that I should pass SHA256 hash of some string in the request.
In an example request (in the documentation) a string:
hn-Rw2ZHYwllUYkklL5Zo_7lWJVkrbShZPb5CD1expires=1893013926label[0]=any/somestatistics=1d,2d,7d,28d,30d,31d,lifetimestatus=upl,livetitle=a
After executing:
digest = Digest::SHA256.digest(string_to_sign)
signature = Base64::encode64(digest).chomp.gsub(/=+$/, '')
results in a hash:
YRYuN2zO+VvxISNp/vKQM5Cl6Dpzoin7mNES0IZJ06U
This example is in ruby, as the documentation is for ruby developers.
I'm developing my application in C# and for the exactly same string, when I execute:
byte[] rawHash = sha256.ComputeHash(rawRequest, 0, rawRequest.Length);
string friendlyHash = Convert.ToBase64String(rawHash);
and remove the trailing "=" signs, I get:
Vw8pl/KxnjcEbyHtfNiMikXZdIunysFF2Ujsow8hyiw
and therefore, the application fails to execute resulting in an signature mismatch error.
I've tried changing the encoding while converting the string to a byte array preceding the hashing and nothing changed.
Any ideas?
Based on the document here, you are missing a - (that is a dash) in your string. Seems that Acrobat helpfully removes it in a copy paste from the document...
Here is some code that I splatted together that gets the same value as the example (well it would if you trimmed the final =)
string s = "hn-Rw2ZH-YwllUYkklL5Zo_7lWJVkrbShZPb5CD1expires=1893013926label[0]=any/somestatistics=1d,2d,7d,28d,30d,31d,lifetimestatus=upl,livetitle=a";
SHA256Managed sh = new SHA256Managed();
byte[] request = System.Text.UTF8Encoding.UTF8.GetBytes(s);
sh.Initialize();
byte[] b4bbuff = sh.ComputeHash(request, 0, request.Length);
string b64 = Convert.ToBase64String(b4bbuff);

Categories

Resources