diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | LICENSING | 12 | ||||
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | crypt-pbkdf1-sha1.c | 249 | ||||
-rw-r--r-- | crypt-port.h | 2 | ||||
-rw-r--r-- | crypt-private.h | 6 | ||||
-rw-r--r-- | crypt.c | 19 | ||||
-rw-r--r-- | test-crypt-pbkdf1-sha1.c | 95 | ||||
-rw-r--r-- | test-gensalt.c | 23 |
9 files changed, 387 insertions, 26 deletions
@@ -62,6 +62,7 @@ /test-crypt-md5 /test-crypt-nonnull /test-crypt-nthash +/test-crypt-pbkdf1-sha1 /test-crypt-sha256 /test-crypt-sha512 /test-crypt-sunmd5 @@ -33,15 +33,18 @@ source tree. For specific licensing terms consult the files themselves. * Public domain, written by Steve Reid et al.: alg-sha1.c, alg-sha1.h, test-alg-sha1.c + * Copyright Juniper Networks, Inc.; 3-clause BSD: + crypt-pbkdf1-sha1.c, crypt-pbkdf1-sha1.c + + * Copyright Björn Esser; 2-clause BSD: + alg-hmac-sha1.c, alg-hmac-sha1.h, test-alg-hmac-sha1.c + * Public domain, written by Zack Weinberg et al.: byteorder.h, randombytes.c, test-byteorder.c gen-crypt-h.awk, gen-map.awk, gen-vers.awk test-symbols-compat, test-symbols-renames, test-symbols-static m4/zw_alignment.m4, m4/zw_static_assert.m4, m4/skip-if-exec-format-error - * Copyright Björn Esser; 2-clause BSD: - alg-hmac-sha1.c, alg-hmac-sha1.h, test-alg-hmac-sha1.c - * Copyright Zack Weinberg and Free Software Foundation, Inc; GPL (v3 or later), with Autoconf exception: m4/zw_automodern.m4, m4/zw_simple_warnings.m4 @@ -57,7 +60,8 @@ source tree. For specific licensing terms consult the files themselves. test-crypt-md5.c, test-crypt-sha256.c, test-crypt-sha512.c, test-des-cases.h, test-des-obsolete{,_r}.c, test-gensalt.c, test-crypt-nthash.c (adaption of test-crypt-des.c), - test-crypt-sunmd5.c (adaption of test-crypt-des.c) + test-crypt-sunmd5.c (adaption of test-crypt-des.c), + test-crypt-pbkdf1-sha1.c (adaption of test-crypt-des.c) * The NEWS file formerly contained the following copyright assertions: diff --git a/Makefile.am b/Makefile.am index 1973a6b..4bd9fbb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -113,7 +113,8 @@ check_PROGRAMS = \ if ENABLE_WEAK_HASHES libcrypt_la_SOURCES += \ - crypt-md5.c crypt-des.c crypt-nthash.c crypt-sunmd5.c \ + crypt-md5.c crypt-des.c crypt-nthash.c crypt-pbkdf1-sha1.c \ + crypt-sunmd5.c \ alg-des.c alg-hmac-sha1.c alg-md4.c alg-md5.c alg-sha1.c nodist_libcrypt_la_SOURCES = \ @@ -144,7 +145,7 @@ check_PROGRAMS += \ test-alg-des test-alg-hmac-sha1 test-alg-md4 \ test-alg-md5 test-alg-sha1 \ test-crypt-badsalt test-crypt-des test-crypt-md5 \ - test-crypt-nthash test-crypt-sunmd5 + test-crypt-nthash test-crypt-sunmd5 test-crypt-pbkdf1-sha1 endif if ENABLE_OBSOLETE_API @@ -182,6 +183,7 @@ test_crypt_des_LDADD = libcrypt.la test_crypt_md5_LDADD = libcrypt.la test_crypt_nonnull_LDADD = libcrypt.la test_crypt_nthash_LDADD = libcrypt.la +test_crypt_pbkdf1_sha1_LDADD = libcrypt.la test_crypt_sha256_LDADD = libcrypt.la test_crypt_sha512_LDADD = libcrypt.la test_crypt_sunmd5_LDADD = libcrypt.la diff --git a/crypt-pbkdf1-sha1.c b/crypt-pbkdf1-sha1.c new file mode 100644 index 0000000..d2754cb --- /dev/null +++ b/crypt-pbkdf1-sha1.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2004, Juniper Networks, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "crypt-port.h" +#include "crypt-private.h" +#include "alg-hmac-sha1.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * The default iterations - should take >0s on a fast CPU + * but not be insane for a slow CPU. + */ +#ifndef CRYPT_SHA1_ITERATIONS +# define CRYPT_SHA1_ITERATIONS 262144 +#endif +/* + * Support a reasonably? long salt. + */ +#ifndef CRYPT_SHA1_SALT_LENGTH +# define CRYPT_SHA1_SALT_LENGTH 64 +#endif + +#define SHA1_SIZE 20 + +static const uint8_t itoa64[] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static inline void +to64 (uint8_t *s, long unsigned v, int n) +{ + while (--n >= 0) + { + *s++ = itoa64[v & 0x3f]; + v >>= 6; + } +} + +/* + * This may be called from crypt_sha1 or gensalt. + * + * The value returned will be slightly less than <hint> which defaults + * to 24680. The goals are that the number of iterations should take + * non-zero amount of time on a fast cpu while not taking insanely + * long on a slow cpu. The current default will take about 5 seconds + * on a 100MHz sparc, and about 0.04 seconds on a 3GHz i386. + * The number is varied to frustrate those attempting to generate a + * dictionary of pre-computed hashes. + */ +static unsigned long +crypt_sha1_iterations (unsigned long hint) +{ + unsigned long random; + + /* + * We treat CRYPT_SHA1_ITERATIONS as a hint. + * Make it harder for someone to pre-compute hashes for a + * dictionary attack by not using the same iteration count for + * every entry. + */ + get_random_bytes (&random, sizeof (unsigned long)); + if (hint == 0) + hint = CRYPT_SHA1_ITERATIONS; + return hint - (random % (hint / 4)); +} + +/* + * UNIX password using hmac_sha1 + * This is PBKDF1 from RFC 2898, but using hmac_sha1. + * + * The format of the encrypted password is: + * $<tag>$<iterations>$<salt>$<digest> + * + * where: + * <tag> is "sha1" + * <iterations> is an unsigned int identifying how many rounds + * have been applied to <digest>. The number + * should vary slightly for each password to make + * it harder to generate a dictionary of + * pre-computed hashes. See crypt_sha1_iterations. + * <salt> up to 64 bytes of random data, 8 bytes is + * currently considered more than enough. + * <digest> the hashed password. + * + * NOTE: + * To be FIPS 140 compliant, the password which is used as a hmac key, + * should be between 10 and 20 characters to provide at least 80bits + * strength, and avoid the need to hash it before using as the + * hmac key. + */ +void +crypt_sha1_rn (const char *phrase, const char *setting, + uint8_t *output, size_t o_size, + void *scratch, size_t s_size) +{ + static const char *magic = "$sha1$"; + + if ((o_size < (strlen (magic) + 2 + 10 + CRYPT_SHA1_SALT_LENGTH + SHA1_SIZE)) || + s_size < SHA1_SIZE) + { + errno = ERANGE; + return; + } + + const char *sp; + uint8_t *ep; + unsigned long ul; + size_t sl; + size_t pl; + char *salt; + int dl; + unsigned long iterations; + unsigned long i; + /* XXX silence -Wpointer-sign (would be nice to fix this some other way) */ + const uint8_t *pwu = (const uint8_t *)phrase; + uint8_t *hmac_buf = scratch; + + /* + * Salt format is + * $<tag>$<iterations>$salt[$] + * If it does not start with $ we use our default iterations. + */ + + /* If it starts with the magic string, then skip that */ + if (!strncmp (setting, magic, strlen (magic))) + { + setting += strlen (magic); + /* and get the iteration count */ + iterations = (unsigned long)strtoul (setting, (char **)&ep, 10); + if (*ep != '$') + { + errno = EINVAL; + return; /* invalid input */ + } + setting = (char *)ep + 1; /* skip over the '$' */ + } + else + { + iterations = (unsigned long)crypt_sha1_iterations (0); + } + + /* It stops at the next '$', max CRYPT_SHA1_ITERATIONS chars */ + for (sp = setting; *sp && *sp != '$' && sp < (setting + CRYPT_SHA1_ITERATIONS); sp++) + continue; + + /* Get the length of the actual salt */ + sl = (size_t)(sp - setting); + + salt = malloc (sl + 1); + strncpy (salt, setting, sl); + salt[sl] = '\0'; + + pl = strlen (phrase); + + /* + * Now get to work... + * Prime the pump with <salt><magic><iterations> + */ + dl = snprintf ((char *)output, o_size, "%s%s%lu", + salt, magic, iterations); + /* + * Then hmac using <phrase> as key, and repeat... + */ + hmac_sha1_process_data ((const unsigned char *)output, (size_t)dl, pwu, pl, hmac_buf); + for (i = 1; i < iterations; ++i) + { + hmac_sha1_process_data (hmac_buf, SHA1_SIZE, pwu, pl, hmac_buf); + } + /* Now output... */ + pl = (size_t)snprintf ((char *)output, o_size, "%s%lu$%s$", + magic, iterations, salt); + ep = output + pl; + + /* Every 3 bytes of hash gives 24 bits which is 4 base64 chars */ + for (i = 0; i < SHA1_SIZE - 3; i += 3) + { + ul = (unsigned long)((hmac_buf[i+0] << 16) | + (hmac_buf[i+1] << 8) | + hmac_buf[i+2]); + to64 (ep, ul, 4); + ep += 4; + } + /* Only 2 bytes left, so we pad with byte0 */ + ul = (unsigned long)((hmac_buf[SHA1_SIZE - 2] << 16) | + (hmac_buf[SHA1_SIZE - 1] << 8) | + hmac_buf[0]); + to64 (ep, ul, 4); + ep += 4; + *ep = '\0'; + + /* Don't leave anything around in vm they could use. */ + memset (scratch, 0, s_size); +} + +/* Modified excerpt from: + http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/lib/libcrypt/pw_gensalt.c */ +void +gensalt_sha1_rn (unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t o_size) +{ + /* The salt can be up to 64 bytes, but 32 + is considered enough for now. */ + const uint8_t saltlen = 16; + + if ((o_size < (size_t)(6 + CRYPT_SHA1_SALT_LENGTH + 2)) || + (nrbytes < saltlen)) + { + errno = ERANGE; + return; + } + + int n = snprintf((char *)output, o_size, "$sha1$%u$", + (unsigned int)crypt_sha1_iterations(count)); + + for (uint32_t c = 0; c < saltlen; ++c) + to64 (output + n + c, (unsigned long)*(rbytes + c), 1); + output[n + saltlen] = '$'; + output[n + saltlen + 1] = '\0'; +} diff --git a/crypt-port.h b/crypt-port.h index 4c40d74..1c77d48 100644 --- a/crypt-port.h +++ b/crypt-port.h @@ -191,6 +191,7 @@ typedef union #define crypt_des_xbsd_rn _crypt_crypt_des_xbsd_rn #define crypt_md5_rn _crypt_crypt_md5_rn #define crypt_nthash_rn _crypt_crypt_nthash_rn +#define crypt_sha1_rn _crypt_crypt_sha1_rn #define crypt_sunmd5_rn _crypt_crypt_sunmd5_rn #define des_crypt_block _crypt_des_crypt_block #define des_set_key _crypt_des_set_key @@ -201,6 +202,7 @@ typedef union #define gensalt_des_xbsd_rn _crypt_gensalt_des_xbsd_rn #define gensalt_md5_rn _crypt_gensalt_md5_rn #define gensalt_nthash_rn _crypt_gensalt_nthash_rn +#define gensalt_sha1_rn _crypt_gensalt_sha1_rn #define gensalt_sunmd5_rn _crypt_gensalt_sunmd5_rn #define ip_maskl _crypt_ip_maskl #define ip_maskr _crypt_ip_maskr diff --git a/crypt-private.h b/crypt-private.h index 60d72ab..80a916d 100644 --- a/crypt-private.h +++ b/crypt-private.h @@ -51,6 +51,9 @@ extern void crypt_md5_rn (const char *phrase, const char *setting, extern void crypt_nthash_rn (const char *phrase, const char *setting, uint8_t *output, size_t o_size, void *scratch, size_t s_size); +extern void crypt_sha1_rn (const char *phrase, const char *setting, + uint8_t *output, size_t o_size, + void *scratch, size_t s_size); extern void crypt_sunmd5_rn (const char *phrase, const char *setting, uint8_t *output, size_t o_size, void *scratch, size_t s_size); @@ -79,6 +82,9 @@ extern void gensalt_md5_rn (unsigned long count, extern void gensalt_nthash_rn (unsigned long count, const uint8_t *rbytes, size_t nrbytes, uint8_t *output, size_t o_size); +extern void gensalt_sha1_rn (unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t o_size); extern void gensalt_sunmd5_rn (unsigned long count, const uint8_t *rbytes, size_t nrbytes, uint8_t *output, size_t o_size); @@ -80,19 +80,20 @@ struct hashfn static const struct hashfn tagged_hashes[] = { /* bcrypt */ - { "$2a$", crypt_bcrypt_rn, gensalt_bcrypt_a_rn }, - { "$2b$", crypt_bcrypt_rn, gensalt_bcrypt_b_rn }, - { "$2x$", crypt_bcrypt_rn, gensalt_bcrypt_x_rn }, - { "$2y$", crypt_bcrypt_rn, gensalt_bcrypt_y_rn }, + { "$2a$", crypt_bcrypt_rn, gensalt_bcrypt_a_rn }, + { "$2b$", crypt_bcrypt_rn, gensalt_bcrypt_b_rn }, + { "$2x$", crypt_bcrypt_rn, gensalt_bcrypt_x_rn }, + { "$2y$", crypt_bcrypt_rn, gensalt_bcrypt_y_rn }, /* legacy hashes */ #if ENABLE_WEAK_HASHES - { "$1$", crypt_md5_rn, gensalt_md5_rn }, - { "$3$", crypt_nthash_rn, gensalt_nthash_rn }, - { "$md5", crypt_sunmd5_rn, gensalt_sunmd5_rn }, + { "$1$", crypt_md5_rn, gensalt_md5_rn }, + { "$3$", crypt_nthash_rn, gensalt_nthash_rn }, + { "$md5", crypt_sunmd5_rn, gensalt_sunmd5_rn }, + { "$sha1", crypt_sha1_rn, gensalt_sha1_rn }, #endif - { "$5$", crypt_sha256_rn, gensalt_sha256_rn }, - { "$6$", crypt_sha512_rn, gensalt_sha512_rn }, + { "$5$", crypt_sha256_rn, gensalt_sha256_rn }, + { "$6$", crypt_sha512_rn, gensalt_sha512_rn }, { 0, 0, 0 } }; diff --git a/test-crypt-pbkdf1-sha1.c b/test-crypt-pbkdf1-sha1.c new file mode 100644 index 0000000..8893e68 --- /dev/null +++ b/test-crypt-pbkdf1-sha1.c @@ -0,0 +1,95 @@ +#include "crypt-port.h" +#include "crypt-base.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +const char *password = "zyxwvuts"; +const char *tests[][2] = +{ + /* Hashes have been computed with the following program: + + #!/usr/bin/python + + from passlib.hash import sha1_crypt + import csv + + sha1crypt = sha1_crypt.using(rounds=1) + + with open('sha1crypt.txt') as csvfile: + params = csv.reader(csvfile) + for row in params: + print(sha1crypt.using(rounds=int(row[0]), + salt=row[1]).hash("zyxwvuts")) + + The used csv file had the following format: + + <rounds>,<salt(16byte)> (when no rounds parameter was given, + rounds were set to zero.) + + The salts have been generated by crypt_gensalt on OpenSolaris. + + + Test X.0: checks the password is encryptable with the full hash + as setting. + Test X.1: checks the password is encryptable with the initially + used salt as setting. + */ + { + "$sha1$40295$GGXpNqoJvglVTkGU$vhHjbglrEZcuASMnDHWQVamZx8j1", + "$sha1$40295$GGXpNqoJvglVTkGU$", + }, + { + "$sha1$46858$32Tr4aJd.GGC5U0V$qyEzLlMXHsGvDh5X/4jugldGxTrx", + "$sha1$46858$32Tr4aJd.GGC5U0V$", + }, + { + "$sha1$37586$xSZGpk6Bp4SA3.cR$UtT4FhiuYrfnL0fa4g/if5VON6qG", + "$sha1$37586$xSZGpk6Bp4SA3.cR$", + }, + { + "$sha1$43007$OZZMNHpZiuBanplt$qHVrN.7fz85cYgPxy9StyT/DSXth", + "$sha1$43007$OZZMNHpZiuBanplt$", + }, + { + "$sha1$37393$fQExw2gulRPUbljr$vAq9CV8JR9ux4.G/k9uQU.iyjQqQ", + "$sha1$37393$fQExw2gulRPUbljr$", + }, +}; + +#define ntests (sizeof (tests) / sizeof (tests[0])) + +int +main (void) +{ + struct crypt_data output; + int result = 0; + unsigned int i, j; + char *previous = malloc (sizeof (output.output) + 1); + + for (i = 0; i < ntests; ++i) + { + for (j = 0; j < 2; ++j) + { + char *cp = crypt_r (password, tests[i][j], &output); + if ((j == 0) && (strcmp (cp, tests[i][j]) != 0)) + { + printf ("test %u.%d: expected \"%s\", got \"%s\"\n", + i, errno, tests[i][j], cp); + result = 1; + } + if ((j == 1) && (strcmp (cp, previous) != 0)) + { + printf ("test %u.%u: expected \"%s\", got \"%s\"\n", + i, j, previous, cp); + result = 1; + } + strcpy (previous, tests[i][j]); + } + } + + free (previous); + return result; +} diff --git a/test-gensalt.c b/test-gensalt.c index 2a35d74..b1a06a0 100644 --- a/test-gensalt.c +++ b/test-gensalt.c @@ -24,18 +24,19 @@ struct testcase static const struct testcase testcases[] = { #if ENABLE_WEAK_HASHES - { "", 2 }, // DES - { "_", 9 }, // BSDi extended DES - { "$1$", 11 }, // MD5 - { "$3$", 29 }, // NTHASH - { "$md5", 27 }, // SUNMD5 + { "", 2 }, // DES + { "_", 9 }, // BSDi extended DES + { "$1$", 11 }, // MD5 + { "$3$", 29 }, // NTHASH + { "$md5", 27 }, // SUNMD5 + { "$sha1", 30 }, // PBKDF with SHA1 #endif - { "$5$", 19 }, // SHA-2-256 - { "$6$", 19 }, // SHA-2-512 - { "$2a$", 29 }, // bcrypt mode A - { "$2b$", 29 }, // bcrypt mode B - { "$2x$", 29 }, // bcrypt mode X - { "$2y$", 29 }, // bcrypt mode Y + { "$5$", 19 }, // SHA-2-256 + { "$6$", 19 }, // SHA-2-512 + { "$2a$", 29 }, // bcrypt mode A + { "$2b$", 29 }, // bcrypt mode B + { "$2x$", 29 }, // bcrypt mode X + { "$2y$", 29 }, // bcrypt mode Y { 0, 0 } }; |