16
votes

I am hashing password using the .NET System.Security.Cryptography class. It has a few algorithms for hashing, e.g. MD5, SHA1, SHA256, SHA384, SHA512

The resultant hashed value is a byte array. Should i convert it to a hex string for storing, or Convert.ToBase64String(), or to something else? (I am favoring Base64 as it is shorter than Hex).

Incidentally with so many hashing algo's to pick from, i randomly chose SHA384, but is there one that is "better" or suitable to the task?

Comments please.

Update after reading first eight comments:
Going by the answers and further reading i've done, it seems MD5,SHA1 are more or less equivalent (with SHA1 being slightly more secure). SHA256, 384, 512 provide even better security in increasing order.

Since i won't be needing fort-knox (this is for an internal corporate system with no urls, browsers, internets, intranets, or extranets in sight), i will bypass the "salting" business - i figured if someone can steal the passwords table, they may as well steal the actual data in other tables.

But i will keep the "salt" concept for future reference; not sure if the salt should be appended (at the end) or prepended (at the front) of the password before hashing, and would it make a difference? Also i was thinking of using the first few chars of the password itself as the salt, to avoid an extra field to store it, but i guess it's not long enough - and the salt should be long enough.

The consensus says base64 conversion is a reasonable choice for storage and comparison. It remains for me to figure out what's the max database column lenght i will need for hash storage, given a max password lenght of 15 chars. Perhaps Varchar(64)?

Thank you everyone for your contribution.

10
The length of the field is dependent on your chosen hash, not the length of the passwords. There is no reason to limit the length or available characters for the password. For SHA384 it would be 384/6 characters (64) since each base64 character represents 6 bits.Bell
Why not practice doing it the secure way? :) Do the following: 1. Make a constant128-bit global salt that is stored in your .NET app; 2. Make a 128-bit local salt that is individual for each password and stored next to in in the DB; 3. Calculate a local number N between 1000 and 1500 and store it in the DB, next to each password; 4. Prepend the global and append the local salt, then hash with N iterations of SHA512. Store this in the DB. Guaranteed to be a secure way to store your passwords. :DVilx-
Persisting passwords securely is, really, very easy. There's no excuse for not doing it right, especially not "I don't need Fort Knox". Depending on the database system and the language that you will be using, you may decide to store salts and hashes as byte-arrays (BINARY in SQL Server - for SHA512, you will need a BINARY(64) column for the hash).yfeldblum
Salting your hash makes a much bigger difference than choosing SHA256 over MD5. If you don't salt, reverse lookup will be much easier (simple google search can bring results in milliseconds) and if someone gets the db with the passwords, he can at least know what are the passwords used by several people and start with cracking those. So, start using a salt first, and ponder the probabilities of cracking SHA256 against SHA512 later. (that said, forget MD5)Yann Schwartz

10 Answers

12
votes

Even if your solution is not fort-knox, you should be diligent and implement salting. Because many people reuse their passwords elsewhere, and the break-in will do additional damage outside your organization, should the attackers choose to use the cracked password database for other purposes.

Salting makes dictionary attacks more expensive. By deciding what salt size to use you can fine tune your chances. Here's the quote from Bruce Schneier's "Applied Cryptography":

"Salt isn't a panacea; increasing the number of salt bits won't solve everything. Salt only protects against general dictionary attacks on a password file, not a concerted attack on a single password. It protects people who have the same password on multiple machines, but doesn't make poorly chosen passwords any better."

Here's a sample in C#. It ain't that hard. And you can choose what salt size and hash function to use. Disclaimer: use something like bcrypt if you really care about password integrity.


using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

public class PassHash {
    private static readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
    public static readonly int DefaultSaltSize = 8; // 64-bit salt
    public readonly byte[] Salt;
    public readonly byte[] Passhash;

    internal PassHash(byte[] salt, byte[] passhash) {
        Salt = salt;
        Passhash = passhash;
    }

    public override String ToString() {
        return String.Format("{{'salt': '{0}', 'passhash': '{1}'}}",
                             Convert.ToBase64String(Salt),
                             Convert.ToBase64String(Passhash));
    }

    public static PassHash Encode<HA>(String password) where HA : HashAlgorithm {
        return Encode<HA>(password, DefaultSaltSize);
    }

    public static PassHash Encode<HA>(String password, int saltSize) where HA : HashAlgorithm {
        return Encode<HA>(password, GenerateSalt(saltSize));
    }

    private static PassHash Encode<HA>(string password, byte[] salt) where HA : HashAlgorithm {
        BindingFlags publicStatic = BindingFlags.Public | BindingFlags.Static;
        MethodInfo hasher_factory = typeof (HA).GetMethod("Create", publicStatic, Type.DefaultBinder, Type.EmptyTypes, null);
        using (HashAlgorithm hasher = (HashAlgorithm) hasher_factory.Invoke(null, null))
        {
            using (MemoryStream hashInput = new MemoryStream())
            {
                hashInput.Write(salt, 0, salt.Length);
                byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
                hashInput.Write(passwordBytes, 0, passwordBytes.Length);
                hashInput.Seek(0, SeekOrigin.Begin);
                byte[] passhash = hasher.ComputeHash(hashInput);
                return new PassHash(salt, passhash);
            }
        }
    }

    private static byte[] GenerateSalt(int saltSize) {
        // This generates salt.
        // Rephrasing Schneier:
        // "salt" is a random string of bytes that is
        // combined with password bytes before being
        // operated by the one-way function.
        byte[] salt = new byte[saltSize];
        rng.GetBytes(salt);
        return salt;
    }

    public static bool Verify<HA>(string password, byte[] salt, byte[] passhash) where HA : HashAlgorithm {
        // OMG: I don't know how to compare byte arrays in C#.
        return Encode<HA>(password, salt).ToString() == new PassHash(salt, passhash).ToString();
    }
}

Usage:

New user submits their credentials.

PassHash ph = PassHash.Encode<SHA384>(new_user_password);

Store ph.Salt & ph.Passhash somewhere ... Later, when user logs in again, you look up the user record that has salt & passhash, and then you do this:

PassHash.Verify<SHA384>(user_login_password, user_rec.salt, user_rec.passhash)

}

8
votes

Base64 is indeed shorter than hex, with the slight downside that you need to be careful if it ever ends up in a URL (due to the symbols involved). It depends where you're using it, basically. Hex normally has the advantage that it's easier to "read" with the naked eye, but as this will be essentially meaningless data, that's not relevant for a hash. (You could always use an online base64 decoded if necessary.)

This is all assuming you want a string, of course. Strings are nice and easy to store, transmit etc compared with opaque byte arrays.

4
votes

Incidentally with so many hashing algo's to pick from, i randomly chose SHA384, but is there one that is "better" or suitable to the task?

In general I would avoid MD5 or SHA-0 or SHA-1. But SHA-384 should be sufficient for your needs at the moment. See this question for some discussion on algorithms.

4
votes

Interesting note I've seen before, if you have an array of bytes, and you base64 encode it, some of the resulting characters can cause problems in urls - however, if you base 64 encode it a second time, those offending characters tend to be removed, and the base64 string can be used in urls. You can compare using the double encoded string.

On hashing algorithms, sha384 is probably a sufficient algorithm, but ensure that you salt your hash!

4
votes

To answer the second part of your question, SHA384 is probably overkill. SHA1 would serve your purposes just fine but there is a theoretical exploit for it (relating to being able to create a tampered document that hashes to the same value, nothing you need to worry about).

And in case you're not doing it, make sure you salt your passwords before hashing them to protect against the more common hacker attacks (identifying similar passwords, rainbow tables, etc). Read this article for more information.

For the record, I like Base64 since .NET has built-in routines for converting to/from it.

3
votes

You could also convert it to plain bytes. Thus a 128 bits MD5 hash value would result in a 128 bit / 8 bit/byte = 16 byte long string.

0
votes

For passwords, I believe that any of them serves perfectly. The SHA 512 will create a key higher, reducing the chances of a "collision", knowing that different passwords may have the same hash, but a possibility is very low.

0
votes

The answer depends on the context and constrain you might have. Base64 will be more compact but a bit more computing expensive to generate compared to hex encoding.

You should add a salt to your hashed data. It is a random value put in front of the pasword you hash. Then store the salt value with the hashed value so that you can regenerate the hash value.

The role of this salt is to prevent building dictionnary mapping common passwords to hashed values. If someone can get a copy of your hashed password file, he could use this dictionary to find the clear text password. With a 16bit salt you have 65536 different hash values for the same clear text password. The dictionnary attack becomes less efficient. Better pick a 64bit salt, because it won't add much more effort to hash and verify passwords.

You should also add a magic byte sequence (constant byte sequence) with the hashed value. The attacker will have to know this magic byte sequence in order to be able to generate the appropriate hash values. So he won't go very far if the only thing it has is the hashed password file. He will need to get the magic word that will be most likely burried in your code somewhere.

Regarding the algorithms to use SHA1 is very good and SHA256 would be belt and straps. MD5 is much less expensive to compute and would satisfy most use case. Consider this if your processing and storage resource might be a limiting factor.

0
votes

I would use Base64 .... oh and dont forget to salt your hash if its a password you are hashing :) And anything smaller than SHA384 is a security risk if we talk about password, because is so easy to get quality rainbow tables for SHA1 and other common hashing algos.

0
votes

How about using Base36 or Base62 notation which contains only alphabets and digits (Safely can transmit in URLS). I simply tried the following in java

BigInteger hexNumber = new BigInteger(hexString, 16);
// Convert it to Base 36 yields smaller string than hexString
hexNumber.toString(36);

Convert the Base36 Number back to Hex

BigInteger b36String= new BigInteger(b36String, 36);
// Convert it to Base 16 yields original hex string
hexNumber.toString(16);

BTW Java's BigInteger Maximum radix value is 36.

For Base62 We can have a lookup table with the 62 chars (26 lower, 26 upper and 10 digits) mathematically divide the hex number by 64 and keep append the character retrieved from the lookup table by using remainder.

Suggestions are welcome.