Module Name:    src
Committed By:   nia
Date:           Wed Oct 20 13:03:29 UTC 2021

Modified Files:
        src/lib/libcrypt: crypt-argon2.c pw_gensalt.c

Log Message:
crypt(3): Adapt default Argon2 parameters to system performance

If the parameters are unspecified:

- Set the default memory consumption based on the amount of memory
available to userspace.

The algorithm actually slows down incredibly quickly as the "memory"
parameter is increased. We want to avoid running out of memory on low
memory systems, but increase the difficulty of bruteforcing passwords
from systems with a lot of memory. At the same time, we want to avoid
problems when concurrent logins are happening.

- Run a hashing loop for one second with steadily increasing "time"
until we settle on a value for "time". We want to use as much CPU time
as reasonable for computing the password hash without making logins
inconvenient.


To generate a diff of this commit:
cvs rdiff -u -r1.10 -r1.11 src/lib/libcrypt/crypt-argon2.c
cvs rdiff -u -r1.12 -r1.13 src/lib/libcrypt/pw_gensalt.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libcrypt/crypt-argon2.c
diff -u src/lib/libcrypt/crypt-argon2.c:1.10 src/lib/libcrypt/crypt-argon2.c:1.11
--- src/lib/libcrypt/crypt-argon2.c:1.10	Sat Oct 16 10:53:33 2021
+++ src/lib/libcrypt/crypt-argon2.c	Wed Oct 20 13:03:29 2021
@@ -24,6 +24,11 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <sys/resource.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/syslimits.h>
+
 #include <stdlib.h>
 #include <stdio.h> 
 #include <unistd.h>
@@ -37,6 +42,10 @@
 #include <err.h>
 #include "crypt.h"
 
+crypt_private int
+estimate_argon2_params(argon2_type, uint32_t *,
+    uint32_t *, uint32_t *);
+
 /* defaults pulled from run.c */
 #define HASHLEN		32
 #define T_COST_DEF 	3 
@@ -57,6 +66,10 @@
 #define ARGON2_ARGON2D_STR	"argon2d"
 #define ARGON2_ARGON2ID_STR	"argon2id"
 
+/*
+ * Unpadded Base64 calculations are taken from the Apache2/CC-0
+ * licensed libargon2 for compatibility
+ */
 
 /*
  * Some macros for constant-time comparisons. These work over values in
@@ -123,6 +136,103 @@ from_base64(void *dst, size_t *dst_len, 
 	return src;
 }
 
+/*
+ * Used to find default parameters that perform well on the host
+ * machine.  Inputs should dereference to either 0 (to estimate),
+ * or desired value.
+ */
+crypt_private int
+estimate_argon2_params(argon2_type atype, uint32_t *etime,
+    uint32_t *ememory, uint32_t *ethreads)
+{
+	const int mib[] = { CTL_HW, HW_USERMEM64 };
+	struct timespec tp1, tp2, delta;
+	char tmp_salt[16];
+	char tmp_pwd[16];
+	uint32_t tmp_hash[32];
+	char tmp_encoded[256];
+	struct rlimit rlim;
+	uint64_t max_mem;
+	size_t max_mem_sz = sizeof(max_mem);
+	/* low values from argon2 test suite... */
+	uint32_t memory = 256;
+	uint32_t time = 2;
+	uint32_t threads = 1;
+
+	if (*ememory < ARGON2_MIN_MEMORY) {
+		/*
+		 * attempt to find a reasonble bound for memory use
+		 */
+		if (sysctl(mib, __arraycount(mib),
+		    &max_mem, &max_mem_sz, NULL, 0) < 0) {
+			goto error;
+		}
+		if (getrlimit(RLIMIT_AS, &rlim) < 0)
+			goto error;
+		if (max_mem > rlim.rlim_cur && rlim.rlim_cur != RLIM_INFINITY)
+			max_mem = rlim.rlim_cur;
+
+		/*
+		 * Note that adding memory also greatly slows the algorithm.
+		 * Do we need to be concerned about memory usage during
+		 * concurrent connections?
+		 */
+		max_mem /= 1000000;
+		if (max_mem > 30000) {
+			memory = 8192;
+		} else if (max_mem > 7000) {
+			memory = 4096;
+		} else if (max_mem > 24) {
+			memory = 256;
+		} else {
+			memory = ARGON2_MIN_MEMORY;
+		}
+	} else {
+		memory = *ememory;
+	}
+
+	if (*etime < ARGON2_MIN_TIME) {
+		/*
+		 * just fill these with random stuff since we'll immediately
+		 * discard them after calculating hashes for 1 second
+		 */
+		arc4random_buf(tmp_pwd, sizeof(tmp_pwd));
+		arc4random_buf(tmp_salt, sizeof(tmp_salt));
+
+		if (clock_gettime(CLOCK_MONOTONIC, &tp1) == -1)
+			goto error;
+		for (; delta.tv_sec < 1 && time < ARGON2_MAX_TIME; ++time) {
+			if (argon2_hash(time, memory, threads,
+			    tmp_pwd, sizeof(tmp_pwd), 
+			    tmp_salt, sizeof(tmp_salt), 
+			    tmp_hash, sizeof(tmp_hash), 
+			    tmp_encoded, sizeof(tmp_encoded), 
+			    atype, ARGON2_VERSION_NUMBER) != ARGON2_OK) {
+				goto reset;
+			}
+			if (clock_gettime(CLOCK_MONOTONIC, &tp2) == -1)
+				break;
+			if (timespeccmp(&tp1, &tp2, >))
+				break; /* broken system... */
+			timespecsub(&tp2, &tp1, &delta);
+		}
+	} else {
+		time = *etime;
+	}
+
+error:
+	*etime = time;
+	*ememory = memory;
+	*ethreads = threads;
+	return 0;
+reset:
+	time = 2;
+	memory = 256;
+	threads = 1;
+	goto error;
+}
+
+
 /* process params to argon2 */
 /* we don't force param order as input, */
 /* but we do provide the expected order to argon2 api */

Index: src/lib/libcrypt/pw_gensalt.c
diff -u src/lib/libcrypt/pw_gensalt.c:1.12 src/lib/libcrypt/pw_gensalt.c:1.13
--- src/lib/libcrypt/pw_gensalt.c:1.12	Sat Oct 16 10:53:33 2021
+++ src/lib/libcrypt/pw_gensalt.c	Wed Oct 20 13:03:29 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: pw_gensalt.c,v 1.12 2021/10/16 10:53:33 nia Exp $	*/
+/*	$NetBSD: pw_gensalt.c,v 1.13 2021/10/20 13:03:29 nia Exp $	*/
 
 /*
  * Copyright 1997 Niels Provos <pro...@physnet.uni-hamburg.de>
@@ -34,7 +34,7 @@
 
 #include <sys/cdefs.h>
 #ifndef lint
-__RCSID("$NetBSD: pw_gensalt.c,v 1.12 2021/10/16 10:53:33 nia Exp $");
+__RCSID("$NetBSD: pw_gensalt.c,v 1.13 2021/10/20 13:03:29 nia Exp $");
 #endif /* not lint */
 
 #include <sys/syslimits.h>
@@ -59,6 +59,9 @@ __RCSID("$NetBSD: pw_gensalt.c,v 1.12 20
 #define ARGON2_ARGON2I_STR      "argon2i"
 #define ARGON2_ARGON2D_STR      "argon2d"
 #define ARGON2_ARGON2ID_STR     "argon2id"
+
+crypt_private int
+estimate_argon2_params(argon2_type, uint32_t *, uint32_t *, uint32_t *);
 #endif /* HAVE_ARGON2 */
 
 static const struct pw_salt {
@@ -163,15 +166,16 @@ __gensalt_sha1(char *salt, size_t saltsi
 
 #ifdef HAVE_ARGON2
 static int
-__gensalt_argon2_decode_option(char *dst, size_t dlen, const char *option)
+__gensalt_argon2_decode_option(char *dst, size_t dlen,
+    const char *option, argon2_type atype)
 {
-
-	char * in = 0;
-	char * a = 0;
+	char *in = 0;
+	char *a = 0;
 	size_t tmp = 0;
 	int error = 0;
-	/* ob buffer: m_cost, t_cost, threads */
-	uint32_t ob[3] = {4096, 3, 1}; 
+	uint32_t memory = 0;
+	uint32_t time = 0;
+	uint32_t threads = 0;
 
 	memset(dst, 0, dlen);
 
@@ -179,37 +183,33 @@ __gensalt_argon2_decode_option(char *dst
 		goto done;
 	}
 
-	in = (char *)strdup(option);
+	in = strdup(option);
 
 	while ((a = strsep(&in, ",")) != NULL) {
-		switch(*a) {
-
+		switch (*a) {
 			case 'm':
 				a += strlen("m=");
 				if ((getnum(a, &tmp)) == -1) {
 					--error;
 				} else {
-					ob[0] = tmp;
+					memory = tmp;
 				}
-
 				break;
 			case 't':
 				a += strlen("t=");
 				if ((getnum(a, &tmp)) == -1) {
 					--error;
 				} else {
-					ob[1] = tmp;
+					time = tmp;
 				}
-
 				break;
 			case 'p':
 				a += strlen("p=");
 				if ((getnum(a, &tmp)) == -1) {
 					--error;
 				} else {
-					ob[2] = tmp;
+					threads = tmp;
 				}
-
 				break;
 			default:
 				--error;
@@ -217,22 +217,35 @@ __gensalt_argon2_decode_option(char *dst
 	}
 
 	free(in);
+
 done:
-	snprintf(dst, dlen, "m=%d,t=%d,p=%d", ob[0], ob[1], ob[2]);
+	/*
+	 * If parameters are unspecified, calculate some reasonable
+	 * ones based on system time.
+	 */
+	if (memory < ARGON2_MIN_MEMORY ||
+	    time < ARGON2_MIN_TIME ||
+	    threads < ARGON2_MIN_THREADS) {
+		estimate_argon2_params(atype, &time, &memory, &threads);
+	}
+
+	snprintf(dst, dlen, "m=%d,t=%d,p=%d", memory, time, threads);
 
 	return error;
 }
 
 
 static int
-__gensalt_argon2(char *salt, size_t saltsiz, const char *option,argon2_type atype)
+__gensalt_argon2(char *salt, size_t saltsiz,
+    const char *option, argon2_type atype)
 {
 	int rc;
 	int n;
 	char buf[64];
 
 	/* get param, enforcing order and applying defaults */
-	if ((rc = __gensalt_argon2_decode_option(buf, sizeof(buf), option)) < 0) {
+	if ((rc = __gensalt_argon2_decode_option(buf,
+	    sizeof(buf), option, atype)) < 0) {
 		return 0;
 	}
 

Reply via email to