From a3b4be9ff3982ac445ae14c29bc423c7cc5c84b2 Mon Sep 17 00:00:00 2001 From: Samanta Navarro Date: Tue, 13 Oct 2020 12:01:15 +0000 Subject: [PATCH] Add argon2 backend Argon2 is a memory-hard function for password-hashing and proof-of-work applications. It aims at the highest memory filling rate and effective use of multiple computing units, while still providing defense against tradeoff attacks. See: https://github.com/P-H-C/phc-winner-argon2 The argon2 hashes require libargon2, which is an optional dependency. Compatible with output of argon2 executable. --- AUTHORS | 5 + LICENSING | 6 + Makefile.am | 3 + NEWS | 4 +- README.md | 2 +- TODO.md | 1 - configure.ac | 10 +- doc/crypt.5 | 24 ++++ lib/alg-argon2-encoding.c | 151 ++++++++++++++++++++++++ lib/alg-argon2-encoding.h | 25 ++++ lib/crypt-argon2.c | 233 ++++++++++++++++++++++++++++++++++++++ lib/hashes.conf | 37 +++--- 12 files changed, 480 insertions(+), 21 deletions(-) create mode 100644 lib/alg-argon2-encoding.c create mode 100644 lib/alg-argon2-encoding.h create mode 100644 lib/crypt-argon2.c diff --git a/AUTHORS b/AUTHORS index 1371508f..423b1ebb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,8 @@ +The Argon2 hash module is based on algorithms and ideas developed +by Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and +Samuel Neves. See: https://github.com/P-H-C/phc-winner-argon2 for +reference. + The yescrypt code comes from yescrypt by Solar Designer . It builds upon Colin Percival's scrypt. See: http://openwall.com/yescrypt/ for reference. diff --git a/LICENSING b/LICENSING index 0781153b..d268c511 100644 --- a/LICENSING +++ b/LICENSING @@ -89,6 +89,12 @@ source tree. For specific licensing terms consult the files themselves. * Copyright ; 0-clause BSD: crypt-yescrypt.c, test-crypt-yescrypt.c + * Copyright Daniel Dinu et al.; CC0 1.0 Universal/Apache 2.0: + alg-argon2-encoding.c + + * Copyright ; 0-clause BSD: + crypt-argon2.c + * Copyright Kevin Cernekee; FSF All Permissive License: m4/ax_check_vscript.m4 diff --git a/Makefile.am b/Makefile.am index 430115ac..c50c3408 100644 --- a/Makefile.am +++ b/Makefile.am @@ -63,6 +63,7 @@ nodist_noinst_HEADERS = \ crypt-hashes.h \ crypt-symbol-vers.h noinst_HEADERS = \ + lib/alg-argon2-encoding.h \ lib/alg-des.h \ lib/alg-gost3411-2012-const.h \ lib/alg-gost3411-2012-core.h \ @@ -97,6 +98,7 @@ lib_LTLIBRARIES = \ libcrypt.la libcrypt_la_SOURCES = \ + lib/alg-argon2-encoding.c \ lib/alg-des-tables.c \ lib/alg-des.c \ lib/alg-gost3411-2012-core.c \ @@ -109,6 +111,7 @@ libcrypt_la_SOURCES = \ lib/alg-sha512.c \ lib/alg-yescrypt-common.c \ lib/alg-yescrypt-opt.c \ + lib/crypt-argon2.c \ lib/crypt-bcrypt.c \ lib/crypt-common.c \ lib/crypt-des.c \ diff --git a/NEWS b/NEWS index 1a5b581f..03e7e1fd 100644 --- a/NEWS +++ b/NEWS @@ -3,7 +3,9 @@ libxcrypt NEWS -- history of user-visible changes. Please send bug reports, questions and suggestions to . -Version 4.4.18 +Version 4.5 +* Support for the argon2 family of hashing algorithms added + ($argon2d$, $argon2i$, $argon2id$). Version 4.4.17 * Fix compilation error in 'alignas (type)' with older versions diff --git a/README.md b/README.md index d0197a01..803268c5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ README for libxcrypt libxcrypt is a modern library for one-way hashing of passwords. It supports a wide variety of both modern and historical hashing methods: -yescrypt, gost-yescrypt, scrypt, bcrypt, sha512crypt, sha256crypt, +argon2i, yescrypt, gost-yescrypt, scrypt, bcrypt, sha512crypt, sha256crypt, md5crypt, SunMD5, sha1crypt, NT, bsdicrypt, bigcrypt, and descrypt. It provides the traditional Unix `crypt` and `crypt_r` interfaces, as well as a set of extended interfaces pioneered by Openwall Linux, diff --git a/TODO.md b/TODO.md index e6b8f257..7303d2d8 100644 --- a/TODO.md +++ b/TODO.md @@ -35,7 +35,6 @@ It was last updated 20 October 2018. whether we can use these. * Additional hashing methods - * Argon2 * ...? * Runtime configurability (in progress on the [crypt.conf branch][]) diff --git a/configure.ac b/configure.ac index f3978ebc..2c05cebb 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # Process this file with autoconf to produce a configure script. m4_include([m4/zw_automodern.m4]) AC_INIT([xcrypt], - [4.4.18], + [4.5], [https://github.com/besser82/libxcrypt/issues], [libxcrypt], [https://github.com/besser82/libxcrypt]) @@ -405,6 +405,14 @@ case "$hashes_enabled" in ;; esac +# The argon2 hashes require libargon2. +AS_CASE(["$hashes_enabled"], + [*argon2*], [ + AC_SEARCH_LIBS([argon2_hash], [argon2]) + AS_IF([test x"$ac_cv_search_argon2_hash" = xno], + [AC_MSG_ERROR([Argon2 hashes require libargon2, which was not found])]) + ]) + # If the obsolete APIs are disabled, the stubs implicitly disabled as well. if test x"$COMPAT_ABI" = xno && test x"$enable_obsolete_api_enosys" = x1; then AC_MSG_WARN( diff --git a/doc/crypt.5 b/doc/crypt.5 index 6aaf9370..324ad3c2 100644 --- a/doc/crypt.5 +++ b/doc/crypt.5 @@ -189,6 +189,30 @@ The algorithm was specifically designed to make it costly to perform large-scale custom hardware attacks by requiring large amounts of memory. In 2016, the scrypt algorithm was published by IETF as RFC 7914. .hash "$7$" "\e$7\e$[./A-Za-z0-9]{11,97}\e$[./A-Za-z0-9]{43}" unlimited 8 256 256 "up to 512" "6 to 11 (logarithmic)" +.Ss argon2_d +Argon2d is a memory-hard function for password-hashing and proof-of-work +applications. It aims at the highest memory filling rate and effective use +of multiple computing units, while still providing defense against tradeoff +attacks. Argon2d uses data-dependent memory access, which makes it suitable +for cryptocurrencies and proof-of-work applications with no threats from +side-channel timing attacks. +.hash "$argon2d$" "\e$argon2i\e$v=[0-9]{2}\e$m=[0-9]{1,10},t=[0-9]{1,10},p=[0-9]{1,10}\e$[A-Za-z0-9/+]{11,86}$[A-Za-z0-9/+]{43}" unlimited 8 256 256 "64 to 512" "1 to 4,294,967,295" +.Ss argon2_i +argon2_i is a memory-hard function for password-hashing and proof-of-work +applications. It aims at the highest memory filling rate and effective use +of multiple computing units, while still providing defense against tradeoff +attacks. Argon2i uses data-independent memory access, which is preferred +for password hashing and password-based key derivation. +.hash "$argon2i$" "\e$argon2i\e$v=[0-9]{2}\e$m=[0-9]{1,10},t=[0-9]{1,10},p=[0-9]{1,10}\e$[A-Za-z0-9/+]{11,86}$[A-Za-z0-9/+]{43}" unlimited 8 256 256 "64 to 512" "1 to 4,294,967,295" +.Ss argon2_id +Argon2id is a memory-hard function for password-hashing and proof-of-work +applications. It aims at the highest memory filling rate and effective use +of multiple computing units, while still providing defense against tradeoff +attacks. Argon2id works as Argon2i for the first half of the first pass +over the memory, and as Argon2d for the rest, thus providing both +side-channel attack protection and brute-force cost savings due to +time-memory tradeoffs. +.hash "$argon2id$" "\e$argon2i\e$v=[0-9]{2}\e$m=[0-9]{1,10},t=[0-9]{1,10},p=[0-9]{1,10}\e$[A-Za-z0-9/+]{11,86}$[A-Za-z0-9/+]{43}" unlimited 8 256 256 "64 to 512" "1 to 4,294,967,295" .Ss bcrypt A hash based on the Blowfish block cipher, modified to have an extra-expensive key schedule. diff --git a/lib/alg-argon2-encoding.c b/lib/alg-argon2-encoding.c new file mode 100644 index 00000000..2dc71161 --- /dev/null +++ b/lib/alg-argon2-encoding.c @@ -0,0 +1,151 @@ +/* + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#include + +#include "alg-argon2-encoding.h" + +/* + * Some macros for constant-time comparisons. These work over values in + * the 0..255 range. Returned value is 0x00 on "false", 0xFF on "true". + */ +#define EQ(x, y) ((((0U - ((unsigned)(x) ^ (unsigned)(y))) >> 8) & 0xFF) ^ 0xFF) +#define GT(x, y) ((((unsigned)(y) - (unsigned)(x)) >> 8) & 0xFF) +#define GE(x, y) (GT(y, x) ^ 0xFF) +#define LT(x, y) GT(y, x) +#define LE(x, y) GE(y, x) + +/* + * Convert value x (0..63) to corresponding Base64 character. + */ +static unsigned b64_byte_to_char(unsigned x) { + return (LT(x, 26) & (x + 'A')) | + (GE(x, 26) & LT(x, 52) & (x + (unsigned)('a' - 26))) | + (GE(x, 52) & LT(x, 62) & (x + (unsigned)('0' - 52))) | (EQ(x, 62) & '+') | + (EQ(x, 63) & '/'); +} + +/* + * Convert character c to the corresponding 6-bit value. If character c + * is not a Base64 character, then 0xFF (255) is returned. + */ +static unsigned b64_char_to_byte(int c) { + unsigned x; + + x = (GE(c, 'A') & LE(c, 'Z') & (unsigned)(c - 'A')) | + (GE(c, 'a') & LE(c, 'z') & (unsigned)(c - ('a' - 26))) | + (GE(c, '0') & LE(c, '9') & (unsigned)(c - ('0' - 52))) | (EQ(c, '+') & 62) | + (EQ(c, '/') & 63); + return x | (EQ(x, 0) & (EQ(c, 'A') ^ 0xFF)); +} + +/* + * Convert some bytes to Base64. 'dst_len' is the length (in characters) + * of the output buffer 'dst'; if that buffer is not large enough to + * receive the result (including the terminating 0), then (size_t)-1 + * is returned. Otherwise, the zero-terminated Base64 string is written + * in the buffer, and the output length (counted WITHOUT the terminating + * zero) is returned. + */ +size_t argon2_encode64(char *dst, size_t dst_len, const uint8_t *src, + size_t src_len) { + size_t olen; + const unsigned char *buf; + unsigned acc, acc_len; + + olen = (src_len / 3) << 2; + switch (src_len % 3) { + case 2: + olen++; + /* fall through */ + case 1: + olen += 2; + break; + } + if (dst_len <= olen) { + return (size_t)-1; + } + acc = 0; + acc_len = 0; + buf = (const unsigned char *)src; + while (src_len-- > 0) { + acc = (acc << 8) + (*buf++); + acc_len += 8; + while (acc_len >= 6) { + acc_len -= 6; + *dst++ = (char)b64_byte_to_char((acc >> acc_len) & 0x3F); + } + } + if (acc_len > 0) { + *dst++ = (char)b64_byte_to_char((acc << (6 - acc_len)) & 0x3F); + } + *dst++ = 0; + return olen; +} + +/* + * Decode Base64 chars into bytes. The '*dst_len' value must initially + * contain the length of the output buffer '*dst'; when the decoding + * ends, the actual number of decoded bytes is written back in + * '*dst_len'. + * + * Decoding stops when a non-Base64 character is encountered, or when + * the output buffer capacity is exceeded. If an error occurred (output + * buffer is too small, invalid last characters leading to unprocessed + * buffered bits), then NULL is returned; otherwise, the returned value + * points to the first non-Base64 character in the source stream, which + * may be the terminating zero. + */ +const char *argon2_decode64(uint8_t *dst, size_t *dst_len, const char *src) { + size_t len; + unsigned char *buf; + unsigned acc, acc_len; + + buf = (unsigned char *)dst; + len = 0; + acc = 0; + acc_len = 0; + for (;;) { + unsigned d; + + d = b64_char_to_byte(*src); + if (d == 0xFF) { + break; + } + src++; + acc = (acc << 6) + d; + acc_len += 6; + if (acc_len >= 8) { + acc_len -= 8; + if ((len++) >= *dst_len) { + return NULL; + } + *buf++ = (acc >> acc_len) & 0xFF; + } + } + + /* + * If the input length is equal to 1 modulo 4 (which is + * invalid), then there will remain 6 unprocessed bits; + * otherwise, only 0, 2 or 4 bits are buffered. The buffered + * bits must also all be zero. + */ + if (acc_len > 4 || (acc & (((unsigned)1 << acc_len) - 1)) != 0) { + return NULL; + } + *dst_len = len; + return src; +} + diff --git a/lib/alg-argon2-encoding.h b/lib/alg-argon2-encoding.h new file mode 100644 index 00000000..5f0a482b --- /dev/null +++ b/lib/alg-argon2-encoding.h @@ -0,0 +1,25 @@ +/* + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#ifndef ALG_ARGON2_ENCODING_H +#define ALG_ARGON2_ENCODING_H + +#include + +size_t argon2_encode64 (char *dst, size_t dst_len, const uint8_t *src, + size_t src_len); +const char *argon2_decode64 (uint8_t *dst, size_t *dst_len, const char *src); + +#endif diff --git a/lib/crypt-argon2.c b/lib/crypt-argon2.c new file mode 100644 index 00000000..a4fdb440 --- /dev/null +++ b/lib/crypt-argon2.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2020 Samanta Navarro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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-hashes.h" + +#include + +#if INCLUDE_argon2_d || INCLUDE_argon2_i || INCLUDE_argon2_id + +#include +#include +#include +#include +#include + +#include "alg-argon2-encoding.h" + +static uint32_t +get_value(const char *nptr, const char *arg, char **endptr, const char end) +{ + unsigned long val; + + if (strncmp (nptr, arg, strlen (arg))) + return 0; + nptr += strlen (arg); + if (!(*nptr >= '1' && *nptr <= '9')) + return 0; + val = strtoul (nptr, endptr, 10); + if (val == 0 || val > UINT32_MAX || **endptr != end) + return 0; + return (uint32_t) val; +} + +#define GET_VALUE(dst, arg, end) \ +{ \ + dst = get_value (s, arg, &q, end); \ + if (dst == 0) \ + { \ + errno = EINVAL; \ + return; \ + } \ + s = q + 1; \ +} + +static void +crypt_argon2_rn (const argon2_type hash_type, + const char *phrase, size_t phr_size, + const char *setting, size_t set_size, + uint8_t *output, size_t o_size, + void *scratch, size_t s_size) +{ + char *buf = scratch, *q; + const char *d, *s = setting, *type; + uint8_t *salt; + size_t pos, salt_len; + uint32_t v, m, t, p; + + if (s_size == 0 || s_size < phr_size || set_size < 26) + { + errno = EINVAL; + return; + } + + switch (hash_type) { + case Argon2_d: + type = "$argon2d$"; + break; + case Argon2_i: + type = "$argon2i$"; + break; + case Argon2_id: + type = "$argon2id$"; + break; + default: + type = NULL; + break; + } + + if (type == NULL) + { + errno = EINVAL; + return; + } + + /* Setting for a different hash algorithm. */ + if (strncmp (s, type, strlen(type))) + { + errno = EINVAL; + return; + } + s += strlen(type); + + GET_VALUE(v, "v=", '$'); + GET_VALUE(m, "m=", ','); + GET_VALUE(t, "t=", ','); + GET_VALUE(p, "p=", '$'); + + strncpy (buf, s, s_size); + buf[s_size - 1] = '\0'; + pos = strcspn (buf, "$"); + buf[pos] = '\0'; + salt = (uint8_t *) buf + pos + 1; + salt_len = s_size - pos - 1; + if ((d = argon2_decode64 (salt, &salt_len, buf)) == NULL || + (*d != '\0' && *d != '$')) + { + errno = EINVAL; + return; + } + + /* Parameters are invalid. */ + if (argon2_hash (t, m, p, phrase, phr_size, salt, salt_len, + NULL, 32, (char *) output, o_size, hash_type, v) != ARGON2_OK) + { + errno = EINVAL; + return; + } +} + +#if INCLUDE_argon2_d +void +crypt_argon2_d_rn (const char *phrase, size_t phr_size, + const char *setting, size_t set_size, + uint8_t *output, size_t o_size, + void *scratch, size_t s_size) +{ + crypt_argon2_rn (Argon2_d, phrase, phr_size, setting, set_size, + output, o_size, scratch, s_size); +} +#endif /* INCLUDE_argon2_d */ + +#if INCLUDE_argon2_i +void +crypt_argon2_i_rn (const char *phrase, size_t phr_size, + const char *setting, size_t set_size, + uint8_t *output, size_t o_size, + void *scratch, size_t s_size) +{ + crypt_argon2_rn (Argon2_i, phrase, phr_size, setting, set_size, + output, o_size, scratch, s_size); +} +#endif /* INCLUDE_argon2_i */ + +#if INCLUDE_argon2_id +void +crypt_argon2_id_rn (const char *phrase, size_t phr_size, + const char *setting, size_t set_size, + uint8_t *output, size_t o_size, + void *scratch, size_t s_size) +{ + crypt_argon2_rn (Argon2_id, phrase, phr_size, setting, set_size, + output, o_size, scratch, s_size); +} +#endif /* INCLUDE_argon2_id */ + +static void +gensalt_argon2_rn (const char *type, unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t o_size) +{ + char buf[87]; + int s; + + if (count < ARGON2_MIN_TIME || count > ARGON2_MAX_TIME || + nrbytes < ARGON2_MIN_SALT_LENGTH || nrbytes > ARGON2_MAX_SALT_LENGTH) + { + errno = ERANGE; + return; + } + + if (argon2_encode64 (buf, sizeof(buf), rbytes, nrbytes) == (size_t) -1) + { + errno = ERANGE; + return; + } + + s = snprintf ((char *) output, o_size, "$%s$v=%d$m=4096,t=%lu,p=1$%s$", + type, ARGON2_VERSION_NUMBER, count, buf); + if (s < 0 || (size_t) s >= o_size) + { + errno = ERANGE; + return; + } +} + +#if INCLUDE_argon2_d +void +gensalt_argon2_d_rn (unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t o_size) +{ + gensalt_argon2_rn("argon2d", count, rbytes, nrbytes, output, o_size); +} +#endif /* INCLUDE_argon2_d */ + +#if INCLUDE_argon2_i +void +gensalt_argon2_i_rn (unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t o_size) +{ + gensalt_argon2_rn("argon2i", count, rbytes, nrbytes, output, o_size); +} +#endif /* INCLUDE_argon2_i */ + +#if INCLUDE_argon2_id +void +gensalt_argon2_id_rn (unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t o_size) +{ + gensalt_argon2_rn("argon2id", count, rbytes, nrbytes, output, o_size); +} +#endif /* INCLUDE_argon2_id */ + +#endif /* INCLUDE_argon2_d || INCLUDE_argon2_i || INCLUDE_argon2_id */ diff --git a/lib/hashes.conf b/lib/hashes.conf index 99ed1162..e781640d 100644 --- a/lib/hashes.conf +++ b/lib/hashes.conf @@ -38,20 +38,23 @@ # descrypt) must be last (conveniently, these are also the weakest # supported hashes). # -#name h_prefix nrbytes flags -yescrypt $y$ 16 STRONG,DEFAULT,ALT,FEDORA -gost_yescrypt $gy$ 16 STRONG,ALT -scrypt $7$ 16 STRONG -bcrypt $2b$ 16 STRONG,DEFAULT,ALT,FREEBSD,NETBSD,OPENBSD,OWL,SOLARIS,SUSE -bcrypt_y $2y$ 16 STRONG,ALT,OWL,SUSE -bcrypt_a $2a$ 16 STRONG,ALT,FREEBSD,NETBSD,OPENBSD,OWL,SOLARIS,SUSE -bcrypt_x $2x$ 16 ALT,OWL,SUSE -sha512crypt $6$ 15 STRONG,DEFAULT,GLIBC,FREEBSD,SOLARIS -sha256crypt $5$ 15 GLIBC,FREEBSD,SOLARIS -sha1crypt $sha1 20 NETBSD -sunmd5 $md5 8 SOLARIS -md5crypt $1$ 9 GLIBC,FREEBSD,NETBSD,OPENBSD,SOLARIS -nt $3$ 1 FREEBSD -bsdicrypt _ 3 FREEBSD,NETBSD,OPENBSD,OSX -bigcrypt : 2 : -descrypt : 2 GLIBC,FREEBSD,NETBSD,OPENBSD,SOLARIS,OSX +#name h_prefix nrbytes flags +yescrypt $y$ 16 STRONG,DEFAULT,ALT,FEDORA +gost_yescrypt $gy$ 16 STRONG,ALT +argon2_id $argon2id$ 16 STRONG,NETBSD +argon2_i $argon2i$ 16 STRONG +argon2_d $argon2d$ 16 STRONG +scrypt $7$ 16 STRONG +bcrypt $2b$ 16 STRONG,DEFAULT,ALT,FREEBSD,NETBSD,OPENBSD,OWL,SOLARIS,SUSE +bcrypt_y $2y$ 16 STRONG,ALT,OWL,SUSE +bcrypt_a $2a$ 16 STRONG,ALT,FREEBSD,NETBSD,OPENBSD,OWL,SOLARIS,SUSE +bcrypt_x $2x$ 16 ALT,OWL,SUSE +sha512crypt $6$ 15 STRONG,DEFAULT,GLIBC,FREEBSD,SOLARIS +sha256crypt $5$ 15 GLIBC,FREEBSD,SOLARIS +sha1crypt $sha1 20 NETBSD +sunmd5 $md5 8 SOLARIS +md5crypt $1$ 9 GLIBC,FREEBSD,NETBSD,OPENBSD,SOLARIS +nt $3$ 1 FREEBSD +bsdicrypt _ 3 FREEBSD,NETBSD,OPENBSD,OSX +bigcrypt : 2 : +descrypt : 2 GLIBC,FREEBSD,NETBSD,OPENBSD,SOLARIS,OSX