diff options
| author | Mattias Andrée <m@maandree.se> | 2026-05-01 17:45:39 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-05-01 17:45:39 +0200 |
| commit | adfa8e1265f6155d1a582baa9929af198bb5d4de (patch) | |
| tree | e3cee62aa5a8768621cd294295f787b8cc54141b /librecrypt_hash_.c | |
| parent | Add librecrypt.7 and README (diff) | |
| download | librecrypt-adfa8e1265f6155d1a582baa9929af198bb5d4de.tar.gz librecrypt-adfa8e1265f6155d1a582baa9929af198bb5d4de.tar.bz2 librecrypt-adfa8e1265f6155d1a582baa9929af198bb5d4de.tar.xz | |
Misc
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'librecrypt_hash_.c')
| -rw-r--r-- | librecrypt_hash_.c | 177 |
1 files changed, 146 insertions, 31 deletions
diff --git a/librecrypt_hash_.c b/librecrypt_hash_.c index ed842e9..563e072 100644 --- a/librecrypt_hash_.c +++ b/librecrypt_hash_.c @@ -14,6 +14,20 @@ zero_generator(void *out, size_t n, void *user) } +static int +has_asterisk_encoded_salt(const char *settings) +{ + const char *asterisk; + /* Check whether there is an asterisk */ + asterisk = strchr(settings, '*'); + if (!asterisk) + return 0; + /* Check that the first asterisk is not part of the hash result + * (would specify hash size) rather than be a salt */ + return !!strchr(&asterisk[1u], LIBRECRYPT_HASH_COMPOSITION_DELIMITER); +} + + ssize_t librecrypt_hash_(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved, enum action action) @@ -23,25 +37,35 @@ librecrypt_hash_(char *restrict out_buffer, size_t size, const char *phrase, siz char *settings_scratch = NULL; char *phrase_scratches[2] = {NULL, NULL}; size_t phrase_scratch_sizes[2] = {0u, 0u}; - size_t n, ascii_len, min, prefix, ret = 0u; + size_t i, n, ascii_len, min, prefix, ret = 0u; + size_t hash_size, digit, quotient, remainder; int has_next, phrase_scratch_i = 0; ssize_t r_len; int r; void *new; - if (reserved != NULL) { - errno = EINVAL; - return -1; - } + /* Ensure the reserved parameter is NULL */ + if (reserved != NULL) + goto einval; - if (!size) - rng = &zero_generator; + /* Realise asterisk-encoded salts */ + if (has_asterisk_encoded_salt(settings)) { + /* Only `librecrypt_crypt` outputs the configrations, + * and thus only `librecrypt_crypt` outputs salts, so + * `librecrypt_hash` and `librecrypt_hash_binary` may + * not use asterisk-encoding for salts as the generated + * salt would be lost (use `librecrypt_realise_salts` + * first instead) */ + if (action != ASCII_CRYPT) + goto einval; - if (strchr(settings, '*')) { - if (action != ASCII_CRYPT) { - errno = EINVAL; - return -1; - } + /* If there no output, don't waste time and entropy + * generating random salts, just write generates + * zeroes instead */ + if (!size) + rng = &zero_generator; + + /* Generate the salts */ r_len = librecrypt_realise_salts(out_buffer, size, settings, rng, NULL); if (r_len < 0) { if (errno == ERANGE) @@ -58,6 +82,7 @@ librecrypt_hash_(char *restrict out_buffer, size_t size, const char *phrase, siz } next: + /* Measure algorithm configuration size until next-algorithm marker (or end of string) */ has_next = 0; for (n = 0u; settings[n]; n++) { if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) { @@ -66,19 +91,77 @@ next: } } + /* Identify the algorithm */ algo = librecrypt_find_first_algorithm_(settings, n); if (!algo) { errno = ENOSYS; goto fail; } - prefix = (*algo->get_prefix)(settings, n); - if (has_next && prefix < n) { - errno = EINVAL; - goto fail; + /* Get length of algorithm configuration text sans hash size */ + prefix = 0u; + for (i = 0u; i < n; i++) + if (settings[i] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER) + prefix = i + 1u; + /* TODO "_" is a prefix that is being used */ + if (!algo->flexible_hash_size && prefix != n) + goto einval; + + /* Get hash size */ + if (!algo->flexible_hash_size) { + /* fixed */ + hash_size = algo->hash_size; + } else if (prefix == n) { + /* default */ + hash_size = algo->hash_size; + } else if (settings[prefix] == '*') { + /* hash length encoded */ + i = prefix + 1u; + hash_size = 0u; + for (; i < n; i++) { + if ('0' > settings[i] || settings[i] > '9') + goto einval; + digit = (size_t)(settings[i] - '0'); + if (hash_size > (SIZE_MAX - digit) / 10u) + goto einval; + hash_size *= 10u; + hash_size += digit; + } + if (!hash_size) + goto einval; + } else if (has_next) { + /* hash encoded, but is intermediate hash */ + goto einval; + } else { + /* hash encoded, and is final hash */ + for (i = prefix; i < n; i++) + if (algo->decoding_lut[(unsigned char)settings[i]] == 0xFFu) + break; + hash_size = i - prefix; + if (algo->pad && algo->strict_pad) { + for (; i < n; i++) + if (settings[i] != algo->pad) + break; + if (i - prefix % 4u) + goto einval; + if (i - prefix - hash_size >= 4u) + goto einval; + } + if (i != n) + goto einval; + quotient = hash_size / 4u; + remainder = hash_size % 4u; + if (remainder == 1u) + goto einval; + hash_size = quotient * 3u + (remainder ? remainder - 1u : 0u); } + /* For `librecrypt_crypt`: copy hash configurations to output */ if (action == ASCII_CRYPT) { + if (has_next) { + /* Include hash length specification */ + prefix = n; + } min = size ? size - 1u < prefix ? size - 1u : prefix : 0u; size -= min; memcpy(out_buffer, settings, min); @@ -86,9 +169,10 @@ next: ret += prefix; } - if (size && phrase_scratch_sizes[phrase_scratch_i] < algo->hash_size) { + /* Unless output is fully truncated, ensure scratch for intermediate hash is large enough */ + if (size && phrase_scratch_sizes[phrase_scratch_i] < hash_size) { librecrypt_wipe(phrase_scratches[phrase_scratch_i], phrase_scratch_sizes[phrase_scratch_i]); - new = realloc(phrase_scratches[phrase_scratch_i], algo->hash_size); + new = realloc(phrase_scratches[phrase_scratch_i], hash_size); if (!new) { free(phrase_scratches[phrase_scratch_i]); phrase_scratches[phrase_scratch_i] = NULL; @@ -96,42 +180,57 @@ next: goto fail; } phrase_scratches[phrase_scratch_i] = new; - phrase_scratch_sizes[phrase_scratch_i] = algo->hash_size; + phrase_scratch_sizes[phrase_scratch_i] = hash_size; } + /* Calculate intermediate or final hash */ if (has_next) { + /* Intermediate hash: write to scratch */ hash_to_scratch: r = (*algo->hash)(size ? phrase_scratches[phrase_scratch_i] : NULL, size ? phrase_scratch_sizes[phrase_scratch_i] : 0u, - phrase, len, settings, prefix, reserved); + phrase, len, settings, n, reserved); } else if (action == BINARY_HASH) { + /* Final hash in binary: write immediate to output */ hash_to_output: - r = (*algo->hash)(out_buffer, size, phrase, len, settings, prefix, reserved); - } else if (size < algo->hash_size) { + r = (*algo->hash)(out_buffer, size, phrase, len, settings, n, reserved); + } else if (size < hash_size) { + /* Final hash in ASCII: write to scratch if output is truncated, + * because it will be converted to ASCII later */ goto hash_to_scratch; } else { + /* Final hash in ASCII: write immedate to output if it fits, + * will be converted to ASCII later */ goto hash_to_output; } if (r < 0) goto fail; + /* Maybe convert to ASCII and get hash size */ if (!has_next) { + /* Final hash: */ if (action == BINARY_HASH) { - ret += algo->hash_size; + /* Binary output: we already have the has in binary, + * so yes add the length to the return value */ + ret += hash_size; } else if (!size) { - ascii_len = algo->hash_size % 3u; + /* ASCII hash but not output: just calculate the + * ASCII length and add it to the return value */ + ascii_len = hash_size % 3u; if (ascii_len) { if (algo->pad && algo->strict_pad) - ascii_len = 4u; + ascii_len = 4u; /* padding to for bytes */ else - ascii_len += 1u; + ascii_len += 1u; /* 3n+m bytes: 4n+m+1 chars, unless m=0 */ } - ascii_len += algo->hash_size / 3u * 4u; + ascii_len += hash_size / 3u * 4u; goto include_ascii; } else { + /* ASCII output: convert from binary to ASCII, + * and add ASCII length to the return value */ ascii_len = librecrypt_encode(out_buffer, size, - size < algo->hash_size ? phrase_scratches[phrase_scratch_i] : out_buffer, - algo->hash_size, algo->encoding_lut, algo->strict_pad ? algo->pad : '\0'); + size < hash_size ? phrase_scratches[phrase_scratch_i] : out_buffer, + hash_size, algo->encoding_lut, algo->strict_pad ? algo->pad : '\0'); include_ascii: min = size ? size - 1u < ascii_len ? size - 1u : ascii_len : 0u; out_buffer = &out_buffer[min]; @@ -139,11 +238,21 @@ next: ret += ascii_len; } } else { + /* Intermediate hash: */ + + /* Swap scratches, so that the intermediate output + * becomes the next algorithm's input, but use NULL if output is + * truncated (measure only) */ phrase = size ? phrase_scratches[phrase_scratch_i] : NULL; phrase_scratch_i ^= 1; - len = algo->hash_size; + len = hash_size; + /* Seek past the algorithm settings */ settings = &settings[n]; + /* and the '>' */ + settings++; + + /* For `librecrypt_crypt`: add '>' to the password hash string */ if (action == ASCII_CRYPT) { ret += 1u; if (size) { @@ -151,10 +260,12 @@ next: size -= 1u; } } - settings++; + + /* Calculate the hash, from the intermediate output */ goto next; } + /* Erase and deallocate scratch memory */ librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]); librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]); librecrypt_wipe_str(settings_scratch); @@ -162,10 +273,14 @@ next: free(phrase_scratches[1u]); free(settings_scratch); + /* NUL-terminate output if it is a string (`out_buffer` is offset at every write to it) */ if (size && action != BINARY_HASH) out_buffer[0] = '\0'; + return (ssize_t)ret; +einval: + errno = EINVAL; fail: librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]); librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]); |
