summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--LICENSING12
-rw-r--r--Makefile.am6
-rw-r--r--crypt-pbkdf1-sha1.c249
-rw-r--r--crypt-port.h2
-rw-r--r--crypt-private.h6
-rw-r--r--crypt.c19
-rw-r--r--test-crypt-pbkdf1-sha1.c95
-rw-r--r--test-gensalt.c23
9 files changed, 387 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore
index 9af74d5..3728add 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/LICENSING b/LICENSING
index 0b89b75..ad5b4b5 100644
--- a/LICENSING
+++ b/LICENSING
@@ -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);
diff --git a/crypt.c b/crypt.c
index 5308225..c6f0f4b 100644
--- a/crypt.c
+++ b/crypt.c
@@ -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 }
};