Известны ли проблемы с функцией C crypt()?

Я использую crypt() для шифрования паролей для моего проекта. Когда пароль выбирается пользователем, он шифруется следующим образом:

password = crypt(<password chosen>, <user's account name>)

Проблема в том, что пользователь входит в систему, используя свой пароль. Если то, что они вводят, не соответствует их паролю, он должен ввести это, если проверить:

if (strcmp(crypt(<what user types in as password>, <user's account name>), <user's encrypted password>)) {
   //...
}

Это не в одном конкретном сценарии. Допустим, их пароль «asdf». Если они вводят «asdf» с любыми случайными завершающими символами, такими как «asdffffff» или «asdf339sfd», пароль все равно принимается. Кажется, он игнорирует все после «asdf».

Это известная проблема с криптой? Есть ли другой способ шифрования паролей?


person Aelin    schedule 23.09.2019    source источник
comment
Не могли бы вы поделиться минимально воспроизводимым примером? Ваше описание звучит неправильно.   -  person Eugene Sh.    schedule 23.09.2019
comment
Я подозреваю ваш метод input, а не crypt() (который, кстати, работает не так, как вы предполагаете, так что вам лучше прочитать его документация).   -  person Eugene Sh.    schedule 23.09.2019
comment
@ Downvoters: на самом деле здесь достаточно информации для полного ответа, и я пишу его сейчас.   -  person zwol    schedule 23.09.2019
comment
Вы абсолютно уверены, что ваши пароли сокращаются до четырех символов? Может быть, вместо этого было восемь символов? Видите ли, восемь гораздо более объяснимо, чем четыре.   -  person zwol    schedule 23.09.2019
comment
@zwol Ну, это не единственная причина понизить это. Другая причина — не чтение спецификации функции и проверка на наличие ошибок. Третий не отвечает на комментарии.   -  person klutt    schedule 23.09.2019
comment
@klutt Достаточно документации, окружающей crypt и друзей, достаточно старая, достаточно загадочная или просто-неправильная, так что я действительно не виню OP за то, что он запутался. Однако не отвечать на комментарии — это справедливая критика.   -  person zwol    schedule 23.09.2019
comment
@zwol Быть сбитым с толку неточной документацией - это одно. Другое дело не читать. На самом деле нет документации, в которой говорится, что вторым аргументом должно быть имя пользователя. :)   -  person klutt    schedule 23.09.2019
comment
@zwol И, как вы сказали, довольно странно, что это четыре символа, а не восемь.   -  person klutt    schedule 23.09.2019


Ответы (1)


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

$2b$07$fQuDK3TaQP4sw6IX6iVcTw

Часть $2b$07$ сообщает crypt, какой алгоритм хеширования паролей использовать, а следующая за ней строка случайных символов представляет собой соль. Соль должна быть разной для каждого пользователя, но не должна иметь никакой связи с именем учетной записи пользователя. Технически это не обязательно должно быть случайным, но очень важно, чтобы оно было разным для каждого пользователя, и оно должно меняться каждый раз, когда пользователь меняет свой пароль, поэтому лучше всего использовать длинную строку, полученную из криптографического PRNG.

Когда вы аутентифицируете пользователя, который ранее входил в систему, вы используете сохраненный хешированный пароль в качестве строки настройки:

char *new_hash = crypt("password typed in", "stored hash");
if (new_hash && !strcmp(new_hash, "stored hash")) {
    // user has successfully logged in
}

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

(Также обратите внимание на нулевую проверку; некоторые реализации crypt могут дать сбой и сообщить об ошибке, возвращая нулевой указатель.)

Когда вы создаете новую учетную запись или меняете пароль, вам необходимо сгенерировать новую строку настроек. Если у вас есть функция crypt_gensalt, используйте ее:

char *new_setting = crypt_gensalt("$2b$", 0, 0, 0);
if (new_setting) {
    char *new_hash = crypt("user's new password", new_setting);
    // ...
} else {
    // halt and catch fire
}

Если у вас нет crypt_gensalt, к сожалению, вам придется реализовать его самостоятельно. (Что еще хуже, в некоторых Unix-системах есть crypt_gensalt, на документацию которого я ссылался выше, а в других есть другая версия с тем же именем, выполняющая ту же работу, но с другими аргументами. Пора стряхнуть пыль с ваших навыков Autoconfz!)


Теперь вы знаете все это, я могу объяснить, почему

password = crypt("password chosen", "user's account name");

казалось, работает, но обрезает пароль. Имена ваших учетных записей пользователей, вероятно, начинаются как минимум с двух буквенно-цифровых символов, верно? Например, "Ma[ya]" или "zw[ol]"? К сожалению, любые два буквенно-цифровых символа составляют допустимую строку настройки... которая выбирает один из самых старых и наименее безопасных алгоритмов хеширования паролей, известных науке, descrypt. (Это было довольно хорошо, когда его изобрели... в середине 1970-х годов. Сейчас его можно взломать методом грубой силы независимо от пароля.) Одна из многих проблем с этим алгоритмом заключается в том, что он усекает все пароли до восьми символов. asdf и asdfhjkl хешируют разные вещи, а asdfhjkl и asdfhjkl1234 хешируют одно и то же.

Лекарством от этого является использование crypt_gensalt или его эквивалента для выбора современного алгоритма. Все современные алгоритмы принимают сколь угодно длинные парольные фразы.

person zwol    schedule 23.09.2019
comment
Предполагается, что это строка настроек. -- хм, почти уверена, что это должна быть двухсимвольная соль. - person Marco Bonelli; 23.09.2019
comment
@MarcoBonelli: в разных библиотеках/платформах есть разные подпрограммы crypt. И двухсимвольная соль не будет очень сильной. - person Eric Postpischil; 23.09.2019
comment
@MarcoBonelli Если ваш crypt поддерживает только расшифровку, да, но тогда вам нужно обновить его, прежде чем кто-то взломает весь ваш теневой файл :) - person zwol; 23.09.2019
comment
@EricPostpischil хм, конечно, я говорю здесь о man 3 crypt. - person Marco Bonelli; 23.09.2019
comment
@MarcoBonelli Хм, это не должно быть этим man 3 crypt... - person zwol; 23.09.2019
comment
@zwol а, я вижу, это crypt из libcrypt. Благодарю за разъяснение. - person Marco Bonelli; 23.09.2019
comment
@zwol действительно я смотрел на crypt из unistd.h (просто набрал man 3 crypt на своем терминале). - person Marco Bonelli; 23.09.2019
comment
@MarcoBonelli Вы также можете прочитать crypt(5) из того же источника, если вы не сталкивались не-DES-хэши ранее в контексте Unix. Они не новые. - person zwol; 23.09.2019
comment
@zwol да, я знаю, что это довольно старые вещи, у меня просто сложилось впечатление, что ОП говорил о crypt unistd. - person Marco Bonelli; 23.09.2019
comment
Это законное предположение, учитывая, что OP спрашивает о функции C crypt() - person Eugene Sh.; 23.09.2019
comment
Дело в том, что crypt из unistd.h и crypt из libcrypt вполне могут быть одной и той же функцией. Компоновщику все равно, в каком заголовочном файле есть extern char *crypt(const char *, const char *);, и получаете ли вы реализацию от -lcrypt или неявного -lc или где-то еще, вероятно, в настоящее время он поддерживает больше, чем просто DES. - person zwol; 23.09.2019
comment
@zwol Спасибо, что нашли время ответить на мой вопрос. Я смотрю на сильно модифицированный стандартный код многопользовательского подземелья ROM и не писал часть склепа сам - он был написан первоначальными создателями кодовой базы, поэтому я предположил, что он был написан правильно - не думаю! Я обновлю, используя ваши предложения. Спасибо. - person Aelin; 24.09.2019