Using Secure Hashing safely in .Net

by martin 5. October 2011 22:41

SHA 1 and SHA 256 are both great hashing algorithms, but both are susceptible to a Rainbow Table attack and with Moore's law and the great race by Google, Bing, and other search engines to index absolutely every bit of data out there on the internet, the chances of these attack succeeding grows every day.  As a developer fending off this kind of vector, it is considered a best practice to not only always salt your data before hashing it, but to always make sure that the salt is completely random in nature.  Additionally, since both SHA 1 and SHA 256 are Big - O(n) algorithms as far as performance cost is concerned and relatively inexpensive to execute on modern hardware, it is also a good idea to have multiple iterative calls to the hashing algorithm to come up with the hash.  And while we are at it, we might as well switch up between using both algorithms just in case one has a yet undiscovered flaw similar to that which was discovered with MD-5 or even SHA-0 before that.  Below is a C# implementation of a secure hash helper class to help you do all this.  It provides a way to create a hash from a value and to confirm that a hash was made from a given value.  It currently does additional Base64 encoding/decoding of the hash value for ease of use in web applications (tamper proofing forms against request forgery, storing password hashes, etc), but this can easily be removed if you'd prefer to deal in raw byte arrays.

 
Code Snippet
  1. using System;
  2. using System.Security.Cryptography;
  3.  
  4. namespace SecureHashTestConsole
  5. {
  6.     public static class SecureHashHelper
  7.     {
  8.         // set this to a relatively small number that you like.  (4 to 25 suggested range)  Larger number = longer random hash
  9.         private const int defaultNumBytesForSeed = 9;
  10.  
  11.         // set this to a decent number, not too low, not to high (5 to 1000 suggested range)  Larger number = slower performance but less likely rainbow table cracking possibility
  12.         private const int defaultIterations = 88;     
  13.  
  14.         public static bool CompareSecureHash(string TestHash, string TestValue, int numRandBytesForSeed = defaultNumBytesForSeed, int iterations = defaultIterations)
  15.         {
  16.             byte[] hashArray = Convert.FromBase64String(TestHash);
  17.             byte[] derivedSalt = new byte[numRandBytesForSeed];
  18.             Buffer.BlockCopy(hashArray, 0, derivedSalt, 0, numRandBytesForSeed);
  19.             string strComp = CreateSecureHash(TestValue, derivedSalt, iterations);
  20.             return strComp.Equals(TestHash);
  21.         }
  22.  
  23.         public static string CreateSecureHash(byte[] sbuffer, int numRandBytesForSeed = defaultNumBytesForSeed, int iterations = defaultIterations)
  24.         {
  25.             RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
  26.             byte[] randSalt = new byte[numRandBytesForSeed];
  27.             rand.GetBytes(randSalt);
  28.             return CreateSecureHash(randSalt, sbuffer, iterations);
  29.         }
  30.  
  31.         public static string CreateSecureHash(string ValueToHash, int numRandBytesForSeed = defaultNumBytesForSeed, int iterations = defaultIterations)
  32.         {
  33.             RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
  34.             byte[] randSalt = new byte[numRandBytesForSeed];
  35.             rand.GetBytes(randSalt);
  36.             System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
  37.             Byte[] bytes = encoding.GetBytes(ValueToHash);
  38.             return CreateSecureHash(randSalt, bytes, iterations);
  39.         }
  40.  
  41.         private static string CreateSecureHash(string ValueToHash, byte[] randSalt, int iterations = defaultIterations)
  42.         {
  43.             System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
  44.             Byte[] bytes = encoding.GetBytes(ValueToHash);
  45.             return CreateSecureHash(randSalt, bytes, iterations);
  46.         }
  47.         private static string CreateSecureHash(byte[] randSalt, byte[] sbuffer, int iterations = defaultIterations)
  48.         {
  49.             SHA1Managed sha1 = new SHA1Managed();
  50.             SHA256Managed sha256 = new SHA256Managed();
  51.  
  52.             byte[] buffer = new byte[randSalt.Length + sbuffer.Length];
  53.             Buffer.BlockCopy(randSalt, 0, buffer, 0, randSalt.Length);
  54.             Buffer.BlockCopy(sbuffer, 0, buffer, randSalt.Length, sbuffer.Length);
  55.  
  56.             for (int i = 0; i < iterations; i++)
  57.             {
  58.                 // flip the order of using these two around if you want longer hashes
  59.                 sha256.ComputeHash(buffer);
  60.                 buffer = sha256.Hash;
  61.                 sha1.ComputeHash(buffer);
  62.                 buffer = sha1.Hash;
  63.             }
  64.  
  65.             byte[] output = new byte[buffer.Length + randSalt.Length];
  66.             Buffer.BlockCopy(randSalt, 0, output, 0, randSalt.Length);
  67.             Buffer.BlockCopy(buffer, 0, output, randSalt.Length, buffer.Length);
  68.  
  69.             return Convert.ToBase64String(output);
  70.         }
  71.     }
  72. }

Here is a simple console driver application that demonstrates its usage:

Code Snippet
  1. using System;
  2.  
  3. namespace SecureHashTestConsole
  4. {
  5.     class Program
  6.     {
  7.         static void Main(string[] args)
  8.         {
  9.             var testValues = new[] {"Testing" , "2", "4343", "help", "This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here"};
  10.             foreach (string strTestValue in testValues)
  11.             {
  12.                 for (int i = 0; i < 10; i++)
  13.                 {
  14.                     String hash = SecureHashHelper.CreateSecureHash(strTestValue);
  15.                     Console.WriteLine("Hash #{0} of '{1}' = '{2}'", i+1, strTestValue, hash);
  16.                     Console.WriteLine("Comparison = {0} (should be True)", SecureHashHelper.CompareSecureHash(hash, strTestValue));
  17.                     Console.WriteLine("Comparison = {0} (should be False)", SecureHashHelper.CompareSecureHash(hash, strTestValue + " "));
  18.                 }
  19.             }
  20.         }
  21.     }
  22. }

Example Output

Hash #1 of 'Testing' = 'qxj1uwwjKs+4sP9mUc96gpUIVYRcyD2FliNh'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #2 of 'Testing' = '4E7baUemZRmM7oYWk2aWdQ5jpnQ8OJcr4HmS'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #3 of 'Testing' = '0RRhLyFefkcLP9X5qWp/Nih8RNjezPzHm2hM'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #4 of 'Testing' = '/c2cdFCuc+eWAOxp192gSeSdNzWkddxkOuvD'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #5 of 'Testing' = 'OWrWE/821zy8siYjnGA+VD6UYXFRJjaW9gom'

Comparison = True (should be True)

Comparison = False (should be False)

 

.....................................

 

 

Hash #10 of 'help' = 'yiTptJocrqyFRpp6Hlk4/cXF04T6SQdsqqt7'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #1 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'qeIzv06CJeDXzu4p3K/uFSxcqVH45Wm6ESjo'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #2 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'xrQm1kuNrczVrdONoS8sRjddq0zq2si02hyN'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #3 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'WO7aN2lXoZgg7m0EYDnKqoRQw7mg3CSH0ori'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #4 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'CgQq75ZBm40P2IrNzaVZRBBCjjn3QdlN2Ivt'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #5 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'wXGXvdU1TBQCluwL2Kce1x4SbUU0YuFup31V'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #6 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'Xzpdr5p4RHJ1O24LdDxruL4+nw+NOp0tjrsh'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #7 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'kYFQgmaZp3VG1LjwYslKdGHZg7nnlZfLHDHB'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #8 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'nA/7Ky7xbMpAZ1OVuLF7Ku/0YarzZXpKIfA8'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #9 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'b875hD3alHDPv4yrl46GsvgGtuQOLpsECj7o'

Comparison = True (should be True)

Comparison = False (should be False)

Hash #10 of 'This is a test to see if the hash will be small even though the text is a bit bigger than the others that are listed here' = 'BJHwf4plv/t0Hpx1oi0l0VuZa9Dq0FppYI2J'

Comparison = True (should be True)

Comparison = False (should be False)

Currently rated 3.0 by 5 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

ASP.Net | C#

Impossible to show client side validation summary message on form validation failure using the default MS MVC 3 validation?

by martin 15. April 2011 23:05

So Microsoft has a bug in that they do not allow you to show the validation summary message in client side validation mode when you tell it to exclude the property error detail messages in the Html.ValidationSummary(true,"Please enter the required fields below.") way.

I've seen people fix this using jquery validation and unobstructive, but not with the out of the box default MicrosoftMvcValidation.js.

So there were two problems: 

The first was that passing true to ValidationSummary set the replaceValidationSummary parameter in the autogenerated window.mvcClientValidationMetadata.push statement.  What they should have been doing instead is simply not initializing the validationSummaryULElement.  

Ok... this isn't pretty, but it works.  Simply load MicrosoftMvcValidation.js in your page and then execute the code below to dynamically patch the code.

$(function () {    // this function fixes a bug.  It allows the validation summary message to be shown clientside even when the "excludePropertyErrors" bool is set to true in Html.ValidationSummary    
	var old_validate = Sys.Mvc.FormContext.prototype.validate;    
	Sys.Mvc.FormContext.prototype.validate = function () {        
		if (this.replaceValidationSummary == false) {  // only mess with this if it is turned off (MS bug)            
			this.replaceValidationSummary = true// this is for both the debug and non debug versions            
			this._validationSummaryULElement = null;  // this is for the debug version            
			this.$8 = null;  // this is for the non debug version (does the same thing as the line above)        
		}        
		return old_validate.apply(this);   
	}
});

Currently rated 3.0 by 5 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

ASP.Net | JavaScript

ASP.NET MVC Url.Content works inconsistently in HEAD tag

by martin 26. October 2010 14:13
I was trying to fix an MVC master page so that it correctly rendered urls for the referenced style sheets and JavaScript file regardless what the URL path looked like.  So I ran across this StackOverflow article which told me to use Url.Content to properly resolve my relative references from the application root (not the web server root).  So I did this.  The all of the sudden, I started getting crazy error messages saying "A potentially dangerous Request.Path value was detected from the client (<)."  It turns out that ASP.NET process things inconsistently.  After reading this post and playing around, I realized that script src URLs are treated one way and link href URLs are treated a different ways when your HEAD tag has the RunAt attribute set to Server (which it does by default).  I guess I'm surprised that there wasn't more about this anomaly out there.  I figured that there would be a lot more folks out there using MVC that wanted to put both script and link tags in their master page so that they worked regardless of the routing path in the URL.

Currently rated 3.0 by 5 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

ASP.Net

Sorting custom data objects the easy way

by martin 28. September 2009 22:32

I recently had to implement custom sorting on couple of ASP.net pages that had to bind to a collection of custom data objects.  Not wanting to re-invent the wheel on every page, I looked for an easier solution online.  I ran across at least two worth mentioning: Generic Sorting Using Reflection in .NET By Jon Wojtowicz (http://www.eggheadcafe.com/articles/pfc/propertycomparer.asp) and Rocky Lhotka's MSDN article where he presents his Generics based PropertyComparer.  I liked Jon's solution in that it was written using recursion so that you could easily sort by properties of properties.  However Jon's implementation was not "Generics" based.  Rocky's implementation was generics based, but it lacked the recursion that my solution needed.  In the end, I ended up modifying Jon's code to be generics based.  I sent Jon an email asking for permission to share his modified code online, but unfortunately he never responded to my email.

Below is a link to my patch file to Jon's PropertyComparer.cs file.  You can download the original directly from Jon's project hosted on EggHeadCafe in the link above.

PropertyComparer.patch (2.99 kb)

Here is an example of how you would use this to support sorting in a Telerik RadGrid:

        protected void RadGrid1_SortCommand(object source, GridSortCommandEventArgs e)
        {
            List<Employee> employees = (List<Employee>)RadGrid1.DataSource;
            CompareOrder cmpOrder;
            GridSortExpression expression = new GridSortExpression(); 

            if (e.NewSortOrder.Equals(GridSortOrder.Ascending))
            {
                cmpOrder = CompareOrder.Ascending;
                expression.SortOrder = GridSortOrder.Ascending;
            }
            else
            {
                cmpOrder = CompareOrder.Descending;
                expression.SortOrder = GridSortOrder.Descending;
            }

            employees.Sort(new PropertyComparer<Employee>(e.SortExpression, cmpOrder));

            expression.FieldName = e.SortExpression;

            e.Item.OwnerTableView.SortExpressions.Clear();
            e.Item.OwnerTableView.SortExpressions.AddSortExpression(expression);
            e.Item.OwnerTableView.Rebind();
            e.Canceled = true;
        } 
 

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , ,

ASP.Net | C#

Anonymous HTTP PUT with IIS 7

by martin 22. June 2009 21:32

From googling around in trying to get HTTP put enabled on IIS 7, I have concluded that turning on HTTP PUT verb support requires the installation of the WebDAV handler extension and that even then it only works in integrated authentication mode.  If this is not true, please let me know as I was forced to write my own put HTTP handler.  It actually turned out to be a lot easier than I thought it was going to be though.  Implementing IHttpHandler, I ended up just having to binaryread the request into a byte array and then use a binarywriter to write it to a file after validating the PhysicalPath of the request for user permissions and validating that the "pattern" is safe (ex. file extension, path).  I haven't tested the performance against the IIS 6 native solution, but it seems quite fast on today's server hardware.

 

Update May '11:  Here is some code to get you started:

 

Web.Config
  1. <appSettings>
  2.     <add key="BasePutPath" value="C:\PutUploadDir\" />
  3.     <add key="PutSecurityRegularExpression" value="^.+\\UPLOAD\\(\{[0-9A-F]{8}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[0-9A-F]{4}\-[0-9A-F]{12}\})\\(.+\.DOC)$"/>
  4. </appSettings>

 

PutHandler.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Text.RegularExpressions;
  5. using System.Web;
  6. using System.Data.Sql;
  7. using System.Data;
  8. using System.Data.SqlClient;
  9. using System.IO;
  10. using System.Configuration;
  11. using System.Drawing;
  12. using System.Drawing.Imaging;
  13. using System.Drawing.Drawing2D;
  14. using log4net;
  15. using log4net.Config;
  16.  
  17. namespace IIS7Helper
  18. {
  19.     public class PutHandler : IHttpHandler
  20.     {
  21.         private static ILog log;
  22.         private static string strBasePutPath;
  23.         private static string strPutSecurityRegularExpression;
  24.  
  25.         static PutHandler()
  26.         {
  27.             log = LogManager.GetLogger(typeof(PutHandler));
  28.             strBasePutPath = ConfigurationManager.AppSettings["BasePutPath"];
  29.             strPutSecurityRegularExpression = ConfigurationManager.AppSettings["PutSecurityRegularExpression"];
  30.         }
  31.     
  32.  
  33.         public bool IsReusable
  34.         {
  35.             get { return true; }
  36.         }
  37.  
  38.         
  39.         public void ProcessRequest(HttpContext context)
  40.         {
  41.             try
  42.             {
  43.                 byte[] arrBytes = context.Request.BinaryRead(context.Request.ContentLength);
  44.                 string strLocation = context.Request.PhysicalPath.ToUpper();
  45.                 bool blnRequestSecure = false;   // trust no request by default
  46.  
  47.                 // sample url
  48.                 // https://xxx.yyy.com/Upload/{56DA31A8-15A8-4A0B-90F2-4A59909F5DCE}/MyFile.doc
  49.  
  50.                 string strDirName = string.Empty;
  51.                 string strFileName = string.Empty;
  52.                 string strPhysicalLocation = string.Empty;
  53.  
  54.                 Regex re = new Regex(strPutSecurityRegularExpression, RegexOptions.Singleline);
  55.                 Match m = re.Match(strLocation);
  56.                 if (m.Success)
  57.                 {
  58.                     strDirName = m.Groups[1].Captures[0].Value;
  59.                     strFileName = m.Groups[2].Captures[0].Value;
  60.  
  61.                     strPhysicalLocation = Path.Combine(strBasePutPath, strDirName);
  62.  
  63.                     if (Directory.Exists(strPhysicalLocation))
  64.                     {
  65.                         blnRequestSecure = true;
  66.                     }
  67.                     else
  68.                     {
  69.                         log.Error("Directory does not exist : " + strPhysicalLocation);
  70.                     }
  71.                 }
  72.                 else
  73.                 {
  74.                     log.Error("Security Alert : URL RegEx failed for " + strLocation);
  75.                 }
  76.                 strPhysicalLocation = Path.Combine(strPhysicalLocation, strFileName);
  77.                 if (blnRequestSecure)
  78.                 {
  79.                     log.Debug("About to write data to " + strPhysicalLocation);
  80.                     BinaryWriter bw = new BinaryWriter(new FileStream(strPhysicalLocation, FileMode.CreateNew));
  81.                     bw.Write(arrBytes);
  82.                     bw.Flush();
  83.                     bw.Close();
  84.                     bw = null;
  85.                     context.Response.StatusCode = 200;
  86.                     context.Response.Flush();
  87.                 }
  88.                 else
  89.                 {
  90.                     log.Error("Fall through due to either not matching regex or dir not existing. " );
  91.                     context.Response.StatusCode = 500;
  92.                     context.Response.Flush();
  93.                 }
  94.             }
  95.             catch (Exception ex)
  96.             {
  97.                 log.Error("An error occurred : " + ex.ToString());
  98.                 context.Response.StatusCode = 500;
  99.                 context.Response.Flush();
  100.             }
  101.         }     
  102.     }
  103. }

 

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

ASP.Net | C# | Windows

Service Oriented Programming and DSLs?

by martin 19. November 2008 22:20

I recently attended Dev Connections and was shown the benefits of decoupling business logic from aspects such as authentication, authorization, security, transaction support, threading model, instantiation model, encoding, and transport mechanism.  In Juval Lowy's Windows Communication Framework (WCF) seminar, I was exposed to the idea of our software tools and runtime environments evolving to allow us to utilize these functionalities in business application without even doing anything additional.  Juval reiterated that WCF is not just about web service and explained many of the benefits that you would get for using WCF even on an application that does not span CPUs.  These benefits include fault tolerance, fault screening (or formal fault contracts), contract first development, and the benefit of a thought out framework that provides separation of concerns for many aspects of enterprise software development.

At PDC at the end of October, Microsoft announced Azure, Oslo, and Dublin.  So Azure, or the "cloud", will let you deploy and run your production applications easily and supposedly allow them to dynamically scale up on-the-fly as demand warrants.  I heard this maybe being accomplished by dynamically spawning new virtualized servers from software and data that are stored in a massive partitioned and redundant sql server cluster (or I may have dreamt all this).  Oslo is the hybrid (both visual (Quadrant) and textual) programming environment for creating and using custom Domain Specific Languages (DSL) and Dublin is a SQL server based repository and runtime environment for the resulting Oslo DSL programs and data structures.  The DSLs are written in Mgrammer ("Mg") and the modeling language itself is just called "M".  There is a silverlight video from MS on http://modelsremixed.com/ that shows the history of "modeling", starting with prehistoric times.  In it, you will notice that one of the process models that they show is comprised of a component with the tiny logo that reads "Wcf" in its top left corner.

So that is a lot of new information to absorb.... but what does this all mean?  Will we all really be orchestrating business process workflows written in "vertical" DSLs?  Where does BPEL fit in?  Is this Microsoft's killer-app ESB?

http://msdn.microsoft.com/en-us/oslo/default.aspx

 

 

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

ASP.Net | C# | Windows

Welcome

Please contact me if you have a great idea for a project and need technical expertise in designing, developing, or integrating a custom software solution.

Recent Comments

Comment RSS