17

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.

joedotnot
  • 4,810
  • 8
  • 59
  • 91
  • 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 Jun 04 '09 at 13:55
  • 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. :D – Vilx- Jun 04 '09 at 14:10
  • 2
    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 Jun 05 '09 at 00:29
  • 1
    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 Jun 05 '09 at 00:30

10 Answers10

12

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)

}

Pavel Repin
  • 30,663
  • 1
  • 34
  • 41
  • Code handed to me on a plate? i like that :-) But i had done most of it anyway. – joedotnot Jun 05 '09 at 01:04
  • 2
    To compare byte arrays you can do: ba1.SequenceEqual(ba2); // I love Linq – Talljoe Jun 05 '09 at 02:48
  • +1 Talljoe, I'm dinosaur. I was proficient in C# 2.0 and whatever .NET FCL was at, when I had to completely swap out that part of the brain to make room for Python coding. Since then good things happened C#, it seems (like LINQ). – Pavel Repin Jun 05 '09 at 04:19
8

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
4

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.

Community
  • 1
  • 1
4

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!

Alistair Evans
  • 36,057
  • 7
  • 42
  • 54
  • Here is a very informative article about "salting your hash". Read it before rolling your own password hashing/storage thing. http://www.matasano.com/log/958/enough-with-the-rainbow-tables-what-you-need-to-know-about-secure-password-schemes/ – Pavel Repin Jun 04 '09 at 08:46
  • Why would i have to decode it twice? can't i just hash the supplied password, encode it twice, and compare this with the stored hash (which has been encoded twice) ? – joedotnot Jun 04 '09 at 09:48
  • Very true, sorry, just my usage of this method in the past required me to get the byte content back out - edited appropriately. – Alistair Evans Jun 04 '09 at 10:21
  • 2
    hex(string) takes only slightly more (12.5%) space than base64(base64(string)), is less confusing, is not case-sensitive, and does not contain any characters which would need to be escaped in URLs. – yfeldblum Jun 05 '09 at 00:33
4

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.

Graeme Perrow
  • 56,086
  • 21
  • 82
  • 121
Talljoe
  • 14,593
  • 4
  • 43
  • 39
  • 1
    Why is the SHA-1 exploit "nothing you need to worry about"? If someone can manufacture a password that hashes to the same thing as my password, that's the same as breaking my password. I would go any lower than SHA-256 for passwords. – Graeme Perrow Jun 05 '09 at 00:18
  • Correction: I *wouldn't* go any lower than SHA-256. – Graeme Perrow Jun 05 '09 at 00:20
  • 1
    As I understand it, the current _theoretical_ exploit is based off of having the existing plaintext; that is -- creating an evil version of the plaintext. Finding a plaintext that hashes to a given value is still very difficult. And if you already have the password, it doesn't do ou much good to create an evil version of the password. – Talljoe Jun 05 '09 at 02:39
3

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.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
  • The idea is generally sound - bytes can easily be stored as bytes. However, this may make manual manipulation somewhat difficult, since it's not generally possible to see binary data in any reasonable format using an editor/IDE/tool. – Sander Jun 04 '09 at 08:47
  • I'm not sure why this answer was voted down. If the hashed password is stored in a database such as MySQL and SQL Server, *both* of them (and many others I'm willing to bet) support fixed length binary fields, and this would be the most efficient way of storing AND comparing the hashes. The question makes no specific mention that the result MUST be stored as a character string. – Jason Musgrove Jun 04 '09 at 08:51
  • @Sander -- what? That's what you have an IDE for in the first place! – Christoffer Jun 04 '09 at 09:03
  • Why would you want to manipulate a hash value? And even if so, you don’t have to show the hash value as binary data to the user. But for storing, the binary data will be the optimum. – Gumbo Jun 04 '09 at 13:45
  • How would you store this binary data? remember the hashed value is an array of bytes, e.g. {1, 2, 3, 4, ..., 48}, where each of the values 1,2,3 can be any number between 0-255. Somehow i'd need to convert it into a string for storage in one field only. And the shorter this string, the more efficient for comparison purposes. – joedotnot Jun 04 '09 at 14:01
  • Each element would represent a byte. I don’t know .NET but there’s probably a function to convert your integer values into characters. Maybe http://msdn.microsoft.com/library/system.byte.tostring.aspx does this. – Gumbo Jun 04 '09 at 14:21
0

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.

Dungeon Hunter
  • 19,827
  • 13
  • 59
  • 82
0

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.

Felipe Pessoto
  • 6,855
  • 10
  • 42
  • 73
0

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.

chmike
  • 20,922
  • 21
  • 83
  • 106
  • 1
    why do you say put the salt in front of the password? why not at the back? would it make a difference? – joedotnot Jun 04 '09 at 14:07
  • 2
    Yes, it would make a slight difference. A hash function processes input bytes per bytes and it is possible to save and restore an intermediate state. If the salt is appended, you don't have to reahash the whole input string to get the hash. You simply start from the intermediate state. When the salt is put in front, the whole input string has to be rehashed which requires much more computation effort for brute force guessing. – chmike Jun 05 '09 at 13:13
0

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.

Marek Szanyi
  • 2,348
  • 2
  • 22
  • 28
  • I'd argue that anything less than SHA-256 is a security risk. – Graeme Perrow Jun 05 '09 at 00:15
  • https://crackstation.net/ http://www.sha1-online.com/ I tested "alphalphaalpha" from SHA-1 all the way to SHA-512 and it reversed them all. That means unsalted passwords are pretty much useless now. – Brenden Oct 22 '13 at 22:43