Secure Password Hashing in C#

Long-term storage of logon credentials in computer systems is a serious point of vulnerability. Too many systems store credentials in an unencrypted or weakly encrypted form that system administrators or database power-users can view or edit. This may seem OK as long as a set of conditions were true:

  • The administrators of the system are known to be trustworthy and no untrusted users have the ability to view, alter or copy the password file or table.
  • The normal execution flow of the system could not be used to reveal plaintext passwords.
  • The online and backup repositories of authentication data are secure.
    Employee turnover, the increasing complexity of internet-connected systems, and the spread of knowledge and tools for executing simple security exploits all work together to change the rules of the game so that storing passwords in plaintext is no longer acceptable.  Disgruntled employees are now widely acknowledged to be the single largest vulnerability for most companies, and are the most common attackers of computer systems.  Defects in production software, runtimes and platforms can also disclose sensitive information to attackers. A litany of recent headlines where a wide range of organizations have lost control of their authentication database prove that systems that store “secret” but plaintext logon information are unsafe.

Theory of Password Hashing in Brief

The key to hardening authentication systems to use a strong cryptographic function to hash passwords into an unrecoverable blob. A strong hashing algorithm must guard against brute force offline attack via a rainbow tables precompiled hashes on dictionary words. Rainbow tables and software to use them are publicly available which lowers the password cracking bar to the level of script kiddie.

Niels Furguson and Bruce Schneier in Practical Cryptography describe strong a strong hashing technique for safe storage of passwords which requires.

  • A publicly tested secure hashing algorithm such as SHA-256. Neither MD5 nor SHA-1 are good enough anymore.
  • A randomly-generated value known as a Salt is mixed with the plaintext. The salt value is not a secret, it exists to make the output of the hash non-deterministic. There should be a unique salt for each user and a new random salt value should be generated every time a password is set.
  • The hash is stretched, meaning that the output is fed back through the algorithm a large number of times in order to slow down the hashing algorithm to make brute force attacks impractical.

In order to authenticate a user, the system retrieves the salt for the username, calculates the hash for the password the user provided and compares that hash to the value in the authentication database. If the hashes match, then access is granted.

Implementation in C#

In hope of making it easy for .NET developers to implement good authentication security, my company published a secure password hashing algorithm in C# under the BSD license in 2005. This algorithm is a part of the security subsystem of our PeopleMatrix product.

Because the code is no longer available on thoughtfulcomputing.com, so I’m republishing hit here.

Download HashManager source with a sample application.

//*****************************************************************
// <copyright file="HashManager.cs" company="WolfeReiter">
// Copyright (c) 2005 WolfeReiter, LLC.
// </copyright>
//*****************************************************************

/*
** Copyright (c) 2005 WolfeReiter, LLC
**
** This software is provided 'as-is', without any express or implied warranty. In no 
** event will the authors be held liable for any damages arising from the use of 
** this software.
**
** Permission is granted to anyone to use this software for any purpose, including 
** commercial applications, and to alter it and redistribute it freely, subject to 
** the following restrictions:
**
**    1. The origin of this software must not be misrepresented; you must not claim 
**       that you wrote the original software. If you use this software in a product,
**       an acknowledgment in the product documentation would be appreciated but is 
**       not required.
**
**    2. Altered source versions must be plainly marked as such, and must not be 
**       misrepresented as being the original software.
**
**    3. This notice may not be removed or altered from any source distribution.
**
*/

using System;
using System.Globalization;
using System.Text;
using System.Security.Cryptography;

namespace WolfeReiter.Security.Cryptography
{
	/// <summary>
	/// HashManager provides the service of cryptographically hashing and verifying strings
	/// against an existing hash. HashManager uses the SHA-256 hashing transform by default.
	/// </summary>
	/// <remarks>
	/// <para>The MD5 and SHA1 algorithms should be avoided because they are not strong enough
	/// to resist attack with modern processors. Also, it appears that the algorithms for MD5 and SHA1
	/// may have a flaw known as "collision". The short story is that  an attacker can discover the 
	/// plaintext used to generate the hash in much fewer steps than expected.</para>
	/// <para>For example, SHA1 should theoretically require 2<sup>80</sup> steps to brute-force recover 
	/// the original plaintext. Researchers have recently claimed to have a method of recovering SHA1 plaintext
	/// in 2<sup>33</sup> steps.</para></remarks>
	[Serializable]
	public sealed class HashManager 
	{
		private HashAlgorithm _transform;
		private readonly ulong _iterations;

		/// <summary>
		/// CTOR. Creates a new HashManager object that uses the SHA-256 algorithm and stretches the entropy in the hash
		/// with 2<sup>16</sup> iterations.
		/// </summary>
		public HashManager() : this("SHA-256",65536){}
		/// <summary>
		/// CTOR. Creates a new HashManager object that uses the specified well-known hash transform algorithm.
		/// </summary>
		/// <param name="transform">Well-known transform algorithm (eg. MD5, SHA-1, SHA-256, SHA-384, SHA-512, etc.).</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(string transform, ulong iterations) : this(HashAlgorithm.Create(transform),iterations){}

		/// <summary>
		/// CTOR. Creates a new HashManager object that uses provided transform hash algorithm.
		/// </summary>
		/// <param name="transform">HashAlgorithm object to use.</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(HashAlgorithm transform, ulong iterations)
		{
			if( iterations < 1 )
				throw new ArgumentOutOfRangeException("iterations", iterations, "The number of iterations cannot be less than 1");
			_transform  = transform;
			_iterations = iterations;
		}
		/// <summary>
		/// CTOR. Creates a new HashManager object that uses the specified well-known hash transform algorithm.
		/// </summary>
		/// <param name="transform">Well-known transform algorithm (eg. MD5, SHA-1, SHA-256, SHA-384, SHA-512, etc.).</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(string transform, long iterations) : this(HashAlgorithm.Create(transform),iterations){}

		/// <summary>
		/// CTOR. Creates a new HashManager object that uses provided transform hash algorithm.
		/// </summary>
		/// <param name="transform">HashAlgorithm object to use.</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(HashAlgorithm transform, long iterations)
		{
			if( iterations < 1 )
				throw new ArgumentOutOfRangeException("iterations", iterations, "The number of iterations cannot be less than 1");
			_transform  = transform;
			_iterations = (ulong)iterations;
		}
		/// <summary>
		/// Hashes a plaintext string.
		/// </summary>
		/// <param name="s">Plaintext to hash. (not nullable)</param>
		/// <param name="salt">Salt entropy to mix with the s. (not nullable)</param>
		/// <returns>HashManager byte array.</returns>
		/// <exception cref="ArgumentNullException">Throws if either the s or salt arguments are null.</exception>
		public byte[] Encode( string s, byte[] salt )
		{
			if( s==null )
				throw new ArgumentNullException("s");
			if( salt==null )
				throw new ArgumentNullException("salt");

            return Encode( ConvertStringToByteArray( s ), salt );
		}

        public byte[] Encode( byte[] plaintext, byte[] salt )
        {
            byte[] sp = salt;
            byte[] hash = plaintext;
            //stretching via multiple iterations injects entropy that makes collision plaintext recovery much
            //more difficult.
            for( ulong i = 0; i < _iterations; i++ )
            {
                sp = Salt( hash, salt );
                hash = _transform.ComputeHash( sp );
            }

            return hash;
        }

		private byte[] Salt( byte[] p, byte[] salt )
		{
			byte[] buff = new byte[p.Length + salt.Length];
			for( int i=0; i<p.Length; i++ )
				buff[i] = p[i];
			for( int i=0; i<salt.Length; i++ )
				buff[i + p.Length] = salt[i];
			
			return buff;
		}

		/// <summary>
		/// Verifies a plaintext string against an existing hash. This is a case-sensitive operation.
		/// </summary>
		/// <param name="s">Plaintext to verify</param>
		/// <param name="hash">HashManager to compare with the s</param>
		/// <param name="salt">Salt that was used with the original s to generate the hash</param>
		/// <returns>True if the s is the same as the one used to generate the original hash.
		/// Otherwise false.</returns>
		/// <exception cref="ArgumentNullException">Throws if any of the s, hash or salt arguments are null.</exception>
		public bool Verify( string s, byte[] hash, byte[] salt )
		{
			if( s == null )
				throw new ArgumentNullException("s");
			if( salt == null )
				throw new ArgumentNullException("salt");
			if( hash == null )
				throw new ArgumentNullException("hash");

            byte[] testhash = Encode( s, salt );
            return _Verify( testhash, hash );
		}

        public bool Verify( byte[] plaintext, byte[] hash, byte[] salt )
        {
            if( plaintext == null )
                throw new ArgumentNullException( "plaintext" );
            if( salt == null )
                throw new ArgumentNullException( "salt" );
            if( hash == null )
                throw new ArgumentNullException( "hash" );

            byte[] testhash = Encode( plaintext, salt );
            return _Verify( testhash, hash );
        }

        private bool _Verify( byte[] a, byte[] b )
        {
            if( a.Length != b.Length )
                return false;
            for( int i = 0; i < a.Length; i++ )
            {
                if( a[i] != b[i] )
                    return false;
            }
            return true;
        }

		/// <summary>
		/// Generates a 32 byte (256 bit) cryptographically random salt.
		/// </summary>
		/// <returns>256 element Byte array containing cryptographically random bytes.</returns>
		public static byte[] GenerateSalt()
		{
			return GenerateSalt(32);
		}
		/// <summary>
		/// Generates a cryptogrpahically random salt of arbirary size.
		/// </summary>
		/// <param name="size">size of the salt</param>
		/// <returns>Byte array containing cryptographically random bytes.</returns>
		public static byte[] GenerateSalt(int size)
		{
			Byte[] saltBuff = new Byte[size];
			RandomNumberGenerator.Create().GetBytes( saltBuff );
			return saltBuff;
		}

		/// <summary>
		/// Convert a hash (or salt or any other) byte[] to a hexadecimal string.
		/// </summary>
		/// <param name="hash">Byte[] to convert to hex.</param>
		/// <returns></returns>
		public static string ConvertToHexString( byte[] hash )
		{
			StringBuilder sb = new StringBuilder();
			foreach( byte b in hash )
			{
				string bytestr = Convert.ToInt32(b).ToString("X").ToLower();
				if( bytestr.Length==1)
					bytestr = '0' + bytestr;
				sb.Append( bytestr );
			}
			return sb.ToString();
		}

		/// <summary>
		/// Convert a hexadecimal string to byte[].
		/// </summary>
		/// <param name="s"></param>
		/// <returns></returns>
		public static byte[] ConvertFromHexString( string s )
		{
			string hex = s.ToLower();
			if( hex.StartsWith("0x") )
				hex = hex.Substring(2);

			//ensure an even hex number
			hex = (hex.Length % 2 == 0) ? hex : '0' + hex;
			//2 hex digits per byte
			byte[] b = new byte[hex.Length/2];
			for(int i=0,j=0; i<hex.Length; i+=2,j++)
			{
				b[j] = byte.Parse( hex.Substring(i,2), NumberStyles.HexNumber );
			}
			return b;
		}

		/// <summary>
		/// Convert a hash (or salt or any other) byte[] to a base64 string.
		/// </summary>
		/// <param name="hash">Byte[] to convert to base64</param>
		/// <returns></returns>
		public static string ConvertToBase64String( byte[] hash )
		{
			return Convert.ToBase64String(hash);
		}
		
		/// <summary>
		/// Convert a base64 encoded string into a byte array.
		/// </summary>
		/// <param name="s">Base64 string.</param>
		/// <returns>Byte array.</returns>
		public static byte[] ConvertFromBase64String( string s )
		{
			return Convert.FromBase64String( s );
		}

		/// <summary>
		/// Converts a String to a Byte array.
		/// </summary>
		/// <remarks>Only works on .NET string objects or Unicode encoded strings.</remarks>
		/// <param name="s">String to convert</param>
		/// <returns>Byte array.</returns>
		public static byte[] ConvertStringToByteArray( string s )
		{
			if (s == null)
				return null;
			Char[] chars = s.ToCharArray();
			Encoder encoder = Encoding.Unicode.GetEncoder();
			int bytecount = encoder.GetByteCount( chars, 0, chars.Length, true );
			byte[] outbuff = new byte[bytecount];
			encoder.GetBytes(chars, 0, chars.Length, outbuff, 0, true );
			return outbuff;
		}
	}
}

BitCoin is Fatally Flawed

BitCoin is a peer-to-peer virtual currency based on strong cryptography that is starting to gain some traction for use in transactions for real-world goods and services. It recalls plot devices from Neal Stephenson’s Cryptonomicon  or Daniel Suarez’s Freedom™ where major characters are involved in a nascent new economy based on some form of cryptographic cyberbucks. To a certain extent virtual currencies are not new. Edward Castronova has shown that virtual currencies in massively muliplayer online games (MMO) have virtual currencies that exchange into hard currencies and therefore the MMO worlds have GDPs that rival large nations. People will exchange hard currency for virtual goods. Consider that Zynga (Farmville, Mafia Wars) earned half a billion US Dollars selling virtual goods in 2010.

The thing that is different about BitCoin is that it is a peer-to-peer system and as such there is no central bank to control the money supply and no middle-man to mediate transactions. Transactions are anonymous, and secured through a public key cryptogrpahy system.The record of every transaction is stored on every BitCoin node. Ownership of BitCoins is established by possessing the private key used in transactions to acquire coinage.

Instead of a central bank, BitCoin uses a complex, brute-force mathematical function to generate currency. As nodes come online, the peers scale the difficulty of solving the problem such that over time the rate of currency creation will decline as it approaches a maximum of 21 million BitCoins in circulation.

My critique of BitCoin doesn’t depend on the cryptography. I’m willing to stipulate that the system is adequately secure for the purposes of this discussion.

The problem lies in the cap of 21 million BitCoins being created as well as inevitable loss of private keys which will cause coinage to permanently fall out of circulation. This is a deflationary system.

BitCoin is a Deflationary Currency System and That’s Bad

Let’s do a thought experiment. Suppose that the world lost confidence in US Dollars, UK Pounds and Euros and instead BitCoin became the standard transactional currency. Currently, a BitCoin trades can be exchanged for roughly one US Dollar, but if it were used widely by billions of people the exchange rate would have to change such that a single BitCoin is worth millions of Dollars. This is because the supply of BitCoins is capped at 21 million. Anyone who acquired BitCoins now at one-to-one exchange rate and held on to them would be a huge winner.

BitCoins can be traded in fractions up to 8 decimal places. If BitCoin were to ever to become a general currency used by billions, this precision would not be enough for minimum transactions to be small enough to buy consumable goods.

But wait, there’s more.

Suppose you are an appliance dealer. You buy appliances in BitCoin and sell them at a later time in BitCoin. But because the value of BitCoin is always increasing, you may well be forced to sell your appliances for fewer BitCoins than you paid for them. Loans have to be paid back with money that is more valuable than the money that was loaned. Even a 0% loan is unattractive if it must be paid back with currency that is worth 10% or 20% more than what was loaded. It becomes unprofitable to be in business. It becomes too risky to take a loan to start a new business. Money becomes more valuable but it goes out of circulation because everyone is terrified to spend it.

This scenario is call a deflationary spiral. BitCoin is designed to work like the Gold Standard in that it is a fixed pool of currency. But the fact is that having a fixed pool of currency is incredibly dangerous. The Gold Standard caused the Great Depression in the 20th Century and the Panic of 1837 in the 19th century. If you’re not convinced, I recommend listening to “Gold Standard, R.I.P” from NPR’s Planet Money podcast.

I don’t think BitCoin will ever come to this. Long before there is a Great BitCoin Depression, the world will have realized that it is a flawed monetary system. I think we’re much more likely to end up with in a future that conducts business in Facebook Credits than BitCoin.

Related Listening

Mossad Used Blackberries During Assassination?

Some back story on the decision of UAE to ban Blackberry data service. It may be that the Mossad used pre-paid Blackberry handsets to communicate securely while when it assassinated Hamas operative Mahmoud al Mabhouh in a Dubai hotel last January.

via http://twit.tv/tnt46

Who is reading your email?

Email has no privacy assurance whatsoever

Email is short for “electronic mail”. The implication is that this is a direct metaphorical equivalent for the familiar paper process, but it just ain’t so. One of the points of departure from user expectation is the concept of a sealed envelope.

When you put a paper letter in a paper envelope and seal it, there is an expectation of privacy because someone has to physically break the seal which is difficult to do without obviously damaging the envelope. Email, by contrast has no secure envelope. It is transferred over the Internet using plain text over TCP port 25.

The implication is that anyone can read your mail without you realizing it. Most people think the only point of concern is that an attacker capturing packets at a public WiFi hotspot will snoop your mail. The most obvious countermeasure is using SSL with webmail. This is a good countermeasure as far as it goes but it only encrypts the communication between you and your post office. However the transport of your message between post offices is still going to be in plain text and will likely pass through several routers on the Internet en route.

So what? Who can tap traffic on those routers? Perhaps some uber-hacker is siphoning of traffic for analysis but man that seems like a huge amount of data to sift through and not likely worth doing, right?

Unfortunately Deep Packet Inspection is now ubiquitous. ISPs and governments, including the USA, have widely deployed hardware to capture and mine data out of unencrypted data packets passing through the Internet. Effectively that means it is safe to assume that all of your email (and other traffic) is being captured and analyzed by one or more automated systems trying to determine if it matches patterns of “bad behavior”. Deep Packet Inspection, by the way, is the mechanism that ISPs use to provide “tiered service” and detect copyright violations.

Maybe you think this sounds OK because you aren’t doing anything wrong, but consider whether you trust all of the people who have access to this data to do no evil, trust these people and algorithms to never make mistakes and trust that they never lose control of your private data.

A Modest Proposal

RFC 2478 describes a cryptographic mechanism similar to SSL for web sites that places a strong cryptographic layer over the transfer of email via SMTP. This transport layer security is widely supported by mail servers today and is the mechanism by which SMTP client programs like Microsoft Outlook, Mozilla Thunderbird and Apple Mail are able to communicate securely with an SMTP gateway. Many mail providers, like Gmail, already require (or at least offer) SMTP over TLS connections for clients sending mail.

RFC 2478 was published more than ten years ago. Most, if not all, contemporary SMTP gateways are capable of supporting secure SMTP over TLS. If all mail servers simply transferred all messages between each other with TLS encryption then nobody could read the messages except the administrators of the destination and source post offices and the intended recipients. This could be phased in over a reasonable period of time.

  • SMTP in the clear becomes deprecated.
  • During the deprecation period servers are configured to attempt to communicate with TLS by default.
  • After some time, administrators can configure warnings in the headers and to-line like “[UNSECURED]”. This is an important user-education step.
  • SMTP in the clear becomes a banned protocol and servers are forbidden from supporting it. (Testing and troubleshooting SMTP can still be done via classical telnet by using OpenSSL as a wrapper.)
  • Servers will refuse connections from servers that do not offer TLS which will cause the message to bounce to the sender with a statement that their mail server is not secure.

Securing all SMTP traffic in a TLS envelope would go a long way toward restoring some reality to the baseline assumption that nobody is reading email in transit. The technology has been standardized since 1999. Why do we not have secure server-to-server mail transfer ten years later?

%d bloggers like this: