>From 3d3c479c37be19b4bc7b8b322775baf51d4ae826 Mon Sep 17 00:00:00 2001 From: Rich Felker Date: Thu, 12 Nov 2026 10:33:32 -0500 Subject: [PATCH] crypt: drop support for non-strict des salts in new hashes our descrypt implementation followed a historical practice of interpreting salt characters outside the set [./0-9A-Za-z] as alternate/noncanonical encodings. this served basically no purpose except possibly supporting des hashes made on ancient systems enforcing the salt character set. however, allowing this for new hashes has several negative consequences, including: 1. unrecognized hash types encoded with "$n$" notation get interpreted as very weak, fixed-value des salts. 2. with low but plausible probability, the result of des hashing a salt consisting of a '$' followed by a digit can produce hash bits that mimic the setting for a different hash type, resulting in a hash that will be unmatchable later (user locked out). with this change, noncanonical salt characters are still interpreted and result in a hash being computed, but the result is thrown away and replaced with failure unless the full hash containing them was passed as the setting and the password matches. the self-test in top-level __crypt_des is updated to canonicalize the test salt; without this change, the self-test would always have failed. --- src/crypt/crypt_des.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/crypt/crypt_des.c b/src/crypt/crypt_des.c index 38e63395..ae8dd2e8 100644 --- a/src/crypt/crypt_des.c +++ b/src/crypt/crypt_des.c @@ -672,6 +672,15 @@ static inline int ascii_is_unsafe(unsigned char ch) return !ch || ch == '\n' || ch == ':'; } +/* + * We only allow noncanonical salt characters for checking existing + * hashes, not producing new ones. + */ +static inline int ascii_is_noncanonical(unsigned char ch) +{ + return ch!='.' && ch!='/' && (ch|32)-'a'>=26U && ch-'0'>=10U; +} + static uint32_t setup_salt(uint32_t salt) { uint32_t obit, saltbit, saltbits; @@ -978,6 +987,12 @@ static char *_crypt_extended_r_uut(const char *_key, const char *_setting, char *p++ = ascii64[l & 0x3f]; *p = 0; + if (ascii_is_noncanonical(output[0]) + || ascii_is_noncanonical(output[1])) { + if (output[0] != '_' && strcmp(output, _setting)) + return NULL; + } + return output; } @@ -992,8 +1007,8 @@ char *__crypt_des(const char *key, const char *setting, char *output) const char *p; if (*setting != _PASSWORD_EFMT1) { - test_setting = "\x80x"; - test_hash = "\x80x22/wK52ZKGA"; + test_setting = "Gx"; + test_hash = "Gx22/wK52ZKGA"; } /* -- 2.21.0