Building an ecommerce system means that we have to be that little bit more careful when it comes to ensuring the integrity of our system.
In my particular case, we need to update a customer's current order at particular stages during the purchasing process.

From this diagram, we can see that we create an Order at Stage 1, and then have to update it in Stage 3.
So this means we have to keep a note of the original OrderID that we generated in Stage 1. In ASP.NET, we can store this in 4 ways:
Option 1Store it in SessionWe could store this in Session, which means it is completely hidden from the user and available for our application. However, there are problems with this. The main problem is that this system is to be deployed in a load-balanced environment, which means that we either use Sticky Sessions (and put up with the problems that come with that) or we store Session out-of-process. I don't fancy hitting the database for a single int value, so I'm not seriously entertaining this option.
Option 2
Store it in a cookieWe can store it in the user's cookie and go looking for it afterwards. But given such a critical part of the system, cookies are inherently unreliable and as such we have to keep looking for options.
Option 3
Store it in a hidden fieldWe can store it in a hidden field and then go looking for it afterwards. Now, my particular solution uses embedded IFRAMEs (I know, I know) and we would need to get the value from the Parent frame on the client-side and push it to the server, at Stage 3. This is possible with a bit of JavaScript, but it seems overly-hacky, so I don't fancy it.
Option 4
Transmit it via the query stringWe can transmit it on the query string. Now, even though we are on https, there's still nothing stopping a user being malicious and typing www.mysite.com/response.ashx?orderid=123 which could potentially affect the status of Order 123, whoever that belongs to.
So, we can encrypt the order id, but as this doesn't contain any particularly sensitive information by itself, all we need to do is verify that the query string was composed by us and not someone else. To do this, we transmit an authentication token with each message.
Hashing and Salt
Hashing involves taking a message and applying a function to it which will return a hash value, such that any change to the original message would result in a different hash being returned.
For example:
hash('fox') = xyz
Change 'fox' to 'quick fox'
hash('quick fox') = abc
Because xyz != abc, we can therefore verify that the original message must have been altered. Simples.
The algorithm we will likely use is
SHA-1, used by the US Government.
We must also pass in some 'salt' to the hashing algorithm, so that the output is unique to us. Think of this as a private key which we will use to re-hash the original OrderID to verify the integrity of the message.
The .NET framework provides the classes we'll need in the System.Security.Cryptography namespace. An example of calling this code is provided
here, I have also copied this below.
///////////////////////////////////////////////////////////////////////////////
// SAMPLE: Hashing data with salt using MD5 and several SHA algorithms.
//
// To run this sample, create a new Visual C# project using the Console
// Application template and replace the contents of the Class1.cs file with
// the code below.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
//
// Copyright (C) 2002 Obviex(TM). All rights reserved.
//
using System;
using System.Text;
using System.Security.Cryptography;
/// <summary>
/// This class generates and compares hashes using MD5, SHA1, SHA256, SHA384,
/// and SHA512 hashing algorithms. Before computing a hash, it appends a
/// randomly generated salt to the plain text, and stores this salt appended
/// to the result. To verify another plain text value against the given hash,
/// this class will retrieve the salt value from the hash string and use it
/// when computing a new hash of the plain text. Appending a salt value to
/// the hash may not be the most efficient approach, so when using hashes in
/// a real-life application, you may choose to store them separately. You may
/// also opt to keep results as byte arrays instead of converting them into
/// base64-encoded strings.
/// </summary>
public class SimpleHash
{
/// <summary>
/// Generates a hash for the given plain text value and returns a
/// base64-encoded result. Before the hash is computed, a random salt
/// is generated and appended to the plain text. This salt is stored at
/// the end of the hash value, so it can be used later for hash
/// verification.
/// </summary>
/// <param name="plainText">
/// Plaintext value to be hashed. The function does not check whether
/// this parameter is null.
/// </param>
/// <param name="hashAlgorithm">
/// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
/// "SHA256", "SHA384", and "SHA512" (if any other value is specified
/// MD5 hashing algorithm will be used). This value is case-insensitive.
/// </param>
/// <param name="saltBytes">
/// Salt bytes. This parameter can be null, in which case a random salt
/// value will be generated.
/// </param>
/// <returns>
/// Hash value formatted as a base64-encoded string.
/// </returns>
public static string ComputeHash(string plainText,
string hashAlgorithm,
byte[] saltBytes)
{
// If salt is not specified, generate it on the fly.
if (saltBytes == null)
{
// Define min and max salt sizes.
int minSaltSize = 4;
int maxSaltSize = 8;
// Generate a random number for the size of the salt.
Random random = new Random();
int saltSize = random.Next(minSaltSize, maxSaltSize);
// Allocate a byte array, which will hold the salt.
saltBytes = new byte[saltSize];
// Initialize a random number generator.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
// Fill the salt with cryptographically strong byte values.
rng.GetNonZeroBytes(saltBytes);
}
// Convert plain text into a byte array.
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
// Allocate array, which will hold plain text and salt.
byte[] plainTextWithSaltBytes =
new byte[plainTextBytes.Length + saltBytes.Length];
// Copy plain text bytes into resulting array.
for (int i=0; i < plainTextBytes.Length; i++)
plainTextWithSaltBytes[i] = plainTextBytes[i];
// Append salt bytes to the resulting array.
for (int i=0; i < saltBytes.Length; i++)
plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];
// Because we support multiple hashing algorithms, we must define
// hash object as a common (abstract) base class. We will specify the
// actual hashing algorithm class later during object creation.
HashAlgorithm hash;
// Make sure hashing algorithm name is specified.
if (hashAlgorithm == null)
hashAlgorithm = "";
// Initialize appropriate hashing algorithm class.
switch (hashAlgorithm.ToUpper())
{
case "SHA1":
hash = new SHA1Managed();
break;
case "SHA256":
hash = new SHA256Managed();
break;
case "SHA384":
hash = new SHA384Managed();
break;
case "SHA512":
hash = new SHA512Managed();
break;
default:
hash = new MD5CryptoServiceProvider();
break;
}
// Compute hash value of our plain text with appended salt.
byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
// Create array which will hold hash and original salt bytes.
byte[] hashWithSaltBytes = new byte[hashBytes.Length +
saltBytes.Length];
// Copy hash bytes into resulting array.
for (int i=0; i < hashBytes.Length; i++)
hashWithSaltBytes[i] = hashBytes[i];
// Append salt bytes to the result.
for (int i=0; i < saltBytes.Length; i++)
hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];
// Convert result into a base64-encoded string.
string hashValue = Convert.ToBase64String(hashWithSaltBytes);
// Return the result.
return hashValue;
}
/// <summary>
/// Compares a hash of the specified plain text value to a given hash
/// value. Plain text is hashed with the same salt value as the original
/// hash.
/// </summary>
/// <param name="plainText">
/// Plain text to be verified against the specified hash. The function
/// does not check whether this parameter is null.
/// </param>
/// <param name="hashAlgorithm">
/// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
/// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
/// MD5 hashing algorithm will be used). This value is case-insensitive.
/// </param>
/// <param name="hashValue">
/// Base64-encoded hash value produced by ComputeHash function. This value
/// includes the original salt appended to it.
/// </param>
/// <returns>
/// If computed hash mathes the specified hash the function the return
/// value is true; otherwise, the function returns false.
/// </returns>
public static bool VerifyHash(string plainText,
string hashAlgorithm,
string hashValue)
{
// Convert base64-encoded hash value into a byte array.
byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue);
// We must know size of hash (without salt).
int hashSizeInBits, hashSizeInBytes;
// Make sure that hashing algorithm name is specified.
if (hashAlgorithm == null)
hashAlgorithm = "";
// Size of hash is based on the specified algorithm.
switch (hashAlgorithm.ToUpper())
{
case "SHA1":
hashSizeInBits = 160;
break;
case "SHA256":
hashSizeInBits = 256;
break;
case "SHA384":
hashSizeInBits = 384;
break;
case "SHA512":
hashSizeInBits = 512;
break;
default: // Must be MD5
hashSizeInBits = 128;
break;
}
// Convert size of hash from bits to bytes.
hashSizeInBytes = hashSizeInBits / 8;
// Make sure that the specified hash value is long enough.
if (hashWithSaltBytes.Length < hashSizeInBytes)
return false;
// Allocate array to hold original salt bytes retrieved from hash.
byte[] saltBytes = new byte[hashWithSaltBytes.Length -
hashSizeInBytes];
// Copy salt from the end of the hash to the new array.
for (int i=0; i < saltBytes.Length; i++)
saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];
// Compute a new hash string.
string expectedHashString =
ComputeHash(plainText, hashAlgorithm, saltBytes);
// If the computed hash matches the specified hash,
// the plain text value must be correct.
return (hashValue == expectedHashString);
}
}
IMPORTANT: Of course, if you've looked at this code, you'll see that it appends the original salt to the generated hash value. So this would give the game away to a potential attacker. It would be advised that the salt is kept secretly by your application, and passed to VerifyHash() directly.Almost forgot, credit for this article should go to Chris Varley, who made me aware of this in the first place. Check out his blog at -
http://scalethis.blogspot.com/