Encrypting and Decrypting a token with a C# ASHX handler - c#

I've read a nice guide a few days ago about generating a token on the server's side to have the time of the token's creation within the token, along with "Guid.NewGuid()" 's encryption.
However, I've tried to adjust the results to have a user's username within the token, rather than the date time. I'm close, but I cannot extract the username itself, I can only receive it with some random letters after it.
Code of the ASP.NET generic handler to GENERATE the token upon identification
ASCIIEncoding encoder = new ASCIIEncoding();
if(postType == "identify")
{
byte[] binName = encoder.GetBytes(name);
byte[] key = Guid.NewGuid().ToByteArray();
string _token = Convert.ToBase64String(binName.Concat(key).ToArray());
// The above creates a token with the name and the "key", works well
}
Code of the generic handler to decrypt the token (see example for result)
if(postType == "check")
{
string _token = dict["token"] as string;
byte[] data = Convert.FromBase64String(_token);
string theCode = encoder.GetString(data); // This will get both the username and the GUID key within
context.Response.Write(jss.Serialize(new Code { eCode = theCode })); // Returns in JSON, irrelevant to the question, it works well
}
EXAMPLE: If the name would be "user", then the varialbe "theCode" would hold the value of "userXyZxYzXyZ" (while XyZ stands for the GUID's "random" key).
I think it is fair to say that my question is how to separate this GUID's key from the username upon decryption

A guid is 38 characters long, so the name will be theCode.SubString(0, theCode.Length - 38). Alternately, you can compare the current user's name with theCode: theCode.StartsWith(name).

Related

Invalid length for a Base-64 char array or string : C#

I am new to C# ,Here in my web API project I have some code for hashing the user password using SHA3 .
In API I have 2 methods
First method is used for when new User create an account I just hashing the password and store it in the table.
The second method is for when the same user log In again I fetch the hashed password(string) and verify the current password with it .
Here I have faced the error as I mentioned in the title and I referred some related post in SO also but I could't solve this issue .
I have verified that the stored and fetched string has the same characters also the generated and stored string has the same characters.
I don't know where I did the mistakes .
Password : Abcd#123
Hashed String : k/OMmdnW6FZ+zsOrE2rkdy8YEUH/rep5dlcRIwnG8Vc7kQ81VL8dEQv2Clyp7iRhb0HSfKtgOLBj5g/YbqHq7FKDj5epafNwasE=
Calling Confirm method
bool isPasswordPassed = false;
if (mHashedPassword != " " && mUserPassword != " ")
{
isPasswordPassed = Hashing.Confirm(mUserPassword, mHashedPassword, Supported_HA.SHA512);
}
Confirm
public static bool Confirm(string plainText, string hashValue, Supported_HA hash)
{
byte[] hashBytes = Convert.FromBase64String(hashValue);//This line passing the error as in my title.
......
.......
.....
}
But It works fine when I check the code like this ...
check(mUserPassword){
string a = Hashing.ComputeHash(mUserPassword, Supported_HA.SHA512, null);
bool b = Hashing.Confirm(mUserPassword, a, Supported_HA.SHA512);
}
Here I am passing the password to generate hash and the confirm hash but it returns TRUE
Can anyone help me to solve this .
Reference : https://www.youtube.com/watch?v=0dgTf9TUDHU
It may be an encoding conversion Base64 Unicode. you should pass the encoding
var plainTextBytes = System.Text.Encoding.Unicode.GetBytes(plainText);
string hashValue = Convert.ToBase64String(plainTextBytes);
then in the Confirm method
byte[] hashBytes = Convert.FromBase64String(hashValue);
should work. Regards

Password hash and verification don't match

I'm writing custom forms authentication for ASP.NET MVC 5 (no, I don't want to use ASP.NET Identity). I'm trying to hash my passwords using a randomly-generated salt and then hashing salt+password using SHA512. Here are the methods I've written:
private static User SetPassword(User newUser, string password)
{
var rand = RNGCryptoServiceProvider.Create();
var saltBytes = new byte[128];
var passwordBytes = Encoding.UTF8.GetBytes(password);
rand.GetNonZeroBytes(saltBytes);
var passHash = SHA512Managed.Create().ComputeHash(saltBytes.Concat(passwordBytes).ToArray());
var hash = Encoding.UTF8.GetString(passHash);
var salt = Encoding.UTF8.GetString(saltBytes);
newUser.PasswordHash = hash;
newUser.Salt = salt;
return newUser;
}
private static bool ValidatePassword(User user, string passwordTry)
{
var actualPasswordBytes = Encoding.UTF8.GetBytes(user.PasswordHash);
var passwordTryBytes = Encoding.UTF8.GetBytes(passwordTry);
var saltBytes = Encoding.UTF8.GetBytes(user.Salt);
var passwordTryHashBytes = SHA512Managed.Create().ComputeHash(saltBytes.Concat(passwordTryBytes).ToArray());
if (passwordTryHashBytes == actualPasswordBytes)
{
return true;
}
return false;
}
If I step through the code, the registration (SetPassword()) method appears to work successfully. The user's record gets set with a UTF-8 encoded password hash and salt.
If I step through the password validation method, everything also appears to be operating normally. The user's record (the salt and hash) are checked against the password try.
The problem is, when I register with a password and then try to log in as that user, the password validation fails. I'm probably not understanding how one of the Cryptography classes works... can anyone explain why this doesn't work?
The following code will always fail because both objects do not point to the same reference:
if (passwordTryHashBytes == actualPasswordBytes)
Try using LINQ's SequenceEqual() method
passwordTryHashBytes.SequenceEqual(actualPasswordBytes)

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 create a password reset link?

Which way would you suggest to create a secure password reset link in MVC and C#? I mean, I'll create a random token, right? How do I encode it before to sending to user? Is MD5 good enough? Do you know any other secure way?
I mean, I'll create a random token, right?
There are two approaches:
Using a cryptographically secure random series of bytes, which are saved to the database (optionally hashed too) and also sent to the user by e-mail.
The disadvantage to this approach is you need to extend your database design (schema) to have a column to store this data. You should also store the UTC date+time the bytes were generated in order to have the password reset code expire.
Another disadvantage (or an advantage) is that a user can only have at-most 1 pending password-reset.
Using a private key to sign a HMAC message containing minimal details needed to reset the user's password, and this message can include an expiry date+time as well.
This approach avoids needing to store anything in your database, but it also means you cannot revoke any validly-generated password-reset code, which is why it's important to use a short expiry time (about 5 minutes, I reckon).
You could store revocation information in the database (as well as preventing multiple pending password-resets) but this removes all of the advantages of the stateless nature of signed HMACs for authentication.
Approach 1: Cryptographically secure random password reset code
Use System.Security.Cryptography.RandomNumberGenerator which is a cryptographically-secure RNG.
Don't use System.Random, it isn't cryptographically secure.
Use it to generate random bytes and then convert those bytes to human-readable characters that will survive e-mail and being copied and pasted around (i.e. by using Base16 or Base64 encoding).
Then store those same random bytes (or a hash of them, though this doesn't aid security all that much).
And just include that Base16 or Base64 string in the email.
You could have a single clickable link in the email which includes the password reset code in the querystring, however doing so violates HTTP's guidelines on what a GET request should be capable of (as clicking a link is always a GET request, but GET requests should not cause state-changes in persisted data, only POST, PUT, and PATCH requests should do that - which necessitates having the user manually copy the code and submit a POST web-form - which isn't the best user-experience.
Actually, a better approach is to have that link open a page with the password reset code in the querystring, and then that page still has a <form method="POST"> but it's to submit the user's new password, instead of pregenerating a new password for them - thus not violating HTTP's guidelines as no change-of-state is made until the final POST with the new password.
Like so:
Extend your databases' Users table to include columns for the password-reset code:
ALTER TABLE dbo.Users ADD
PasswordResetCode binary(12) NULL,
PasswordResetStart datetime2(7) NULL;
Do something like this in your web application's code:
[HttpGet]
[HttpHead]
public IActionResult GetPasswordResetForm()
{
// Return a <form> allowing the user to confirm they want to reset their password, which POSTs to the action below.
}
static readonly TimeSpan _passwordResetExpiry = TimeSpan.FromMinutes( 5 );
[HttpPost]
public IActionResult SendPasswordResetCode()
{
// 1. Get a cryptographically secure random number:
// using System.Security.Cryptography;
Byte[] bytes;
String bytesBase64Url; // NOTE: This is Base64Url-encoded, not Base64-encoded, so it is safe to use this in a URL, but be sure to convert it to Base64 first when decoding it.
using( RandomNumberGenerator rng = new RandomNumberGenerator() ) {
bytes = new Byte[12]; // Use a multiple of 3 (e.g. 3, 6, 12) to prevent output with trailing padding '=' characters in Base64).
rng.GetBytes( bytes );
// The `.Replace()` methods convert the Base64 string returned from `ToBase64String` to Base64Url.
bytesBase64Url = Convert.ToBase64String( bytes ).Replace( '+', '-' ).Replace( '/', '_' );
}
// 2. Update the user's database row:
using( SqlConnection c = new SqlConnection( CONNECTION_STRING ) )
using( SqlCommand cmd = c.CreateCommand() )
{
cmd.CommandText = "UPDATE dbo.Users SET PasswordResetCode = #code, PasswordResetStart = SYSUTCDATETIME() WHERE UserId = #userId";
SqlParameter pCode = cmd.Parameters.Add( cmd.CreateParameter() );
pCode.ParameterName = "#code";
pCode.SqlDbType = SqlDbType.Binary;
pCode.Value = bytes;
SqlParameter pUserId = cmd.Parameters.Add( cmd.CreateParameter() );
pCode.ParameterName = "#userId";
pCode.SqlDbType = SqlDbType.Int;
pCode.Value = userId;
cmd.ExecuteNonQuery();
}
// 3. Send the email:
{
const String fmt = #"Greetings {0},
I am Ziltoid... the omniscient.
I have come from far across the omniverse.
You shall fetch me your universe's ultimate cup of coffee... uh... I mean, you can reset your password at {1}
You have {2:N0} Earth minutes,
Make it perfect!";
// e.g. "https://example.com/ResetPassword/123/ABCDEF"
String link = "https://example.com/" + this.Url.Action(
controller: nameof(PasswordResetController),
action: nameof(this.ResetPassword),
params: new { userId = userId, codeBase64 = bytesBase64Url }
);
String body = String.Format( CultureInfo.InvariantCulture, fmt, userName, link, _passwordResetExpiry.TotalMinutes );
this.emailService.SendEmail( user.Email, subject: "Password reset link", body );
}
}
[HttpGet( "/PasswordReset/ResetPassword/{userId}/{codeBase64Url}" )]
public IActionResult ResetPassword( Int32 userId, String codeBase64Url )
{
// Lookup the user and see if they have a password reset pending that also matches the code:
String codeBase64 = codeBase64Url.Replace( '-', '+' ).Replace( '_', '/' );
Byte[] providedCode = Convert.FromBase64String( codeBase64 );
if( providedCode.Length != 12 ) return this.BadRequest( "Invalid code." );
using( SqlConnection c = new SqlConnection( CONNECTION_STRING ) )
using( SqlCommand cmd = c.CreateCommand() )
{
cmd.CommandText = "SELECT UserId, PasswordResetCode, PasswordResetStart FROM dbo.Users SET WHERE UserId = #userId";
SqlParameter pUserId = cmd.Parameters.Add( cmd.CreateParameter() );
pCode.ParameterName = "#userId";
pCode.SqlDbType = SqlDbType.Int;
pCode.Value = userId;
using( SqlDataReader rdr = cmd.ExecuteReader() )
{
if( !rdr.Read() )
{
// UserId doesn't exist in the database.
return this.NotFound( "The UserId is invalid." );
}
if( rdr.IsDBNull( 1 ) || rdr.IsDBNull( 2 ) )
{
return this.Conflict( "There is no pending password reset." );
}
Byte[] expectedCode = rdr.GetBytes( 1 );
DateTime? start = rdr.GetDateTime( 2 );
if( !Enumerable.SequenceEqual( providedCode, expectedCode ) )
{
return this.BadRequest( "Incorrect code." );
}
// Now return a new form (with the same password reset code) which allows the user to POST their new desired password to the `SetNewPassword` action` below.
}
}
[HttpPost( "/PasswordReset/ResetPassword/{userId}/{codeBase64}" )]
public IActionResult SetNewPassword( Int32 userId, String codeBase64, [FromForm] String newPassword, [FromForm] String confirmNewPassword )
{
// 1. Use the same code as above to verify `userId` and `codeBase64`, and that `PasswordResetStart` was less than 5 minutes (or `_passwordResetExpiry`) ago.
// 2. Validate that `newPassword` and `confirmNewPassword` are the same.
// 3. Reset `dbo.Users.Password` by hashing `newPassword`, and clear `PasswordResetCode` and `PasswordResetStart`
// 4. Send the user a confirmatory e-mail informing them that their password was reset, consider including the current request's IP address and user-agent info in that e-mail message as well.
// 5. And then perform a HTTP 303 redirect to the login page - or issue a new session token cookie and redirect them to the home-page.
}
}
Approach 2: HMAC code
This approach requires no changes to your database nor to persist new state, but it does require you to understand how HMAC works.
Basically it's a short structured message (rather than being random unpredictable bytes) that contains enough information to allow the system to identify the user whose password should be reset, including an expiry timestamp - to prevent forgery this message is cryptographically-signed with a private-key known only to your application code: this prevents attackers from generating their own password reset codes (which obviously wouldn't be good!).
Here's how you can generate a HMAC code for password reset, as well as how to verify it:
private static readonly Byte[] _privateKey = new Byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; // NOTE: You should use a private-key that's a LOT longer than just 4 bytes.
private static readonly TimeSpan _passwordResetExpiry = TimeSpan.FromMinutes( 5 );
private const Byte _version = 1; // Increment this whenever the structure of the message changes.
public static String CreatePasswordResetHmacCode( Int32 userId )
{
Byte[] message = Enumerable.Empty<Byte>()
.Append( _version )
.Concat( BitConverter.GetBytes( userId ) )
.Concat( BitConverter.GetBytes( DateTime.UtcNow.ToBinary() ) )
.ToArray();
using( HMACSHA256 hmacSha256 = new HMACSHA256( key: _privateKey ) )
{
Byte[] hash = hmacSha256.ComputeHash( buffer: message, offset: 0, count: message.Length );
Byte[] outputMessage = message.Concat( hash ).ToArray();
String outputCodeB64 = Convert.ToBase64( outputMessage );
String outputCode = outputCodeB64.Replace( '+', '-' ).Replace( '/', '_' );
return outputCode;
}
}
public static Boolean VerifyPasswordResetHmacCode( String codeBase64Url, out Int32 userId )
{
String base64 = codeBase64Url.Replace( '-', '+' ).Replace( '_', '/' );
Byte[] message = Convert.FromBase64String( base64 );
Byte version = message[0];
if( version < _version ) return false;
userId = BitConverter.ToInt32( message, startIndex: 1 ); // Reads bytes message[1,2,3,4]
Int64 createdUtcBinary = BitConverter.ToInt64( message, startIndex: 1 + sizeof(Int32) ); // Reads bytes message[5,6,7,8,9,10,11,12]
DateTime createdUtc = DateTime.FromBinary( createdUtcBinary );
if( createdUtc.Add( _passwordResetExpiry ) < DateTime.UtcNow ) return false;
const Int32 _messageLength = 1 + sizeof(Int32) + sizeof(Int64); // 1 + 4 + 8 == 13
using( HMACSHA256 hmacSha256 = new HMACSHA256( key: _privateKey ) )
{
Byte[] hash = hmacSha256.ComputeHash( message, offset: 0, count: _messageLength );
Byte[] messageHash = message.Skip( _messageLength ).ToArray();
return Enumerable.SequenceEquals( hash, messageHash );
}
}
Used like so:
// Note there is no `UserId` URL parameter anymore because it's embedded in `code`:
[HttpGet( "/PasswordReset/ResetPassword/{codeBase64Url}" )]
public IActionResult ConfirmResetPassword( String codeBase64Url )
{
if( !VerifyPasswordResetHmacCode( codeBase64Url, out Int32 userId ) )
{
// Message is invalid, such as the HMAC hash being incorrect, or the code has expired.
return this.BadRequest( "Invalid, tampered, or expired code used." );
}
else
{
// Return a web-page with a <form> to POST the code.
// Render the `codeBase64Url` to an <input type="hidden" /> to avoid the user inadvertently altering it.
// Do not reset the user's password in a GET request because GET requests must be "safe". If you send a password-reset link by SMS text message or even by email, then software bot (like link-preview generators) may follow the link and inadvertently reset the user's password!
}
}
[HttpPost( "/PasswordReset/ResetPassword" )]
public IActionResult ConfirmResetPassword( [FromForm] ConfirmResetPasswordForm model )
{
if( !VerifyPasswordResetHmacCode( model.CodeBase64Url, out Int32 userId ) )
{
return this.BadRequest( "Invalid, tampered, or expired code used." );
}
else
{
// Reset the user's password here.
}
}
Actually, I wouldn't do any of these.
I faced the same issue and I decided to send a reset token and to do this I used a JWT token.
On that token (which is encrypted) you can set an expiry. Simply create a reset token including the Customers email address as a claim and then set your expiry, store this in your database (in its encrypted form) and encode it and place on the link as URL parameter.
Then when you receive the request you can verify the token is valid. You can then unpack it look at the email address and then proceed to direct them to your secure password reset area for their account. (you can include other claims such as username etc).
To get the JWT implemnetation you can type Install-Package JWT
I do not think you need an encrypted string for this purpose. I think creating one string with Guid would be enough.
string thatString=Guid.NewGuid("n").ToString();
Save that in your db table against that particular user account. Create a link for the user which has this string and send it to them. When they click on it, it will take them to an action method and their you get the corresponding user record associated with this temp string we stored and show the form for user to update the password.
And if you have a doubt whether Guid's are unique, checkout this.
Better than using a random number is to salt then hash. Here is a snippet from a security guru:
#using System.Security.Cryptography;
static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
HashAlgorithm algorithm = new SHA256Managed();
byte[] plainTextWithSaltBytes =
new byte[plainText.Length + salt.Length];
for (int i = 0; i < plainText.Length; i++)
{
plainTextWithSaltBytes[i] = plainText[i];
}
for (int i = 0; i < salt.Length; i++)
{
plainTextWithSaltBytes[plainText.Length + i] = salt[i];
}
return algorithm.ComputeHash(plainTextWithSaltBytes);
}
You can see more on his answer here: https://stackoverflow.com/a/2138588/1026459
Basically just create a password. Salt and hash it here, and then compare it when the user returns. The linked answer also contains a comparison method and a more in depth explanation of salt/hashing.

Generating activation key from serial number

I have devices with unique serial number (string incremetation) ex : AS1002 and AS1003.
I need to figure out an algorithm to produce a unique activation key for each serial number.
What would be the best approach for this ?
Thanks !
(This has to be done offline)
You have two things to consider here:
- Whatever key you generate must be able to be entered easily, so this eliminates some weird hash which may produce characters which will be cumbersome to type, although this can be overcome, it’s something you should consider.
- The operation as you stated must be done online
Firstly, there will be no way to say with absolute certainty that someone will not be able to decipher your key generation routine, no matter how much you attempt to obfuscate. Just do a search engine query for “Crack for Xyz software”.
This has been a long battle that will never end, hence the move to deliver software as services, i.e. online where the producer has more control over their content and can explicitly authorize and authenticate a user. In your case you want to do this offline. So in your scenario someone will attach your device to some system, and the accompanying software that you intend to write this routine on will make a check against the serial number of the device v/s user input.
Based on #sll’s answer, given the offline nature of your request. Your best, unfortunately would be to generate a set of random codes, and validate them when user’s call in.
Here is a method borrowed from another SO answer, I've added digits as well
private readonly Random _rng = new Random();
private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; //Added 1-9
private string RandomString(int size)
{
char[] buffer = new char[size];
for (int i = 0; i < size; i++)
{
buffer[i] = _chars[_rng.Next(_chars.Length)];
}
return new string(buffer);
}
So, generating one for each of your devices and storing them somewhere might be your only option because of the offline considerations.
This routine will produce strings like this when set to create a 10 digit string, which is reasonably random.
3477KXFBDQ
ROT6GRA39O
40HTLJPFCL
5M2F44M5CH
CAVAO780NR
8XBQ44WNUA
IA02WEWOCM
EG11L4OGFO
LP2UOGKKLA
H0JB0BA4NJ
KT8AN18KFA
Activation Key
Here is a simple structure of the activation key:
Part
Description
Data
A part of the key encrypted with a password. Contains the key expiration date and application options.
Hash
Checksum of the key expiration date, password, options and environment parameters.
Tail
The initialization vector that used to decode the data (so-called "salt").
class ActivationKey
{
public byte[] Data { get; set; } // Encrypted part.
public byte[] Hash { get; set; } // Hashed part.
public byte[] Tail { get; set; } // Initialization vector.
}
The key could represent as text format: DATA-HASH-TAIL.
For example:
KCATBZ14Y-VGDM2ZQ-ATSVYMI.
The folowing tool will use cryptographic transformations to generate and verify the key.
Generating
The algorithm for obtaining a unique activation key for a data set consists of several steps:
data collection,
getting the hash and data encryption,
converting activation key to string.
Data collection
At this step, you need to get an array of data such as serial number, device ID, expiration date, etc. This purpose can be achieved using the following
method:
unsafe byte[] Serialize(params object[] objects)
{
using (MemoryStream memory = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(memory))
{
foreach (object obj in objects)
{
if (obj == null) continue;
switch (obj)
{
case string str:
if (str.Length > 0)
writer.Write(str.ToCharArray());
continue;
case DateTime date:
writer.Write(date.Ticks);
continue;
case bool #bool:
writer.Write(#bool);
continue;
case short #short:
writer.Write(#short);
continue;
case ushort #ushort:
writer.Write(#ushort);
continue;
case int #int:
writer.Write(#int);
continue;
case uint #uint:
writer.Write(#uint);
continue;
case long #long:
writer.Write(#long);
continue;
case ulong #ulong:
writer.Write(#ulong);
continue;
case float #float:
writer.Write(#float);
continue;
case double #double:
writer.Write(#double);
continue;
case decimal #decimal:
writer.Write(#decimal);
continue;
case byte[] buffer:
if (buffer.Length > 0)
writer.Write(buffer);
continue;
case Array array:
if (array.Length > 0)
foreach (var a in array) writer.Write(Serialize(a));
continue;
case IConvertible conv:
writer.Write(conv.ToString(CultureInfo.InvariantCulture));
continue;
case IFormattable frm:
writer.Write(frm.ToString(null, CultureInfo.InvariantCulture));
continue;
case Stream stream:
stream.CopyTo(stream);
continue;
default:
try
{
int rawsize = Marshal.SizeOf(obj);
byte[] rawdata = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
writer.Write(rawdata);
handle.Free();
}
catch(Exception e)
{
// Place debugging tools here.
}
continue;
}
}
writer.Flush();
byte[] bytes = memory.ToArray();
return bytes;
}
}
Getting the hash and data encryption
This step contains the following substeps:
create an encryption engine using a password and stores the initialization vector in the Tail property.
next step, expiration date and options are encrypted and the encrypted data is saved into the Data property.
finally, the hashing engine calculates a hash based on the expiration date, password, options and environment and puts it in the Hash property.
ActivationKey Create<TAlg, THash>(DateTime expirationDate,
object password,
object options = null,
params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
ActivationKey activationKey = new ActivationKey();
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
if (password == null)
{
password = new byte[0];
}
activationKey.Tail = cryptoAlg.IV;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), activationKey.Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
expirationDate = expirationDate.Date;
long expirationDateStamp = expirationDate.ToBinary();
using (ICryptoTransform transform = cryptoAlg.CreateEncryptor())
{
byte[] data = Serialize(expirationDateStamp, options);
activationKey.Data = transform.TransformFinalBlock(data, 0, data.Length);
}
using (HashAlgorithm hashAlg = Activator.CreateInstance<THash>())
{
byte[] data = Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
activationKey.Tail);
activationKey.Hash = hashAlg.ComputeHash(data);
}
}
return activationKey;
}
Converting to string
Use the ToString method to get a string containing the key text, ready to be transfering to the end user.
N-based encoding (where N is the base of the number system) was often used to convert binary data into a human-readable text. The most commonly used in
activation key is base32. The advantage of this encoding is a large alphabet consisting of numbers and letters that case insensitive. The downside is that this encoding is not implemented in the .NET standard library and you should implement it yourself. You can also use the hex encoding and base64 built into mscorlib. In my example base32 is used, but I will not give its source code here. There are many examples of base32 implementation on this site.
string ToString(ActivationKey activationKey)
{
if (activationKey.Data == null
|| activationKey.Hash == null
|| activationKey.Tail == null)
{
return string.Empty;
}
using (Base32 base32 = new Base32())
{
return base32.Encode(activationKey.Data)
+ "-" + base32.Encode(activationKey.Hash)
+ "-" + base32.Encode(activationKey.Tail);
}
}
To restore use the folowing method:
ActivationKey Parse(string text)
{
ActivationKey activationKey;
string[] items = text.Split('-');
if (items.Length >= 3)
{
using (Base32 base32 = new Base32())
{
activationKey.Data = base32.Decode(items[0]);
activationKey.Hash = base32.Decode(items[1]);
activationKey.Tail = base32.Decode(items[2]);
}
}
return activationKey;
}
Checking
Key verification is carried out using methodes GetOptions an Verify.
GetOptions checks the key and restores embeded data as byte array or null if key is not valid.
Verify just checks the key.
byte[] GetOptions<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
if (Data == null || Hash == null || Tail == null)
{
return null;
}
try
{
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
cryptoAlg.IV = Tail;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
using (ICryptoTransform transform = cryptoAlg.CreateDecryptor())
{
byte[] data = transform.TransformFinalBlock(Data, 0, Data.Length);
int optionsLength = data.Length - 8;
if (optionsLength < 0)
{
return null;
}
byte[] options;
if (optionsLength > 0)
{
options = new byte[optionsLength];
Buffer.BlockCopy(data, 8, options, 0, optionsLength);
}
else
{
options = new byte[0];
}
long expirationDateStamp = BitConverter.ToInt64(data, 0);
DateTime expirationDate = DateTime.FromBinary(expirationDateStamp);
if (expirationDate < DateTime.Today)
{
return null;
}
using (HashAlgorithm hashAlg =
Activator.CreateInstance<THash>())
{
byte[] hash =
hashAlg.ComputeHash(
Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
Tail));
return ByteArrayEquals(Hash, hash) ? options : null;
}
}
}
}
catch
{
return null;
}
}
bool Verify<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
try
{
byte[] key = Serialize(password);
return Verify<TAlg, THash>(key, environment);
}
catch
{
return false;
}
}
Example
Here is a full example of generating the activation key using your own combination of any amount of data - text, strings, numbers, bytes, etc.
Example of usage:
string serialNumber = "0123456789"; // The serial number.
const string appName = "myAppName"; // The application name.
// Generating the key. All the parameters passed to the costructor can be omitted.
ActivationKey activationKey = new ActivationKey(
//expirationDate:
DateTime.Now.AddMonths(1), // Expiration date 1 month later.
// Pass DateTime.Max for unlimited use.
//password:
null, // Password protection;
// this parameter can be null.
//options:
null // Pass here numbers, flags, text or other
// that you want to restore
// or null if no necessary.
//environment:
appName, serialNumber // Application name and serial number.
);
// Thus, a simple check of the key for validity is carried out.
bool checkKey = activationKey.Verify((byte[])null, appName, serialNumber);
if (!checkKey)
{
MessageBox.Show("Your copy is not activated! Please get a valid activation key.");
Application.Exit();
}
By far the most secure way to do it is to have a centralized database of (serial number, activation key) pairs and have the user activate over the internet so you can check the key locally (on the server).
In this implementation, the activation key can be completely random since it doesn't need to depend on the serial number.
You want it to be easy to check, and hard to "go backwards". You'll see a lot of suggestions for using hashing functions, those functions are easy to go one way, but hard to go backwards. Previously, I phrased that as "it is easy to turn a cow into a hamburger, but hard to turn a hamburger into a cow". In this case, a device should know its own serial number and be able to "add" (or append) some secret (usually called "salt") to the serial and then hash or encrypt it.
If you are using reversible encryption, you want to add some sort of "check digit" to the serial numbers so that if someone does figure your encryption scheme out, there is another layer for them to figure out.
An example of a function that is easy enough to "go backwards" was one I solved with Excel while trying to avoid homework.
And you probably want to make things easier for your customers by making the encoding less likely to be messed up when the activation codes are handwritten (such as you write it down from the email then walk over to where the device is and punch the letters/digits in). In many fonts, I and 1, and 0 and O are similar enough that many encodings, such as your car's VIN do not use the letters i and o (and I remember older typewriters that lacked a key for the digit 1 because you were expected to use lowercase L). In such cases, Y, 4 and 7 can appear the same depending on some handwriting. So know your audience and what are their limits.
If your device has some secured memory which can not be read by connecting an programmator or an other device -you can store some key-code and then use any hashing algorithm like MD5 or SHA-1/2 to generate hash by:
HASH(PUBLIC_SERIALNUMBER + PRIVATE_KEYCODE)
And pairs of SERIALNUMBER + KEYCODE should be stored in local DB.
In this way: (offline)
Client calling you and asking for the Activation Code
You asking for a SERIALNUMBER of particular device
Then you search for a KEYCODE by a given SERIALNUMBER in your local DB and generate Activation Code (even using MD5 this will be sacure as long KEYCODE is privately stored in your DB)
Client enter Activation Code into the device, device able to generate hash
by own SERIALNUMBER and KEYCODE and then compare to Activation Code entered by user
This could be simplified by storing activation code itself if device has a secured memory onboard (like SmartCards has). In this way you can just keep own database of SerialCode - ActivationCode pairs.
How about: Invent a password that is not revealed to the user. Then concatenate this password with the serial number and hash the combination.
Anything you do can be broken by a dedicated enough hacker. The question is not, "Can I create absolutely unbreakable security?" but "Can I create security good enough to protect against unskilled hackers and to make it not worth the effort for the skilled hackers?" If you reasonably expect to sell 10 million copies of your product, you'll be a big target and there may be lots of hackers out there who will try to break it. If you expect to sell a few hundred or maybe a few thousand copies, not so much.

Categories