Emma Geller-Green
Emma Geller-Green

Reputation: 307

How to properly store password locally

I've been reading this article from MSDN on Rfc2898DeriveBytes. Here is the sample encryption code they provide.

string pwd1 = passwordargs[0];
// Create a byte array to hold the random value. 
byte[] salt1 = new byte[8];
using (RNGCryptoServiceProvider rngCsp = ne RNGCryptoServiceProvider())
{
    // Fill the array with a random value.
    rngCsp.GetBytes(salt1);
}

//data1 can be a string or contents of a file.
string data1 = "Some test data";
//The default iteration count is 1000 so the two methods use the same iteration count.
int myIterations = 1000;
try
{
    Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(pwd1,salt1,myIterations);
    Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(pwd1, salt1);
    // Encrypt the data.
    TripleDES encAlg = TripleDES.Create();
    encAlg.Key = k1.GetBytes(16);
    MemoryStream encryptionStream = new MemoryStream();
    CryptoStream encrypt = newCryptoStream(encryptionStream, encAlg.CreateEncryptor(), CryptoStreamMode.Write);
    byte[] utfD1 = new System.Text.UTF8Encoding(false).GetBytes(data1);

    encrypt.Write(utfD1, 0, utfD1.Length);
    encrypt.FlushFinalBlock();
    encrypt.Close();
    byte[] edata1 = encryptionStream.ToArray();
    k1.Reset();

My question is, how would I properly Read/Write the hashed data to/from a text file?

My main goal is to do what this developer is doing. I need to store a password locally. When my application prompts the user for the password, the user will enter the password, then my application will read from the text file and verify if the password that the user entered is indeed correct. How would I go about doing it?

Upvotes: 13

Views: 5371

Answers (3)

Giorgi Moniava
Giorgi Moniava

Reputation: 28654

You typically store the hash of the password, then when user enters password, you compute hash over the entered password and compare it with the hash which was stored - that said, just hashing is usually not enough (from security point of view) and you should use a function such as PKBDF2 (Password-Based Key Derivation Function 2) instead. Here is article covering all that information in more elaborate way as well as sample code (bottom of the page): http://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right

Here is a link to codereview, which I guess refers to the same implementation as above article.

Upvotes: 5

Mr. Young
Mr. Young

Reputation: 2424

You should store the password as a one-way hash and the salt used to create that password. This way you are absolutely sure that the password for the user can never be DECRYPTED. Never use any two-way encryption for this particular task, as you risk exposing user information to would-be attackers.

void Main()
{
    string phrase, salt, result;
    phrase = "test";
    result = Sha256Hash(phrase, out salt);

    Sha256Compare(phrase, result, salt);
}

public string Sha256Hash(string phrase, out string salt)
{
    salt = Create256BitSalt();
    string saltAndPwd = String.Concat(phrase, salt);
    Encoding encoder = Encoding.Default;
    SHA256Managed sha256hasher = new SHA256Managed();
    byte[] hashedDataBytes = sha256hasher.ComputeHash(encoder.GetBytes(saltAndPwd));
    string hashedPwd = Encoding.Default.GetString(hashedDataBytes);
    return hashedPwd;
}

public bool Sha256Compare(string phrase, string hash, string salt)
{
    string saltAndPwd = String.Concat(phrase, salt);
    Encoding encoder = Encoding.Default;
    SHA256Managed sha256hasher = new SHA256Managed();
    byte[] hashedDataBytes = sha256hasher.ComputeHash(encoder.GetBytes(saltAndPwd));
    string hashedPwd = Encoding.Default.GetString(hashedDataBytes);
    return string.Compare(hash, hashedPwd, false) == 0;
}

public string Create256BitSalt()
{
    int _saltSize = 32;
    byte[] ba = new byte[_saltSize];
    RNGCryptoServiceProvider.Create().GetBytes(ba);
    return Encoding.Default.GetString(ba);
}

You could also figure out another method for obtaining the salt, but I have made mine to that it computes 2048 bits worth of random data. You could just use a random long you generate but that would be a lot less secure. You won't be able to use SecureString because SecureString isn't Serializable. Which the whole point of DPAPI. There are ways to get the data out but you end up having to jump a few hurdles to do it.

FWIW, PBKDF2 (Password-Based Key Derivation Function 2) is basically the same thing as SHA256 except slower (a good thing). On its own both are very secure. If you combined PBKDF2 with an SHA256 as your salt then you'd have a very secure system.

Upvotes: 3

Erik Philips
Erik Philips

Reputation: 54618

How to properly store password locally

Just don't do it. No really don't do it.

...But if you really really have to, never just implement it yourself. I would recommend reviewing how ASP.NET Identity hashes passwords. Version 3 is pretty rock solid at the moment:

note that the following is taken from github.com and may be changed at any time. For the latest, please refer to the previous link.

private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested)
    {
        // Produce a version 3 (see comment above) text hash.
        byte[] salt = new byte[saltSize];
        rng.GetBytes(salt);
        byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

        var outputBytes = new byte[13 + salt.Length + subkey.Length];
        outputBytes[0] = 0x01; // format marker
        WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
        WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);
        WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);
        Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
        Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
        return outputBytes;
    }

Upvotes: 5

Related Questions