Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

storing hashed passwords - base64, or hex string, or something else?

Tags:

hash

base64

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.

like image 673
joedotnot Avatar asked Jun 04 '09 08:06

joedotnot


People also ask

How are hashed passwords stored?

Hashing allows passwords to be stored in a format that can't be reversed at any reasonable amount of time or cost for a hacker. Hashing algorithms turn the plaintext password into an output of characters of a fixed length.

Should I use Hex or Base64?

The difference between Base64 and hex is really just how bytes are represented. Hex is another way of saying "Base16". Hex will take two characters for each byte - Base64 takes 4 characters for every 3 bytes, so it's more efficient than hex.

Is Storing hashed passwords safe?

Hashing and encryption both provide ways to keep sensitive data safe. However, in almost all circumstances, passwords should be hashed, NOT encrypted. Hashing is a one-way function (i.e., it is impossible to "decrypt" a hash and obtain the original plaintext value). Hashing is appropriate for password validation.

Is Base64 encoding a hash?

Base64 encoding and hashing (sha1 etc.) are different concepts. They will both transform data into another format. Encoding is reversible , hashing is not.


1 Answers

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)

}

like image 188
Pavel Repin Avatar answered Jan 01 '23 21:01

Pavel Repin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!