Switch password hashing from DES crypt to SHA-256 master
authorRemco Rijnders <remmy@serenity-irc.net>
Sat, 7 Mar 2026 17:17:36 +0000 (12:17 -0500)
committerRemco Rijnders <remmy@serenity-irc.net>
Sat, 7 Mar 2026 17:17:36 +0000 (12:17 -0500)
DES crypt is trivially crackable. Passwords starting with $ are now
auto-detected as crypt hashes (supports $5$ SHA-256, $6$ SHA-512, etc).
Plaintext passwords still work for backwards compatibility.

Remove crypt_oper_password and crypt_iline_password config options —
detection is now automatic based on the $ prefix in stored passwords.

Update mkpasswd to generate SHA-256 ($5$) hashes with random salts.
Stop logging passwords (plaintext or hashed) in oper logs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
doc/example.conf
src/crypt/mkpasswd.c
src/s_user.c

index 09829d0ad69ea6dd248857d2773b98e922385b60..d5ec5037d41481168bc2eedb881c6fa20e409cfd 100644 (file)
@@ -148,7 +148,8 @@ listen {
 # Defines who has Oper access on the server.
 #
 #   host     - Address mask the oper must connect from.
 # Defines who has Oper access on the server.
 #
 #   host     - Address mask the oper must connect from.
-#   password - Oper password (use mkpasswd if compiled with encryption).
+#   password - Oper password. Use mkpasswd to generate a SHA-256 hash,
+#              or use plaintext. Hashed passwords start with $5$.
 #   name     - Oper nickname.
 #   flags    - Access flags (see below).
 #   class    - Connection class number.
 #   name     - Oper nickname.
 #   flags    - Access flags (see below).
 #   class    - Connection class number.
@@ -232,15 +233,11 @@ uworld {
 #   hub                  - Enable hub mode (accepts multiple server links).
 #   throttle             - Enable clone/throttle detection (yes/no).
 #   seeuserstats         - Show user stat notifications to opers (yes/no).
 #   hub                  - Enable hub mode (accepts multiple server links).
 #   throttle             - Enable clone/throttle detection (yes/no).
 #   seeuserstats         - Show user stat notifications to opers (yes/no).
-#   crypt_oper_password  - Oper passwords are encrypted (yes/no).
-#   crypt_iline_password - I-line passwords are encrypted (yes/no).
 #
 general {
     # hub yes;
     throttle yes;
     seeuserstats yes;
 #
 general {
     # hub yes;
     throttle yes;
     seeuserstats yes;
-    crypt_oper_password yes;
-    crypt_iline_password yes;
 };
 
 #
 };
 
 #
index eb6a855e394860e818e03f1d9e761a8c42844d41..a85d6485b89b5da00adb15dbdb4f4e8b842e5c9c 100755 (executable)
@@ -1,40 +1,44 @@
-/* simple password generator by Nelson Minar (minar@reed.edu)
- * copyright 1991, all rights reserved.
- * You can use this code as long as my name stays with it.
+/* mkpasswd - generate SHA-256 password hashes for ircd.conf
+ *
+ * Originally by Nelson Minar (minar@reed.edu), updated for SHA-256.
+ * Usage: mkpasswd [salt]
+ *   Without arguments, generates a random salt.
+ *   With an argument, uses it as the salt.
  */
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
  */
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
+#include <unistd.h>
 
 
-extern char *getpass();
-
-int main(argc, argv)
-int argc;
-char *argv[];
+int main(int argc, char *argv[])
 {
 {
-  static char saltChars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
-  char salt[3];
-  char * plaintext;
-  int i;
-
-  if (argc < 2) {
-    srandom(time(0));          /* may not be the BEST salt, but its close */
-    salt[0] = saltChars[random() % 64];
-    salt[1] = saltChars[random() % 64];
-    salt[2] = 0;
-  }
-  else {
-    salt[0] = argv[1][0];
-    salt[1] = argv[1][1];
-    salt[2] = '\0';
-    if ((strchr(saltChars, salt[0]) == NULL) || (strchr(saltChars, salt[1]) == NULL))
-      fprintf(stderr, "illegal salt %s\n", salt), exit(1);
-  }
+    static const char saltchars[] =
+        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
+    char salt[24];
+    char *plaintext;
+    char *hash;
+    int i;
 
 
-  plaintext = getpass("plaintext: ");
+    /* Build $5$........$ salt (SHA-256) */
+    strcpy(salt, "$5$");
+    if (argc >= 2) {
+        strncat(salt, argv[1], 16);
+    } else {
+        srandom(time(NULL) ^ getpid());
+        for (i = 0; i < 16; i++)
+            salt[3 + i] = saltchars[random() % 64];
+        salt[19] = '\0';
+    }
+    strcat(salt, "$");
 
 
-  printf("%s\n", crypt(plaintext, salt));
-  return 0;
+    plaintext = getpass("plaintext: ");
+    hash = crypt(plaintext, salt);
+    if (!hash || hash[0] == '*') {
+        fprintf(stderr, "crypt() failed — SHA-256 may not be supported\n");
+        return 1;
+    }
+    printf("%s\n", hash);
+    return 0;
 }
 }
-
index b5bdeb66f8c4c208301156cb67333075c09f3f11..3d2f24141280a8d30a0a4d0376bbd002a93bb6b4 100644 (file)
@@ -472,7 +472,6 @@ static int register_user (aClient *cptr, aClient *sptr, char *nick, char *userna
     anUser *user = sptr->user;
     aClient *nsptr;
     int i;
     anUser *user = sptr->user;
     aClient *nsptr;
     int i;
-    char salt[3];
     extern char *crypt ();
 
     user->last = time (NULL);
     extern char *crypt ();
 
     user->last = time (NULL);
@@ -581,23 +580,15 @@ static int register_user (aClient *cptr, aClient *sptr, char *nick, char *userna
            u1 = NULL;
 
        if (!BadPtr (aconf->passwd)) {
            u1 = NULL;
 
        if (!BadPtr (aconf->passwd)) {
-           if (cfg_crypt_iline_password) {
-               /* use first two chars of the password they send in as salt */
-               /* passwd may be NULL. Head it off at the pass... */
-               salt[0] = '\0';
-               if (sptr->passwd && aconf->passwd && aconf->passwd[0] &&
-                   aconf->passwd[1]) {
-                   salt[0] = aconf->passwd[0];
-                   salt[1] = aconf->passwd[1];
-                   salt[2] = '\0';
-                   encr = crypt (sptr->passwd, salt);
-               }
-               else
-                   encr = "";
-           } else
-               encr = sptr->passwd;
+           /* If stored password starts with $, it's a crypt hash —
+            * use it as the salt so crypt auto-detects the algorithm.
+            * Otherwise compare plaintext. */
+           if (sptr->passwd && aconf->passwd[0] == '$')
+               encr = crypt (sptr->passwd, aconf->passwd);
+           else
+               encr = sptr->passwd ? sptr->passwd : "";
 
 
-           if (!StrEq (encr, aconf->passwd)) {
+           if (!encr || !StrEq (encr, aconf->passwd)) {
                ircstp->is_ref++;
                sendto_one (sptr, err_str (ERR_PASSWDMISMATCH), me.name,
                            parv[0]);
                ircstp->is_ref++;
                sendto_one (sptr, err_str (ERR_PASSWDMISMATCH), me.name,
                            parv[0]);
@@ -2690,7 +2681,6 @@ int m_oper (aClient *cptr, aClient *sptr, int parc, char *parv[])
 {
     aConfItem *aconf;
     char *name, *password, *encr;
 {
     aConfItem *aconf;
     char *name, *password, *encr;
-    char salt[3];
     extern char *crypt ();
 
     if (check_registered_user (sptr))
     extern char *crypt ();
 
     if (check_registered_user (sptr))
@@ -2743,23 +2733,16 @@ int m_oper (aClient *cptr, aClient *sptr, int parc, char *parv[])
        sptr->since += 7;
        return 0;
     }
        sptr->since += 7;
        return 0;
     }
-    if (cfg_crypt_oper_password) {
-       /* use first two chars of the password they send in as salt */
-       /* passwd may be NULL. Head it off at the pass... */
-       salt[0] = '\0';
-       if (password && aconf->passwd && aconf->passwd[0] && aconf->passwd[1]) {
-           salt[0] = aconf->passwd[0];
-           salt[1] = aconf->passwd[1];
-           salt[2] = '\0';
-           encr = crypt (password, salt);
-       }
-       else
-           encr = "";
-    } else
-       encr = password;
+    /* If stored password starts with $, it's a crypt hash —
+     * use it as the salt so crypt auto-detects the algorithm.
+     * Otherwise compare plaintext. */
+    if (password && aconf->passwd && aconf->passwd[0] == '$')
+       encr = crypt (password, aconf->passwd);
+    else
+       encr = password ? password : "";
 
     if ((aconf->status & CONF_OPS) &&
 
     if ((aconf->status & CONF_OPS) &&
-       StrEq (encr, aconf->passwd) && !attach_conf (sptr, aconf)) {
+       encr && StrEq (encr, aconf->passwd) && !attach_conf (sptr, aconf)) {
        int old = (sptr->umodes & ALL_UMODES);
        char *s;
 
        int old = (sptr->umodes & ALL_UMODES);
        char *s;
 
@@ -2780,11 +2763,9 @@ int m_oper (aClient *cptr, aClient *sptr, int parc, char *parv[])
        calc_mask(sptr) ;
        (void) m_opermotd (sptr, sptr, 1, parv);
 
        calc_mask(sptr) ;
        (void) m_opermotd (sptr, sptr, 1, parv);
 
-       if (!cfg_crypt_oper_password)
-           encr = "";
 #if defined(USE_SYSLOG) && defined(SYSLOG_OPER)
 #if defined(USE_SYSLOG) && defined(SYSLOG_OPER)
-       syslog (LOG_INFO, "OPER (%s) (%s) by (%s!%s@%s)",
-               name, encr, parv[0], sptr->user->username, sptr->sockhost);
+       syslog (LOG_INFO, "OPER (%s) by (%s!%s@%s)",
+               name, parv[0], sptr->user->username, sptr->sockhost);
 #endif
 #ifdef FNAME_OPERLOG
        {
 #endif
 #ifdef FNAME_OPERLOG
        {
@@ -2800,8 +2781,8 @@ int m_oper (aClient *cptr, aClient *sptr, int parc, char *parv[])
             */
            if (IsPerson (sptr) &&
                (logfile = open (FNAME_OPERLOG, O_WRONLY | O_APPEND)) != -1) {
             */
            if (IsPerson (sptr) &&
                (logfile = open (FNAME_OPERLOG, O_WRONLY | O_APPEND)) != -1) {
-               (void) sprintf (buf, "%s OPER (%s) (%s) by (%s!%s@%s)\n",
-                               myctime (time (NULL)), name, encr,
+               (void) sprintf (buf, "%s OPER (%s) by (%s!%s@%s)\n",
+                               myctime (time (NULL)), name,
                                parv[0], sptr->user->username,
                                sptr->sockhost);
                (void) write (logfile, buf, strlen (buf));
                                parv[0], sptr->user->username,
                                sptr->sockhost);
                (void) write (logfile, buf, strlen (buf));