Как правильно хранить пароль локально

Я читал эту статью из MSDN по Rfc2898DeriveBytes. Вот пример кода шифрования, который они предоставляют.

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();

Мой вопрос в том, как правильно читать/записывать хешированные данные в/из текстового файла?

Моя главная цель - сделать то, что делает этот разработчик. Мне нужно сохранить пароль локально. Когда мое приложение запрашивает у пользователя пароль, пользователь вводит пароль, затем мое приложение будет читать из текстового файла и проверять, действительно ли введенный пользователем пароль. Как мне это сделать?

Ответ 1

Обычно вы храните хеш пароля, а затем, когда пользователь вводит пароль, вы вычисляете хеш по введенному паролю и сравниваете его с хешем, который был сохранен. Тем не менее, просто хэширования обычно недостаточно (с точки зрения безопасности), и вместо этого вы должны использовать функцию, например PKBDF2 (Password Defence Function Function 2). Вот статья, охватывающая всю эту информацию более подробно, а также пример кода (внизу страницы): http://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right

Вот ссылка на codereview, который, как я полагаю, относится к той же самой реализации, что и в предыдущей статье.

Ответ 2

Как правильно хранить пароль локально

Просто не делай этого. На самом деле этого не делать.

... Но если вам действительно нужно, никогда не используйте его сами. Я бы рекомендовал рассмотреть, как ASP.NET Identity хеширует пароли. Версия 3 в настоящий момент довольно твердая:

обратите внимание, что из github.com взято следующее и может быть изменено в любое время. Для получения последней информации обратитесь к предыдущей ссылке.

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;
    }

Ответ 3

Вы должны сохранить пароль как односторонний хэш и соль, используемую для создания этого пароля. Таким образом, вы абсолютно уверены, что пароль для пользователя никогда не может быть ПРОВЕРЕН. Никогда не используйте двустороннее шифрование для этой конкретной задачи, так как вы рискуете подвергнуть пользователя информации потенциальным злоумышленникам.

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);
}

Вы также можете выяснить другой метод получения соли, но я сделал свой вывод, что он вычисляет случайные данные на 2048 бит. Вы могли бы просто использовать случайное время, которое вы создаете, но это было бы намного менее безопасным. Вы не сможете использовать SecureString, потому что SecureString не является Serializable. Какой смысл в DPAPI. Есть способы получить данные, но в итоге вам придется преодолеть несколько препятствий, чтобы сделать это.

FWIW, PBKDF2 (функция деривации пароля на основе пароля 2) - это в основном то же самое, что и SHA256, кроме более медленного (хорошая вещь). Само по себе оба очень безопасны. Если вы комбинировали PBKDF2 с SHA256 в качестве соли, у вас была бы очень безопасная система.