diff --git a/ChangeLog b/ChangeLog index 6195cfc8..82e1a769 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2007-11-19 Nicolas François + + * libmisc/salt.c: The salt has a random size (between 8 and 16 + bytes). + * lib/getdef.c, etc/login.defs: Add definitions for + SHA_CRYPT_MIN_ROUNDS and SHA_CRYPT_MAX_ROUNDS. + * libmisc/salt.c: Use SHA_CRYPT_MIN_ROUNDS and SHA_CRYPT_MAX_ROUNDS + to add a random number of rounds if needed. + 2007-11-19 Nicolas François * libmisc/salt.c (MAGNUM): Terminate the array with nul (the array diff --git a/etc/login.defs b/etc/login.defs index 04d20aa3..dae98f25 100644 --- a/etc/login.defs +++ b/etc/login.defs @@ -288,6 +288,22 @@ CHFN_RESTRICT rwh # #ENCRYPT_METHOD DES +# +# Only works if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + # # List of groups to add to the user's supplementary group set # when logging in on the console (as determined by the CONSOLE diff --git a/lib/getdef.c b/lib/getdef.c index 93546b9d..7b8a798a 100644 --- a/lib/getdef.c +++ b/lib/getdef.c @@ -104,6 +104,8 @@ static struct itemdef def_table[] = { {"PASS_MIN_LEN", NULL}, {"PORTTIME_CHECKS_ENAB", NULL}, {"QUOTAS_ENAB", NULL}, + {"SHA_CRYPT_MAX_ROUNDS", NULL}, + {"SHA_CRYPT_MIN_ROUNDS", NULL}, {"SU_WHEEL_ONLY", NULL}, {"ULIMIT", NULL}, #endif diff --git a/libmisc/salt.c b/libmisc/salt.c index 9f36ec7a..9fb639d1 100644 --- a/libmisc/salt.c +++ b/libmisc/salt.c @@ -53,19 +53,91 @@ char *l64a(long value) #endif /* !HAVE_L64A */ /* - * Generate 8 base64 ASCII characters of random salt. If MD5_CRYPT_ENAB - * in /etc/login.defs is "yes", the salt string will be prefixed by "$1$" - * (magic) and pw_encrypt() will execute the MD5-based FreeBSD-compatible - * version of crypt() instead of the standard one. + * Add the salt prefix. */ - #define MAGNUM(array,ch) (array)[0]= (array)[2] = '$',\ (array)[1]=(ch),\ (array)[2]='\0' +/* + * Return the salt size. + * The size of the salt string is between 8 and 16 bytes for the SHA crypt + * methods. + */ +static unsigned int SHA_salt_size (void) +{ + return 8 + 8*rand ()/(RAND_MAX+1); +} + +/* ! Arguments evaluated twice ! */ +#define MAX(x,y) ((x) > (y) ? (x) : (y)) +#define MIN(x,y) ((x) < (y) ? (x) : (y)) + +/* Default number of rounds if not explicitly specified. */ +#define ROUNDS_DEFAULT 5000 +/* Minimum number of rounds. */ +#define ROUNDS_MIN 1000 +/* Maximum number of rounds. */ +#define ROUNDS_MAX 999999999 + +/* + * Return a salt prefix specifying the rounds number for the SHA crypt methods. + */ +static char *SHA_salt_rounds (void) +{ + static char *rounds_prefix[18]; + long min_rounds = getdef_long ("SHA_CRYPT_MIN_ROUNDS", -1); + long max_rounds = getdef_long ("SHA_CRYPT_MAX_ROUNDS", -1); + long rounds; + + if (-1 == min_rounds && -1 == max_rounds) + return ""; + + if (-1 == min_rounds) + min_rounds = max_rounds; + + if (-1 == max_rounds) + max_rounds = min_rounds; + + if (min_rounds > max_rounds) + max_rounds = min_rounds; + + rounds = min_rounds + (max_rounds - min_rounds)*rand ()/(RAND_MAX+1); + + /* Sanity checks. The libc should also check this, but this + * protects against a rounds_prefix overflow. */ + if (rounds < ROUNDS_MIN) + rounds = ROUNDS_MIN; + + if (rounds > ROUNDS_MAX) + rounds = ROUNDS_MAX; + + snprintf (rounds_prefix, 18, "rounds=%ld$", rounds); + + /* Sanity checks. That should not be necessary. */ + rounds_prefix[17] = '\0'; + if ('$' != rounds_prefix[16]) + rounds_prefix[17] = '$'; + + return rounds_prefix; +} + +/* + * Generate 8 base64 ASCII characters of random salt. If MD5_CRYPT_ENAB + * in /etc/login.defs is "yes", the salt string will be prefixed by "$1$" + * (magic) and pw_encrypt() will execute the MD5-based FreeBSD-compatible + * version of crypt() instead of the standard one. + * Other methods can be set with ENCRYPT_METHOD + */ char *crypt_make_salt (void) { struct timeval tv; + /* Max result size for the SHA methods: + * +3 $5$ + * +17 rounds=999999999$ + * +16 salt + * +1 \0 + */ static char result[40]; int max_salt_len = 8; char *method; @@ -87,10 +159,12 @@ char *crypt_make_salt (void) max_salt_len = 11; } else if (!strncmp (method, "SHA256", 6)) { MAGNUM(result, '5'); - max_salt_len = 11; /* XXX: should not be fixed */ + strcat(result, SHA_salt_rounds()); + max_salt_len = strlen(result) + SHA_salt_size(); } else if (!strncmp (method, "SHA512", 6)) { MAGNUM(result, '6'); - max_salt_len = 11; /* XXX: should not be fixed */ + strcat(result, SHA_salt_rounds()); + max_salt_len = strlen(result) + SHA_salt_size(); } else if (0 != strncmp (method, "DES", 3)) { fprintf (stderr, _("Invalid ENCRYPT_METHOD value: '%s'.\n" @@ -101,12 +175,14 @@ char *crypt_make_salt (void) } #endif /* ENCRYPTMETHOD_SELECT */ #endif /* USE_PAM */ + /* - * Generate 8 chars of salt, the old crypt() will use only first 2. + * Concatenate a pseudo random salt. */ gettimeofday (&tv, (struct timezone *) 0); - strcat (result, l64a (tv.tv_usec)); - strcat (result, l64a (tv.tv_sec + getpid () + clock ())); + strncat (result, sizeof(result), l64a (tv.tv_usec)); + strncat (result, sizeof(result), + l64a (tv.tv_sec + getpid () + clock ())); if (strlen (result) > max_salt_len) /* magic+salt */ result[max_salt_len] = '\0';