И аргумент salt, и шифруемый пароль состоят из символов, выбранных из 64-символьного набора [a-zA-Z0-9/.]. Таким образом, аргумент salt («соль»), состоящий из двух символов, может стать причиной изменения алгоритма шифрования любым из 64 × 64 = 4096 возможных способов. Это означает, что вместо предварительного шифрования целого словаря и проверки зашифрованного пароля на совпадение со всеми словами в словаре взломщику придется проверять пароль на соответствие 4096 зашифрованным версиям словарей.
Зашифрованный пароль, возвращенный функцией crypt(), содержит в двух первых символах копию исходного значения «соли». Это означает, что при шифровании потенциально подходящего пароля можно получить соответствующее значение «соли» из значения зашифрованного пароля, уже хранящегося в файле /etc/shadow. (Такие программы, как passwd(1), при шифровании нового пароля создают произвольное значение «соли».) Фактически функция crypt() игнорирует любые символы в строке «соли», кроме первых двух. Поэтому можно указать в качестве аргумента salt сам зашифрованный пароль.
Если нужно воспользоваться функцией crypt() в Linux, следует откомпилировать программы с ключом — lcrypt, чтобы они были скомпонованы с библиотекой crypt.
Пример программы
В листинге 8.2 показано, как функция crypt() применяется для аутентификации пользователя. Программа в этом листинге сначала считывает имя пользователя, а затем извлекает соответствующую парольную запись и (если таковая существует) теневую запись в файле паролей. Если парольная запись не будет найдена или же если у программы нет полномочий на чтение из теневого файла паролей (для этого требуются полномочия привилегированного пользователя или принадлежность к группе shadow), то программа выводит на экран сообщение об ошибке, а затем осуществляет выход. Затем программа считывает пароль пользователя с помощью функции getpass().
#define _BSD_SOURCE
#include <unistd.h>
char *getpass(const char *prompt);
Возвращает при успешном завершении указатель на статически размещаемую строку ввода пароля или NULL при ошибке
Функция getpass() сначала отключает отображение на экране и всю обработку специальных символов управления терминалом (таких как символ прерывания, обычно это Ctrl+C). (Способы изменения этих настроек терминала рассматриваются в главе 58.) Затем на экран выводится строка с приглашением на ввод и считывается введенная строка, а в качестве результата выполнения функции возвращается строка ввода с завершающим нулевым байтом и удаленным следующим за ней символом новой строки. (Эта строка размещается статически и поэтому будет перезаписана при следующем вызове getpass().) Перед возвращением getpass() восстанавливает настройки терминала до их исходного состояния.
Прочитав пароль с помощью функции getpass(), программа из листинга 8.2 проверяет его. При этом функция crypt() используется для его шифрования и проверки того, что получившаяся строка в точности совпадает зашифрованному паролю, записанному в теневом файле паролей. Если пароль совпадает, идентификатор пользователя выводится на экран, как в следующем примере:
$ su Для чтения теневого файла паролей нужны привилегии
Password:
# ./check_password
Username: mtk
Password: Набирается пароль, который не отображается на экране
Successfully authenticated: UID=1000
Программа в листинге 8.2 определяет размер массива символов, содержащего имя пользователя. Для этого применяется значение, возвращенное выражением sysconf(_SC_LOGIN_NAME_MAX), которое выдает максимальный размер имени пользователя в главной системе. Использование sysconf() объясняется в разделе 11.2.
Листинг 8.2. Аутентификация пользователя с применением теневого файла паролей
users_groups/check_password.c
#define _BSD_SOURCE /* Получение объявления getpass() из <unistd.h> */
#define _XOPEN_SOURCE /* Получение объявления crypt() из <unistd.h> */
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <shadow.h>
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
char *username, *password, *encrypted, *p;
struct passwd *pwd;
struct spwd *spwd;
Boolean authOk;
size_t len;
long lnmax;
lnmax = sysconf(_SC_LOGIN_NAME_MAX);
if (lnmax == -1) /* Если предел не определен, */