Crypt.Password: Support bcrypt hashing.

parent 7b7e515b
......@@ -197,6 +197,7 @@ int verify(string(8bit) password, string(7bit) hash)
return 0;
}
string(7bit) fullhash = hash;
// Then try our implementations.
if (!sscanf(hash, "$%s$%s", scheme, hash)) return 0;
sscanf(hash, "%s$%s", string(7bit) salt, hash);
......@@ -205,12 +206,14 @@ int verify(string(8bit) password, string(7bit) hash)
case "1": // crypt_md5
return Nettle.crypt_md5(passwd, salt) == hash;
#if constant(Nettle.bcrypt_hash)
case "2": // Blowfish (obsolete)
case "2a": // Blowfish (possibly weak)
case "2b": // Blowfish (long password bug fixed)
case "2x": // Blowfish (weak)
case "2y": // Blowfish (stronger)
break;
return Nettle.bcrypt_verify(passwd, fullhash);
#endif
case "nt":
case "3": // MD4 NT LANMANAGER (FreeBSD)
......@@ -315,6 +318,19 @@ int verify(string(8bit) password, string(7bit) hash)
//! @value "NT"
//! The NTLM MD4 hash.
//!
//! @value "2"
//! @value "2a"
//! @value "2b"
//! @value "2x"
//! @value "2y"
//! @value "$2$"
//! @value "$2a$"
//! @value "$2b$"
//! @value "$2x$"
//! @value "$2y$"
//! @[Nettle.bcrypt()] with 128 bits of salt and a default
//! of @expr{10@} rounds.
//!
//! @value "1"
//! @value "$1$"
//! @[MD5.crypt_hash()] with 48 bits of salt and @expr{1000@} rounds.
......@@ -426,16 +442,8 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
string(7bit) hash, int rounds)
{
if (!has_value(scheme, '$')) scheme = "$" + scheme + "$";
int exp2 = 0;
if (rounds < (1<<7)) exp2 = 7;
else if (rounds > (1<<30)) exp2 = 30;
else {
if (rounds & (rounds - 1)) exp2 = 1;
while (rounds) {
rounds >>= 1;
exp2++;
}
}
int(0..) exp2 = 7;
while (1 << exp2 < rounds && ++exp2 < 30);
return sprintf("%s%c%s%s",
scheme, "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[exp2],
salt, hash);
......@@ -451,25 +459,46 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
function(string(7bit), string(7bit), string(7bit), int(0..):string(7bit))
render_hash = render_crypt_hash;
switch(lower_case(scheme)) {
string schemeprefix = scheme;
if (schemeprefix && schemeprefix[0] == '$')
sscanf(schemeprefix, "$%s$", schemeprefix);
switch(lower_case(schemeprefix)) {
case "crypt":
case "{crypt}":
case UNDEFINED:
// FALL_THROUGH
#if constant(Crypto.SHA512)
case "6":
case "$6$":
crypt_hash = Crypto.SHA512.crypt_hash;
scheme = "6";
break;
#endif
case "5":
case "$5$":
crypt_hash = Crypto.SHA256.crypt_hash;
scheme = "5";
break;
#if constant(Nettle.bcrypt_hash)
case "2": // Blowfish (obsolete)
case "2a": // Blowfish (possibly weak)
case "2b": // Blowfish (long password bug fixed)
case "2x": // Blowfish (weak)
case "2y": // Blowfish (stronger)
{
string(8bit) salt;
int exp2 = -1;
if (rounds) {
int(0..) exp2;
for (exp2 = 0; 1 << exp2 < rounds && ++exp2 < 31; );
rounds = exp2;
}
if (sizeof(scheme) < 1 + 2 + 1 + 2 + 1 + 22)
salt = random_string(16);
return Nettle.bcrypt_hash(password, scheme, salt, rounds);
}
#endif
case "1":
case "$1$":
crypt_hash = Crypto.MD5.crypt_hash;
salt_size = 8;
rounds = 1000; // Currently only 1000 rounds is supported.
......@@ -512,7 +541,6 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
break;
case "sha1":
case "$sha1$":
// NetBSD-style crypt_sha1().
crypt_hash = Crypto.SHA1.HMAC.crypt_hash;
render_hash = render_old_crypt_hash;
......@@ -522,7 +550,6 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
break;
case "pbkdf2":
case "$pbkdf2$":
crypt_hash = Crypto.SHA1.crypt_pbkdf2;
render_hash = render_old_crypt_hash;
// Defaults taken from PassLib.
......@@ -532,7 +559,6 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
break;
case "pbkdf2-sha256":
case "$pbkdf2-sha256$":
crypt_hash = Crypto.SHA256.crypt_pbkdf2;
render_hash = render_old_crypt_hash;
// Defaults taken from PassLib.
......@@ -543,7 +569,6 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
#if constant(Crypto.SHA512)
case "pbkdf2-sha512":
case "$pbkdf2-sha512$":
crypt_hash = Crypto.SHA512.crypt_pbkdf2;
render_hash = render_old_crypt_hash;
// Defaults taken from Passlib.
......@@ -554,10 +579,8 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
#endif
case "P":
case "$P$":
case "U$P$":
case "H":
case "$H$":
crypt_hash = Crypto.MD5.crypt_php;
render_hash = render_php_crypt_hash;
salt_size = 8;
......@@ -565,7 +588,6 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
break;
case "Q":
case "$Q$":
crypt_hash = Crypto.SHA1.crypt_php;
render_hash = render_php_crypt_hash;
salt_size = 8;
......@@ -574,7 +596,6 @@ string(7bit) hash(string(8bit) password, string(7bit)|void scheme,
#if constant(Crypto.SHA512)
case "S":
case "$S$":
crypt_hash = Crypto.SHA512.crypt_php;
render_hash = render_php_crypt_hash;
salt_size = 8;
......
......@@ -1628,6 +1628,13 @@ dnl The following test is disabled since we do not perform any
dnl validation of the characters used in the salt.
dnl test_false(P("invalid salt", "$1$`!@#%^&*$E6hD76/pKTS8qToBCkux30"))
]])
cond_resolv(Nettle.bcrypt_hash, [[
test_true(P("yawinpassword",
"$2a$04$MzVXtd4o0y4DOlyHMMLMDeE4/eezrsT5Xad.2lmGr/NkCpwBgvn3e"))
test_true(P("Bootq9sH5",
"$2y$10$1b2lPgo4XumibnJGN3r3sOsXFfVVYlebFjlw47qpaslC4KIwu9dAK"))
test_false(P("1234", "$2y$"))
]])
cond_resolv(Crypto.SHA1, [[
// Generated using the Python PassLib via Phpass
test_true(P("password", "$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI"))
......
......@@ -128,6 +128,7 @@ else
nettle_curve25519_mul nettle_ed25519_sha512_sign \
nettle_version_major \
nettle_cmac128_set_key \
nettle_blowfish_bcrypt_hash \
)
AC_MSG_CHECKING([for idea.c])
......
......@@ -17,6 +17,7 @@
#include <nettle/yarrow.h>
#include <nettle/memxor.h>
#include <nettle/blowfish.h>
#ifdef HAVE_NETTLE_VERSION_H
#include <nettle/version.h>
......@@ -52,6 +53,92 @@ PIKEFUN string version()
#endif
}
#ifdef HAVE_NETTLE_BLOWFISH_BCRYPT_HASH
/*! @decl
*!
*! Low level implementation of the bcrypt password-hashing algorithm.
*!
*! @param password
*! The cleartext password. Only accepts 8-bit strings.
*!
*! @param scheme
*! Specifies the scheme to be used to generate the hash.
*! The settings either cleanly specify the scheme of either "2a", "2b",
*! "2x" or "2y", or they contain the (part of the prefix of) normal
*! hashed password string, so an existing hashed password string can
*! be passed unmodified.
*!
*! When generating a new hash from scratch, the following minimum needs to be
*! specified, e.g. "$2y$10$1b2lPgo4XumibnJGN3r3sO". In this "$" is the
*! separator, "2y" specifies
*! the used hash-algorithm, "10" specifies 2^10 encryption rounds
*! and "1b2lPgo4XumibnJGN3r3sO" is the salt (16 bytes, base64 encoded).
*! The minimal value for settings would be "$2y$".
*!
*! @param salt
*! The salt can be supplied as part of @[settings], or separately
*! as a 16-byte binary string.
*!
*! @param log2rounds
*! The log2 number of encryption rounds. If unspecified it is taken
*! from the settings string, and if not specified there it defaults to
*! 10 which equals 1024 encryption rounds.
*!
*! @returns The (according to the specified algorithm, encryption
*! rounds, and salt) hashed and encoded version of the supplied password.
*! Throws an error on invalid input.
*!
*! @note You should normally use @[Crypto.Password] instead.
*!
*! @seealso
*! @[Crypto.Password], @[Crypto.BLOWFISH]
*/
PIKEFUN string(7bit) bcrypt_hash(string(8bit) password, string(7bit) scheme,
string(8bit)|void salt, int|void log2rounds)
{
int retval;
struct string_builder ret;
password->flags |= STRING_CLEAR_ON_EXIT;
if (salt)
NO_WIDE_STRING(salt);
init_string_builder_alloc(&ret, BLOWFISH_BCRYPT_HASH_SIZE, 0);
retval = nettle_blowfish_bcrypt_hash(BLOWFISH_BCRYPT_HASH_SIZE, STR0(ret.s),
STR0(password), STR0(scheme),
log2rounds ? log2rounds->u.integer : -1,
salt && salt->len >= BLOWFISH_BCRYPT_BINSALT_SIZE ? STR0(salt) : NULL);
if (!retval) {
free_string_builder(&ret);
Pike_error("Invalid password hash scheme for bcrypt.\n");
}
ret.s->len = strlen(STR0(ret.s));
RETURN finish_string_builder(&ret);
}
/*! @decl
*!
*! Low level implementation of the bcrypt password-verifying algorithm.
*!
*! @param password
*! The cleartext password. Only accepts 8-bit strings.
*!
*! @param hashedpassword
*! This is the full hashed password string.
*!
*! @returns Returns 1 if the cleartext password matches the hashed password
*! and zero otherwise.
*!
*! @note You should normally use @[Crypto.Password] instead.
*!
*! @seealso
*! @[Crypto.Password], @[Crypto.BLOWFISH]
*/
PIKEFUN int bcrypt_verify(string(8bit) password, string(7bit) hashedpassword)
{
password->flags |= STRING_CLEAR_ON_EXIT;
RETURN nettle_blowfish_bcrypt_verify(STR0(password), STR0(hashedpassword));
}
#endif
/*! @class Yarrow
*!
*! Yarrow is a family of pseudo-randomness generators, designed for
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment