diff --git a/src/sbbs3/str.cpp b/src/sbbs3/str.cpp
index fff7567b4736f1f8d45ab965cb2f897b731b4771..d7124dd3ab0165a893163002ba03baf0800a3fc8 100644
--- a/src/sbbs3/str.cpp
+++ b/src/sbbs3/str.cpp
@@ -843,86 +843,18 @@ bool sbbs_t::inputnstime(time_t *dt)
 	return true;
 }
 
-/*****************************************************************************/
-/* Checks a password for uniqueness and validity                              */
-/*****************************************************************************/
+/****************************************************************************/
+/* Check a password for validity and print reason upon failure				*/
+/****************************************************************************/
 bool sbbs_t::chkpass(char *passwd, user_t* user, bool unique)
 {
-	char first[128], last[128], sysop[41], sysname[41], *p;
-	char alias[LEN_ALIAS + 1], name[LEN_NAME + 1], handle[LEN_HANDLE + 1];
-	char pass[LEN_PASS + 1];
-
-	SAFECOPY(pass, passwd);
-	strupr(pass);
-
-	int len = strlen(pass);
-	if (len < cfg.min_pwlen || len < MIN_PASS_LEN) {
-		bputs(text[PasswordTooShort]);
-		return false;
-	}
-	if (unique && strcmp(pass, user->pass) == 0) {
-		bputs(text[PasswordNotChanged]);
-		return false;
-	}
-	int i;
-	int run = 0;
-	for (i = 0; i < (len - 1); ++i) {
-		if (abs(toupper(pass[i]) - toupper(pass[i + 1])) > 1) {
-			if (++run >= cfg.min_pwlen / 2)
-				break;
-		} else
-			run = 0;
-	}
-	if (i >= (len - 1)) {
-		bputs(text[PasswordInvalid]);
-		return false;
-	}
-	SAFECOPY(name, user->name);
-	strupr(name);
-	SAFECOPY(alias, user->alias);
-	strupr(alias);
-	SAFECOPY(first, alias);
-	p = strchr(first, ' ');
-	if (p) {
-		*p = 0;
-		SAFECOPY(last, p + 1);
-	}
-	else
-		last[0] = 0;
-	SAFECOPY(handle, user->handle);
-	strupr(handle);
-	SAFECOPY(sysop, cfg.sys_op);
-	strupr(sysop);
-	SAFECOPY(sysname, cfg.sys_name);
-	strupr(sysname);
-	if ((unique && user->pass[0]
-	     && (strstr(pass, user->pass) || strstr(user->pass, pass)))
-	    || (name[0]
-	        && (strstr(pass, name) || strstr(name, pass)))
-	    || strstr(pass, alias) || strstr(alias, pass)
-	    || strstr(pass, first) || strstr(first, pass)
-	    || (last[0]
-	        && (strstr(pass, last) || strstr(last, pass)))
-	    || strstr(pass, handle) || strstr(handle, pass)
-	    || (user->zipcode[0]
-	        && (strstr(pass, user->zipcode) || strstr(user->zipcode, pass)))
-	    || (sysname[0]
-	        && (strstr(pass, sysname) || strstr(sysname, pass)))
-	    || (sysop[0]
-	        && (strstr(pass, sysop) || strstr(sysop, pass)))
-	    || (cfg.sys_id[0]
-	        && (strstr(pass, cfg.sys_id) || strstr(cfg.sys_id, pass)))
-	    || (cfg.node_phone[0] && strstr(pass, cfg.node_phone))
-	    || (user->phone[0] && strstr(user->phone, pass))
-	    || !strncmp(pass, "QWER", 4)
-	    || !strncmp(pass, "ASDF", 4)
-	    || !strncmp(pass, "!@#$", 4)
-	    )
-	{
-		bputs(text[PasswordObvious]);
+	int reason = -1;
+	if (!check_pass(&cfg, passwd, user, unique, &reason)) {
+		if (reason >= 0)
+			bputs(text[reason]);
 		return false;
 	}
-	return !trashcan(pass, "password");
+	return !trashcan(passwd, "password");
 }
 
 /****************************************************************************/
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 662ac7634be3841b78836c47c5b2f982f961983b..9b2b0e095512f0909692698f21a43ddfc1be3c0b 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -4588,3 +4588,88 @@ enum parsed_vpath parse_vpath(scfg_t* cfg, const char* vpath, int* lib, int* dir
 
 	return *filename == NULL ? PARSED_VPATH_DIR : PARSED_VPATH_FULL;
 }
+
+/****************************************************************************/
+/* Check a password for uniqueness and validity								*/
+/* Does *not* check the password.can file!									*/
+/****************************************************************************/
+bool check_pass(scfg_t* cfg, const char *pass, user_t* user, bool unique, int* reason)
+{
+	int reason_;
+
+	if (reason == NULL)
+		reason = &reason_;
+
+	int len = strlen(pass);
+	if (len < cfg->min_pwlen || len < MIN_PASS_LEN) {
+		*reason = PasswordTooShort;
+		return false;
+	}
+	if (unique) {
+		if (user == NULL)
+			return false;
+		if (stricmp(pass, user->pass) == 0) {
+			*reason = PasswordNotChanged;
+			return false;
+		}
+	}
+
+	// Require a minimum sequence of unique (non-repeating/increment/decrementing) characters
+	int i;
+	int run = 0;
+	for (i = 0; i < (len - 1); ++i) {
+		if (abs(toupper(pass[i]) - toupper(pass[i + 1])) > 1) {
+			if (++run >= cfg->min_pwlen / 2)
+				break;
+		} else
+			run = 0;
+	}
+	if (i >= (len - 1)) {
+		*reason = PasswordInvalid;
+		return false;
+	}
+
+	// Compare proposed password against user properties
+	if (user != NULL) {
+		char first[128], last[128], *p;
+
+		SAFECOPY(first, user->alias);
+		p = strchr(first, ' ');
+		if (p) {
+			*p = 0;
+			SAFECOPY(last, p + 1);
+		}
+		else
+			last[0] = 0;
+		if ((unique && user->pass[0]
+			 && (strcasestr(pass, user->pass) || strcasestr(user->pass, pass)))
+			|| (user->name[0]
+				&& (strcasestr(pass, user->name) || strcasestr(user->name, pass)))
+			|| strcasestr(pass, user->alias) || strcasestr(user->alias, pass)
+			|| strcasestr(pass, first) || strcasestr(first, pass)
+			|| (last[0]
+				&& (strcasestr(pass, last) || strcasestr(last, pass)))
+			|| strcasestr(pass, user->handle) || strcasestr(user->handle, pass)
+			|| (user->zipcode[0]
+				&& (strcasestr(pass, user->zipcode) || strcasestr(user->zipcode, pass)))
+			|| (user->phone[0] && strcasestr(user->phone, pass))
+			) {
+			*reason = PasswordObvious;
+			return false;
+		}
+	}
+
+	// Compare proposed password against system properties
+	if ((cfg->sys_name[0]
+	        && (strcasestr(pass, cfg->sys_name) || strcasestr(cfg->sys_name, pass)))
+	    || (cfg->sys_op[0]
+	        && (strcasestr(pass, cfg->sys_op) || strcasestr(cfg->sys_op, pass)))
+	    || (cfg->sys_id[0]
+	        && (strcasestr(pass, cfg->sys_id) || strcasestr(cfg->sys_id, pass)))
+	    || (cfg->node_phone[0] && strcasestr(pass, cfg->node_phone))
+	    ) {
+		*reason = PasswordObvious;
+		return false;
+	}
+	return true;
+}
diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h
index a04744540e50333375ceeed9a6ce69028850b3cb..6d07f7915bf3400c6506ffec862efc38e8eb9700 100644
--- a/src/sbbs3/userdat.h
+++ b/src/sbbs3/userdat.h
@@ -202,6 +202,7 @@ DLLEXPORT bool  user_adjust_minutes(scfg_t*, user_t*, long amount);
 
 DLLEXPORT time_t gettimeleft(scfg_t*, user_t*, time_t starttime);
 
+DLLEXPORT bool  check_pass(scfg_t*, const char *passwd, user_t* user, bool unique, int* reason);
 DLLEXPORT bool  check_name(scfg_t*, const char* name);
 DLLEXPORT bool  check_realname(scfg_t*, const char* name);
 DLLEXPORT bool  sysop_available(scfg_t*);