Эффективное шифрование пароля - PullRequest
12 голосов
/ 19 мая 2009

Я взглянул на вопрос StackOverflow, "Шифрование паролей / AES уровня базы данных или AES уровня приложения", , и я хотел бы эффективно и действенно хэшировать свои пароли при регистрации (веб-приложение ), а затем сможете проверить их правильность при входе в систему. Я использую VB, но удобно использовать C #.

Я хотел бы использовать класс шифрования Джеффа Этвуда, описанный в ". NET Encryption Simplified" , поскольку это действительно легко понять. У него есть класс хеширования - но я не знаю, как войти в систему и сравнить хэши после их хеширования. Это демонстрация Джеффом его хеш-методов с использованием класса шифрования:

Sub DemoHash()
    Dim d As New Encryption.Data( _
        "{ts '2004-10-09 08:10:04'}The world is beautiful and needs caring by its children")

    Dim hash As New Encryption.Hash(Encryption.Hash.Provider.SHA1)
    Dim hash2 As New Encryption.Hash(Encryption.Hash.Provider.SHA256)
    Dim hash3 As New Encryption.Hash(Encryption.Hash.Provider.SHA384)
    Dim hash4 As New Encryption.Hash(Encryption.Hash.Provider.SHA512)
    Dim hash5 As New Encryption.Hash(Encryption.Hash.Provider.MD5)
    Dim hash6 As New Encryption.Hash(Encryption.Hash.Provider.CRC32)

    hash.Calculate(d)
    hash2.Calculate(d)
    hash3.Calculate(d)
    hash4.Calculate(d)
    hash5.Calculate(d)

    Console.WriteLine("SHA1:   " & hash.Value.Hex)
    Console.WriteLine("SHA256: " & hash2.Value.Hex)
    Console.WriteLine("SHA384: " & hash3.Value.Hex)
    Console.WriteLine("SHA512: " & hash4.Value.Hex)
    Console.WriteLine("MD5:    " & hash5.Value.Hex)
    Console.WriteLine("CRC32:  " & hash6.Calculate(d).Hex)
    Console.WriteLine()

    Dim salt As New Encryption.Data("salty!")
    Console.WriteLine("Salted CRC32:  " & hash6.Calculate(d, salt).Hex)

    Console.WriteLine("Press ENTER to continue...")
    Console.ReadLine()
End Sub

Итак, мои вопросы:

  1. Я могу зашифровать пароль (хотя я не собираюсь его хранить) и хэшировать строку. Если бы у меня был пользователь с именем «barry» с паролем «fishlegs», каков наилучший способ сохранить его и восстановить?

  2. В SQL Server; Является ли двоичный файл или nvarchar лучшим вариантом для хранения хеша?

  3. Как эффективно хранится хеш на основе «Барри» и его пароля? Это шифрование "рыбьих ножек", добавленных к соли?

Криптография сложна!

Спасибо всем, кто может помочь ...

Ответы [ 8 ]

86 голосов
/ 19 мая 2009

Хм, я думаю, вы просто упускаете некоторые основные понятия, связанные с тем, как работает хеширование. Позвольте мне попытаться объяснить вкратце. Я собираюсь начать с простого и подробно изложить свой ответ позже, поэтому, пожалуйста, прочитайте все это, информация в начале не будет безопасной.

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

Давайте возьмем MD5 в качестве примера функции хеширования. Если я запускаю MD5 в строке «пароль», я всегда получу результат «5f4dcc3b5aa765d61d8327deb882cf99». Однако, если вы просто дадите кому-то эту строку результата («5f4d ...»), то для них невозможно применить какой-либо математический процесс для «реверсирования» функции и выяснить, что она пришла из «пароля». *

Это означает, что когда пользователь впервые устанавливает свой пароль, вы применяете к нему функцию хеширования и сохраняете результат. Таким образом, вместо хранения «пароля», вы сохраняете «5f4dcc3b5aa765d61d8327deb882cf99». Затем, когда этот пользователь пытается войти в систему, вы берете все, что они ввели в поле пароля в форме входа, и применяете ту же функцию хеширования. Если вы получите тот же результат, что и данные, хранящиеся в базе данных, они должны были ввести тот же пароль, который они первоначально выбрали, даже если вы не знаете, каким на самом деле был этот оригинальный пароль.

Теперь, несмотря на то, что невозможно «обратить» хеш-функцию, тот факт, что один и тот же ввод всегда дает один и тот же вывод, означает, что кто-то может просто создать большую базу данных пар ввода-вывода и использовать ее для эффективного обращения хэши. Это называется "радужным столом". Многие из них доступны в Интернете, поэтому небезопасно использовать простое хеширование, на случай, если ваша база данных будет взломана. То есть, хотя математически невозможно * взять "5f4dcc3b5aa765d61d8327deb882cf99" и выяснить, что он был получен при запуске MD5 по "паролю", это очень легко определить на практике. Все, что вам нужно сделать, это запустить каждое слово в словаре через MD5 и сохранить результаты, и вы можете легко перевернуть простые пароли.

Вот тут и начинается "засоление". Если вы генерируете случайную строку "солт" для каждого пользователя и присоединяете ее к своему паролю, это эффективно разрушает радужные таблицы. Например, предположим, что тот же самый пользователь выше регистрируется со своим паролем как «пароль». Мы генерируем случайную 8-символьную соль для присоединения к паролю перед его хэшированием. Допустим, это "A4BR82QX". Теперь вместо хеширования «пароль» мы хешируем «A4BR82QXpassword». Это дает результат "87a4ba071c8bcb5efe457e6c4e6c4490", поэтому мы сохраняем его в базе данных вместе со строкой соли. Затем, когда этот пользователь пытается войти в систему, вместо того, чтобы напрямую хэшировать и сравнивать пароль, который он ввел в форму входа, мы берем то, что они ввели, снова ставим перед ним «A4BR82QX» и хешируем это. Как и раньше, если он совпадает с сохраненным хешем, мы знаем, что они ввели правильный пароль.

По сути, вы сделали так, чтобы предварительно созданные радужные таблицы были бесполезны для попыток взлома паролей в вашей базе данных. Поскольку соль случайна, и у каждого пользователя свой (в общем) свой, злоумышленнику придется заново генерировать свои радужные таблицы для каждого отдельного пользователя . Это намного сложнее.

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

Итак, следующий шаг - найти медленную хеш-функцию и использовать ее вместо быстрой, такой как MD5. Заставить ваш сайт потратить пару секунд, чтобы проверить логин, это совсем не проблема. Но когда кто-то пытается создать радужные таблицы для взлома пароля, каждая запись занимает несколько секунд, что является абсолютным убийцей. Я написал здесь достаточно, поэтому я просто закончу ссылками на эту статью, в которой много подробностей о выборе хорошей, медленной функции хеширования: Достаточно с радужными таблицами: что нужно знать о безопасности Схемы паролей .

Это был довольно масштабный ответ, если что-то из этого неясно, пожалуйста, дайте мне знать в комментарии, и я отредактирую для уточнения.

7 голосов
/ 19 мая 2009

ОК, так что пара вещей о классе Джеффа в первую очередь. SHA1 и MD5 устарели. CRC32 вообще не подходит для паролей. Во-вторых, вы должны солить каждый хэш, предпочтительно с другим значением соли. Обычно вы выбираете для этого криптографически случайный блок данных, но одним нажатием вы можете использовать имя пользователя. Чтобы засолить, вы префикс или суффикс значения соли где-то в процессе. Я обычно хэширую пароль, хэширую соль, объединяю их, затем снова хэширую. Но вы можете обмениваться вещами, это не имеет большого значения, если вы последовательны.

Так что вместо того, чтобы путать вещи с классом Джеффа, давайте сделаем это классическим способом.

Первое случайное образование соли.

public static byte[] GetRandomSalt()
{
  int minSaltSize = 16;
  int maxSaltSize = 32;

  Random random = new Random();
  int saltSize = random.Next(minSaltSize, maxSaltSize);
  saltBytes = new byte[saltSize];
  RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
  rng.GetNonZeroBytes(saltBytes); 
  return saltBytes;
}

Тогда хеширование

public static byte[] ComputeHash(string plainText)
{
  byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
  HashAlgorithm hash = new SHA256Managed();
  return hash.ComputeHash(plainTextWithSaltBytes);
}

Так что это вычислит хеш SHA256 и вернет его в виде байтового массива.

Если бы вы солили, вы бы сделали что-то вроде следующего

byte[] passwordHash = ComputeHash(password);
byte[] salt = GetRandomSalt();
byte[] saltHash = ComputeHash(salt);

byte[] hashWithSaltBytes = new byte[hashBytes.Length + saltBytes.Length];
for (int i=0; i < hashBytes.Length; i++)
  hashWithSaltBytes[i] = hashBytes[i];
for (int i=0; i < saltBytes.Length; i++)
  hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];

А потом, если вам скучно, попробуйте снова или оставьте как есть.

Чтобы превратить массив байтов в строку, если вы не хотите хранить байты, вы можете использовать

string hashValue = Convert.ToBase64String(hashWithSaltBytes);

Сравнение строк проще, чем байтовое сравнение, когда вам приходится перебирать каждый массив, до вас. Просто помните, что если вы используете случайные соли, их нужно хранить рядом с паролем.

6 голосов
/ 19 мая 2009
  • Возьмите пароль пользователя " Secret! ".
  • Генерирует случайную соль из нескольких байтов " s4L75a1T ".
  • Конкатировать их в " s4L75a1TSecret! ".
  • Рассчитать хеш " b9797a5b683804eb195b6ba2a5e368ae74394cd3 " (это SHA-1 в шестнадцатеричном виде)
  • Сохраните хеш и соль (обе в виде шестнадцатеричной строки, строки Base64 или как вам угодно) в базе данных вместе с именем пользователя и другой пользовательской информацией (в примере соль - это просто простая строка и хеш в кодировке Base64).
    FirstName    LastName    Salt        Hash
    -----------------------------------------------------------------
    John         Doe         s4L75a1T    uXl6W2g4BOsZW2uipeNornQ5TNM=

Если вы хотите проверить пароль пользователя, просто возьмите имя пользователя, найдите соль, сделайте вышеупомянутое снова и посмотрите, совпадают ли вычисленный хэш и тот, что в базе данных. Не существует (известного) способа (легко) восстановить пароль.

4 голосов
/ 19 мая 2009

Не путайте шифрование паролей с их хешированием; с хорошей криптографической хеш-функцией вы не сможете отменить процесс хеширования, чтобы получить исходную строку.

Ответ Чада, приведенный выше, является отличным объяснением понятий.

Этот предмет был сделан до смерти по всему Интернету; не только на переполнении стека; серьезно - простой поиск в Интернете должен найти вам довольно полное руководство.

Спасибо, Джефф, за распространение еще одной пачки дезинформации. Я полагаю, что на следующей неделе мы увидим целый ряд ошибочных вопросов о хешировании, шифровании и т. Д., Не говоря уже о том дерьме, которое возникнет в отношении арифметики с плавающей точкой.

3 голосов
/ 19 мая 2009

Что вы, по сути, хотите сделать, это:

A) При создании учетной записи вы получаете имя пользователя и пароль от пользователя, вы хешируете их вместе со своей солью и сохраняете результирующую строку в своей базе данных, например:

Dim sUserName = "barry"
Dim sPassWord = "fishlegs"
Dim mySalt = "A deliciously salty string! fillED WIth all KindS of Junkk(&^&*(£"
Dim d As New Encryption.Data(mySalt + sUserName + sPassWord)
Dim hash As New Encryption.Hash(Encryption.Hash.Provider.SHA256)
hash.Calculate(d)
Dim sTheSaltedHashedUnPassCombination = hash.Value.Hex;
SavenewPasswordHashToDatabase(sTheSaltedHashedUnPassCombination)

Вы никогда не храните sPassWord.

B) Когда пользователь входит в систему, вы выполняете ту же самую операцию с предоставленными именем пользователя и паролем, затем сравните результирующий хеш с ранее сохраненным значением в базе данных, чтобы вы использовали:

Dim sThePreviouslyCreatedHashFromTheDatabase = GetHashForUserFromDatabase(usernameProvided)
Dim mySalt = "A deliciously salty string! fillED WIth all KindS of Junkk(&^&*(£"
Dim d As New Encryption.Data(mySalt + usernameProvided+ passwordProvided)
Dim hash As New Encryption.Hash(Encryption.Hash.Provider.SHA256)
hash.Calculate(d)
Dim sTheSaltedHashedUnPassCombination = hash.Value.Hex;
if (sThePreviouslyCreatedHashFromTheDatabase.Equals(sTheSaltedHashedUnPassCombination))
    'UN & Password Valid!
else
    'UN & PW Invalid!
end

(простите за любые ошибки, VB не мой язык)

Чтобы ответить на ваши заданные вопросы:

1) Смотри выше. Никогда не храните пароль напрямую, сохраняйте хешированное значение

2) используйте символ (X), количество символов, возвращаемых из хеша, является постоянным, поэтому вы можете задать ему известный размер хранилища.

3) То, что вы эффективно храните, - это пароль пользователя, добавленный с его именем пользователя, а также с константой на вашем сервере. Это будет строка фиксированной длины, и вы не сможете изменить эту строку обратно на отдельное имя пользователя и пароль.

3 голосов
/ 19 мая 2009

Я полагаю, что процесс такой: генерировать случайную соль, поэтому 'fishlegs' + 'r4nd0mS4lt', чтобы получить 'fishlegsr4nd0mS4lt'. Затем вы хешируете это, скажем, с MD5 (хотя вы можете использовать SHA256 для большей безопасности), чтобы получить: 593d5518d759b4860188158ef4c71f28. Храните это и случайно сгенерированную соль. Когда пользователь входит в систему, добавьте случайную соль и затем проверьте, совпадает ли его введенный пароль с солью с хешем в базе данных.

2 голосов
/ 19 мая 2009
  1. Весь смысл хеширования заключается в том, что вы храните хеши, а не пароли в виде открытого текста, и их невозможно восстановить (по крайней мере, только с большим трудом). Хеш-функции предназначены для односторонней передачи, поэтому поиск невозможен - такова их сущность.
  2. nchar[n], вероятно, является наилучшим вариантом, поскольку обычные алгоритмы хеширования дают результаты постоянной длины ( SHA1 выводит 160-битный хеш). Обычной практикой является преобразование хэша в Base64 , чтобы его можно было сохранить в виде текста ASCII.
  3. Соль - это просто метод, который добавляет случайные биты в хеш, чтобы усложнить процесс взлома. Каждый лишний кусочек соли означает, что взломщик перебором должен занимать вдвое больше времени (и использовать вдвое больше памяти).
0 голосов
/ 23 мая 2009

ASP.NET включает основанного на SQL поставщика членства , который позволяет создавать, хранить и проверять зашифрованные или хешированные пароли без необходимости что-либо делать - как говорит Эрик Липперт:

позвольте мне дать вам все мои стандартные предостережения о развертывании ваших собственных криптографических алгоритмов и систем безопасности: не надо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...