aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore16
-rw-r--r--LICENSE15
-rw-r--r--Makefile126
-rw-r--r--algorithms.h25
-rw-r--r--common.h426
-rw-r--r--config.mk8
-rw-r--r--librecrypt.h776
-rw-r--r--librecrypt_add_algorithm.3134
-rw-r--r--librecrypt_add_algorithm.c96
-rw-r--r--librecrypt_algorithms_.c69
-rw-r--r--librecrypt_chain_length.369
-rw-r--r--librecrypt_chain_length.c27
-rw-r--r--librecrypt_crypt.3131
-rw-r--r--librecrypt_crypt.c26
-rw-r--r--librecrypt_decode.3104
-rw-r--r--librecrypt_decode.c257
-rw-r--r--librecrypt_decompose_chain.398
-rw-r--r--librecrypt_decompose_chain.c153
-rw-r--r--librecrypt_decompose_chain1.375
-rw-r--r--librecrypt_decompose_chain1.c37
-rw-r--r--librecrypt_encode.393
-rw-r--r--librecrypt_encode.c177
-rw-r--r--librecrypt_equal.370
-rw-r--r--librecrypt_equal.c39
-rw-r--r--librecrypt_equal_binary.365
-rw-r--r--librecrypt_equal_binary.c77
-rw-r--r--librecrypt_fill_with_random_.c142
-rw-r--r--librecrypt_find_first_algorithm_.c41
-rw-r--r--librecrypt_get_encoding.3112
-rw-r--r--librecrypt_get_encoding.c46
-rw-r--r--librecrypt_hash.3135
-rw-r--r--librecrypt_hash.c26
-rw-r--r--librecrypt_hash_.c187
-rw-r--r--librecrypt_hash_binary.3138
-rw-r--r--librecrypt_hash_binary.c26
-rw-r--r--librecrypt_make_settings.3177
-rw-r--r--librecrypt_make_settings.c50
-rw-r--r--librecrypt_next_algorithm.396
-rw-r--r--librecrypt_next_algorithm.c82
-rw-r--r--librecrypt_realise_salts.3161
-rw-r--r--librecrypt_realise_salts.c151
-rw-r--r--librecrypt_rng_.c146
-rw-r--r--librecrypt_settings_prefix.380
-rw-r--r--librecrypt_settings_prefix.c34
-rw-r--r--librecrypt_test_supported.398
-rw-r--r--librecrypt_test_supported.c59
-rw-r--r--librecrypt_wipe.357
-rw-r--r--librecrypt_wipe.c65
-rw-r--r--librecrypt_wipe_str.361
-rw-r--r--librecrypt_wipe_str.c39
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
53 files changed, 5416 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bb11397
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*\#*
+*~
+*.o
+*.a
+*.t
+*.lo
+*.to
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1634eae
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2026 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..549b8fb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,126 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = recrypt
+
+
+OBJ_PUBLIC =\
+ librecrypt_settings_prefix.o\
+ librecrypt_chain_length.o\
+ librecrypt_decompose_chain.o\
+ librecrypt_decompose_chain1.o\
+ librecrypt_next_algorithm.o\
+ librecrypt_encode.o\
+ librecrypt_decode.o\
+ librecrypt_get_encoding.o\
+ librecrypt_wipe.o\
+ librecrypt_wipe_str.o\
+ librecrypt_equal_binary.o\
+ librecrypt_equal.o\
+ librecrypt_realise_salts.o\
+ librecrypt_make_settings.o\
+ librecrypt_hash_binary.o\
+ librecrypt_hash.o\
+ librecrypt_crypt.o\
+ librecrypt_add_algorithm.o\
+ librecrypt_test_supported.o
+
+OBJ_PRIVATE =\
+ librecrypt_hash_.o\
+ librecrypt_rng_.o\
+ librecrypt_fill_with_random_.o\
+ librecrypt_find_first_algorithm_.o
+
+OBJ = $(OBJ_PUBLIC) $(OBJ_PRIVATE)
+
+HDR =\
+ librecrypt.h\
+ common.h\
+ algorithms.h
+
+LOBJ = $(OBJ:.o=.lo)
+TOBJ = $(OBJ:.o=.to)
+TEST = $(OBJ:.o=.t)
+MAN3 = $(OBJ_PUBLIC:.o=.3)
+MAN7 = librecrypt.7
+
+
+all: librecrypt.a librecrypt.$(LIBEXT) $(TEST)
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+$(TOBJ): $(HDR)
+$(TEST): librecrypt.a
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.to:
+ $(CC) -DTEST -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.to.t:
+ $(CC) -o $@ $< librecrypt.a $(LDFLAGS)
+
+.c.t:
+ $(CC) -DTEST -o $@ $< librecrypt.a $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
+
+librecrypt.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+ $(AR) ts $@ > /dev/null
+
+librecrypt.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+check: $(TEST)
+ @set -ex;\
+ for t in $(TEST); do\
+ $(CHECK_PREFIX) ./$$t;\
+ done
+
+install: librecrypt.a librecrypt.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man3"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man7"
+ cp -- librecrypt.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- librecrypt.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMINOREXT)"
+ $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMINOREXT)"
+ ln -sf -- librecrypt.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMAJOREXT)"
+ ln -sf -- librecrypt.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBEXT)"
+ cp -- librecrypt.h "$(DESTDIR)$(PREFIX)/include/"
+ cp -P -- $(MAN3) "$(DESTDIR)$(MANPREFIX)/man3/"
+ cp -P -- $(MAN7) "$(DESTDIR)$(MANPREFIX)/man7/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/librecrypt.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/librecrypt.h"
+ -cd -- "$(DESTDIR)$(MANPREFIX)/man3/" && rm -f -- $(MAN3)
+ -cd -- "$(DESTDIR)$(MANPREFIX)/man7/" && rm -f -- $(MAN7)
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT)
+ -rm -f -- *.to *.t
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c .to .t
+
+.PHONY: all check install uninstall clean
diff --git a/algorithms.h b/algorithms.h
new file mode 100644
index 0000000..d38dcf5
--- /dev/null
+++ b/algorithms.h
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+/* included from "common.h" */
+
+
+/* ordered by preference */
+#define LIST_ALGORITHMS(X) /* TODO add algorithms */
+
+
+#define Y(ALGO)\
+ HIDDEN size_t librecrypt__##ALGO##__get_prefix(const char *settings, size_t len);\
+ HIDDEN unsigned librecrypt__##ALGO##__is_algorithm(const char *settings, size_t len);\
+ HIDDEN int librecrypt__##ALGO##__hash(char *restrict out_buffer, size_t size, const char *phrase,\
+ size_t len, const char *settings, size_t prefix, void *reserved);\
+ HIDDEN int librecrypt__##ALGO##__test_supported(const char *phrase, size_t len, int text,\
+ const char *settings, size_t prefix);\
+ HIDDEN ssize_t librecrypt__##ALGO##__make_settings(char *out_buffer, size_t size, const char *algorithm,\
+ size_t memcost, uintmax_t timecost, int gensalt,\
+ ssize_t (*rng)(void *out, size_t n, void *user), void *user);\
+ NONSTRING extern const char librecrypt__##ALGO##__encoding_lut[256];\
+ extern const unsigned char librecrypt__##ALGO##__decoding_lut[256];
+
+#define X(ALGO) IF__##ALGO##__SUPPORTED(Y(ALGO))
+LIST_ALGORITHMS(X)
+#undef X
+#undef Y
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..389244f
--- /dev/null
+++ b/common.h
@@ -0,0 +1,426 @@
+/* See LICENSE file for copyright and license details. */
+#include "librecrypt.h"
+#if defined(__linux__)
+# include <sys/auxv.h>
+# include <sys/random.h>
+#endif
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "algorithms.h"
+
+
+#if defined(__GNUC__)
+# define HIDDEN __attribute__((__visibility__("hidden")))
+# define CONST __attribute__((__const__))
+#else
+# define HIDDEN
+# define CONST
+#endif
+
+#define NONSTRING
+#if defined(__GNUC__) && !defined(__clang__)
+# if __GNUC__ >= 8
+# undef NONSTRING
+# define NONSTRING __attribute__((__nonstring__))
+# endif
+#endif
+
+
+/**
+ * Used for literal commas in macro calls
+ */
+#define COMMA ,
+
+/**
+ * Get the number of elements in an array
+ *
+ * @param ARRAY:nondecayed-array The array
+ * @return :size_t The number of elements in `ARRAY`
+ */
+#define ELEMSOF(ARRAY) (sizeof(ARRAY) / sizeof(*(ARRAY)))
+
+
+/**
+ * Which function `librecrypt_hash_` shall behave as
+ */
+enum action {
+ /**
+ * Used for `librecrypt_hash_binary`
+ *
+ * Output is binary and hash result-only
+ */
+ BINARY_HASH,
+
+ /**
+ * Used for `librecrypt_hash`
+ *
+ * Output is ASCII and hash result-only
+ */
+ ASCII_HASH,
+
+ /**
+ * Used for `librecrypt_hash`
+ *
+ * Output is ASCII and contains algorithm configurations
+ */
+ ASCII_CRYPT
+};
+
+
+/**
+ * Hash algorithm information and implementation
+ */
+struct algorithm {
+ /**
+ * Get the number of bytes that constitute
+ * the algorithm specification and configuration
+ *
+ * @param settings The password hash string, containing a single algorithm
+ * @param len The number of bytes in `settings`
+ * @return `len` if the string does not contain any hash result,
+ * otherwise the offset of `settings` where the hash
+ * result begins
+ *
+ * This function shall be MT-Safe and AS-Safe
+ */
+ size_t (*get_prefix)(const char *settings, size_t len);
+
+ /**
+ * Determine if a password hash string
+ * selects the algorithm
+ *
+ * @param settings The password hash string, containing a single algorithm
+ * @param len The number of bytes in `settings`
+ * @return A positive value if the string matches the algorithm,
+ * 0 otherwise
+ *
+ * If non-zero is returned for multiple algorithm,
+ * the first with the highest value wins
+ *
+ * This function shall be MT-Safe and AS-Safe
+ */
+ unsigned (*is_algorithm)(const char *settings, size_t len);
+
+ /**
+ * Implements `librecrypt_hash_binary` for a single hash algorithm;
+ * see `librecrypt_hash_binary` for more information
+ *
+ * @param out_buffer See `librecrypt_hash_binary`
+ * @param size See `librecrypt_hash_binary`
+ * @param phrase See `librecrypt_hash_binary`; may be `NULL`
+ * even if `len` is positive, this happens when
+ * `size` is too small and the hash result will
+ * not be included, so there is no need to actually
+ * calculate the hash, however `len` and `settings`
+ * should still be checked
+ * @param len See `librecrypt_hash_binary`
+ * @param settings See `librecrypt_hash_binary`,
+ * will not contains asterisk-encoding
+ * @param prefix The length of `settings`, in bytes
+ * @param reserved See `librecrypt_hash_binary`
+ * @return 0 on success, -1 on failure
+ * @throws See `librecrypt_hash_binary`
+ *
+ * This function shall be MT-Safe but may be AS-Unsafe
+ */
+ int (*hash)(char *restrict out_buffer, size_t size, const char *phrase, size_t len,
+ const char *settings, size_t prefix, void *reserved);
+
+ /**
+ * Check whether the hash algorithm is supported for given
+ * configuration and input
+ *
+ * @param phrase The password to hash, may contain NUL bytes;
+ * may be `NULL` even if `len` is non-zero
+ * @param len The number of bytes in `phrase`, if `phrase` is `NULL`,
+ * the function will check that the specified number of
+ * bytes is supported as well as any byte sequence unless
+ * `text` is non-zero
+ * @param text Assume the password is valid UTF-8 text (without NUL bytes)
+ * iff non-zero; ignored if `phrase` is non-`NULL`
+ * @param settings The password hash string; it is allowed for algorithm
+ * tuning parameters, and the hash result, to be omitted
+ * @return 1 if the configuration is supported and correctly
+ * configured, 0 otherwise
+ *
+ * This function shall be MT-Safe and AS-Safe
+ */
+ int (*test_supported)(const char *phrase, size_t len, int text, const char *settings, size_t prefix);
+
+ /**
+ * See `librecrypt_make_settings`
+ *
+ * @param out_buffer See `librecrypt_make_settings`
+ * @param size See `librecrypt_make_settings`
+ * @param algorithm See `librecrypt_make_settings`,
+ * will match the algorithm or be `NULL`
+ * @param memcost See `librecrypt_make_settings`
+ * @param timecost See `librecrypt_make_settings`
+ * @param gensalt See `librecrypt_make_settings`
+ * @param rng See `librecrypt_make_settings`
+ * @param user See `librecrypt_make_settings`
+ * @return See `librecrypt_make_settings`
+ * @throws See `librecrypt_make_settings`
+ *
+ * This function shall be MT-Safe but may be AS-Safe
+ */
+ ssize_t (*make_settings)(char *out_buffer, size_t size, const char *algorithm, size_t memcost, uintmax_t timecost,
+ int gensalt, ssize_t (*rng)(void *out, size_t n, void *user), void *user);
+
+ /**
+ * Expected argument for the `lut` parameter
+ * of the `librecrypt_encode` function
+ */
+ const char *encoding_lut;
+
+ /**
+ * Expected argument for the `lut` parameter
+ * of the `librecrypt_decode` function
+ */
+ const unsigned char *decoding_lut;
+
+ /**
+ * The algoritm's hash result size, in number
+ * of bytes when using binary encoding
+ */
+ size_t hash_size;
+
+ /**
+ * Expected argument for the `strict_pad` parameter
+ * of the `librecrypt_decode` function
+ */
+ int strict_pad;
+
+ /**
+ * Expected argument for the `pad` parameter
+ * of the `librecrypt_decode` function
+ */
+ char pad;
+};
+
+/**
+ * Check if an `struct algorithm *` is the `END_OF_ALGORITHMS`
+ * at the end of `librecrypt_algorithms_`
+ */
+#define IS_END_OF_ALGORITHMS(A) (!(A)->get_prefix)
+
+/**
+ * Used at the end of `librecrypt_algorithms_`
+ */
+#define END_OF_ALGORITHMS {NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, '\0'}
+
+/**
+ * Create a concatenation of `ALPHABET` repeated
+ * for times; used to convert a base-64 alphabet
+ * to an encoding lookup table
+ *
+ * @param ALPHABET:string-literal A 64-character string
+ * @return :string-literal A 256-character string
+ *
+ * @seealso NONSTRING
+ */
+#define MAKE_ENCODING_LUT(ALPHABET) ALPHABET ALPHABET ALPHABET ALPHABET
+
+/**
+ * Use in algorithms's base-64 decoding lookup tables
+ * in entries matching the pad character and invalid
+ * characters
+ */
+#define XX 0xFFu
+
+
+/**
+ * The list of all supported algorithms (those not disabled
+ * at compile-time)
+ *
+ * They are ordered by how preferable they are as the
+ * default algorithm, with the most preferable first
+ *
+ * The list is terminated by `END_OF_ALGORITHMS`,
+ * which can be checked using `IS_END_OF_ALGORITHMS`
+ */
+extern struct algorithm librecrypt_algorithms_[];
+
+/**
+ * This just points to memset(3), but the pointer is volalite
+ * so that the compiler cannot assume that, and therefore the
+ * call cannot be optimised away
+ */
+extern void *(*volatile librecrypt_explicit_memset_____)(void *, int, size_t); /* librecrypt_wipe.c */
+
+/**
+ * The is a pointer to a function that doesn't do anything,
+ * however the pointer is volatile so that the function
+ * cannot assume that and cannot optimise away the calculating
+ * of the input value
+ */
+extern void (*volatile librecrypt_explicit_____)(unsigned char); /* librecrypt_equal_binary.c */
+
+
+/**
+ * This function implements `librecrypt_hash_binary`, `librecrypt_hash`,
+ * and `librecrypt_test_supported`, depending on the value of `action`,
+ * see documentation of those functions for more default information
+ *
+ * @param out_buffer Output buffer for the hash result
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param phrase The password to hash, may contain NUL bytes
+ * @param len The number of bytes in `phrase`
+ * @param settings The password hash configuration string,
+ * may contain resulting hash, which will be ignored
+ * @param reserved Reserved for future use, should be `NULL`
+ * @param action The function this function shall implement
+ * @return The number of bytes that would have been written to `out_buffer`
+ * if `size` was sufficiently large, excluding a terminating
+ * NUL byte (not present if `action == BINARY_HASH`);
+ * -1 on failure
+ *
+ * This function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4)
+LIBRECRYPT_READ_STR__(6) LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__ HIDDEN
+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);
+
+
+/**
+ * Default random number generator, used if no other
+ * is specified
+ *
+ * Generates up to `n`, or {SSIZE_MAX} if `n` is
+ * greater than {SSIZE_MAX}, random bytes
+ *
+ * @param out Output buffer for the random bytes
+ * @param n The maximum number of bytes to generate
+ * @param user Not used
+ * @return The number of generated bytes;
+ * will always be positive
+ *
+ * This function cannot fail, however it will ignore
+ * any `EINTR`; if `EINTR` is encountered `errno` will
+ * be set to `EINTR` upon return, otherwise `errno`
+ * will remain unmodifed
+ *
+ * This function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_WUR__ HIDDEN
+ssize_t librecrypt_rng_(void *out, size_t n, void *user);
+
+
+/**
+ * Generate a specific number of random bytes
+ *
+ * @param out Output buffer for the random bytes
+ * @param n The number of bytes to generate
+ * @param rng The random number generator to use,
+ * `librecrypt_rng_` will be used if `NULL`,
+ * see `librecrypt_rng_` details, but not
+ * that it may return -1 on failure and has
+ * no restrictions on modifying `errno`
+ * @param user Passed as is to `*rng` as its third argument,
+ * so that it can be used for user-defined
+ * purposes
+ * @return 0 on success, -1 on failure
+ *
+ * When `rng` is non-`NULL`, this function inherits any
+ * MT-Unsafe and AS-Unsafe properties from `*rng`, being
+ * is MT-Safe and AS-Safe as a baseline; however when
+ * `rng` is `NULL`, this function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_WUR__ HIDDEN
+int librecrypt_fill_with_random_(void *out, size_t n, ssize_t (*rng)(void *out, size_t n, void *user), void *user);
+
+
+/**
+ * Find the first algorithm specified by a password has string
+ *
+ * @param settings The password has string
+ * @param len The number of bytes in `settings`
+ * @return Pointer to the algorithm information,
+ * `NULL` if not found
+ *
+ * This function does not modify `errno`, but the
+ * caller should usually set `errno` to `ENOSYS`
+ * if this function returns `NULL`
+ *
+ * This function is MT-Safe And AS-Safe
+ */
+LIBRECRYPT_READ_MEM__(1, 2) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ HIDDEN
+const struct algorithm *librecrypt_find_first_algorithm_(const char *settings, size_t len);
+
+
+
+#ifdef TEST
+# ifdef __linux__
+# include <sys/prctl.h>
+# endif
+# include <sys/resource.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <assert.h>
+# include <signal.h>
+# include <stdio.h>
+# include <string.h>
+# include <unistd.h>
+
+# define SET_UP_ALARM()\
+ do {\
+ unsigned int alarm_time__ = alarm(10u);\
+ if (alarm_time__ > 10u)\
+ alarm(alarm_time__);\
+ } while (0)
+
+# if defined(PR_SET_DUMPABLE)
+# define INIT_TEST_ABORT()\
+ do {\
+ struct rlimit rl__;\
+ rl__.rlim_cur = 0;\
+ rl__.rlim_max = 0;\
+ (void) setrlimit(RLIMIT_CORE, &rl__);\
+ (void) prctl(PR_SET_DUMPABLE, 0);\
+ EXPECT_ABORT(abort());\
+ } while (0)
+# else
+# define INIT_TEST_ABORT()\
+ do {\
+ struct rlimit rl__;\
+ rl__.rlim_cur = 0;\
+ rl__.rlim_max = 0;\
+ (void) setrlimit(RLIMIT_CORE, &rl__);\
+ EXPECT_ABORT(abort());\
+ } while (0)
+# endif
+
+# define EXPECT__(EXPR, HOW, RETEXTRACT, RETEXPECT)\
+ do {\
+ pid_t pid__;\
+ int status__;\
+ pid__ = fork();\
+ EXPECT(pid__ != -1);\
+ if (pid__ == 0) {\
+ (EXPR);\
+ _exit(0);\
+ }\
+ EXPECT(waitpid(pid__, &status__, 0) == pid__);\
+ EXPECT(HOW(status__));\
+ EXPECT(RETEXTRACT(status__) == RETEXPECT);\
+ } while (0)
+
+# define EXPECT_ABORT(EXPR)\
+ do {\
+ EXPECT__(EXPR, WIFSIGNALED, WTERMSIG, SIGABRT);\
+ } while (0)
+
+# define EXPECT(EXPR)\
+ do {\
+ if (!(EXPR)) {\
+ fprintf(stderr, "Failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\
+ exit(1);\
+ }\
+ } while (0)
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..f4adf12
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS =
diff --git a/librecrypt.h b/librecrypt.h
new file mode 100644
index 0000000..9cf966b
--- /dev/null
+++ b/librecrypt.h
@@ -0,0 +1,776 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBRECRYPT_H
+#define LIBRECRYPT_H
+
+#include <sys/types.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+
+#if defined(__GNUC__)
+# define LIBRECRYPT_PURE__ __attribute__((__pure__))
+# define LIBRECRYPT_NONNULL__ __attribute__((__nonnull__))
+# define LIBRECRYPT_NONNULL_I__(I) __attribute__((__nonnull__(I)))
+# define LIBRECRYPT_WUR__ __attribute__((__warn_unused_result__))
+# define LIBRECRYPT_READ_STR__(S) __attribute__((__access__(read_only, S)))
+# define LIBRECRYPT_READ_MEM__(B, N) __attribute__((__access__(read_only, B, N)))
+# define LIBRECRYPT_WRITE_MEM__(B, N) __attribute__((__access__(write_only, B, N)))
+# define LIBRECRYPT_READ_WRITE_STR__(S) __attribute__((__access__(read_write, S)))
+#else
+# define LIBRECRYPT_PURE__
+# define LIBRECRYPT_NONNULL__
+# define LIBRECRYPT_NONNULL_I__(I)
+# define LIBRECRYPT_WUR__
+# define LIBRECRYPT_READ_STR__(S)
+# define LIBRECRYPT_READ_MEM__(B, N)
+# define LIBRECRYPT_WRITE_MEM__(B, N)
+# define LIBRECRYPT_READ_WRITE_STR__(S)
+#endif
+#define LIBRECRYPT_NONNULL_1__ LIBRECRYPT_NONNULL_I__(1)
+
+
+/**
+ * Symbol used as a general delimiter
+ */
+#define LIBRECRYPT_HASH_COMPOSITION_DELIMITER '$'
+
+/**
+ * Symbol used to delimit algorithms in a chain
+ */
+#define LIBRECRYPT_ALGORITHM_LINK_DELIMITER '>'
+
+
+
+/**
+ * Get number of bytes in a password hash string
+ * that make up the algorithm configuration
+ *
+ * @param hash The password hash string; must not be `NULL`
+ * @return The number of bytes, from the front of `hash`,
+ * that make up the algorithm configuration; may be 0
+ *
+ * For the return value `r`, `&hash[r]` points to the
+ * hash result proper
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ LIBRECRYPT_PURE__
+inline size_t
+librecrypt_settings_prefix(const char *hash)
+{
+ size_t i, ret = 0u;
+
+ for (i = 0u; hash[i]; i++)
+ if (hash[i] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER || hash[i] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER)
+ ret = i + 1u;
+
+ return ret;
+}
+
+
+/**
+ * Get the number of chained hash algorithms specified in
+ * a password hash string
+ *
+ * @param hash The password hash string; must not be `NULL`
+ * @return The number of chained hash algorithms; always non-zero
+ *
+ * @seealso librecrypt_decompose_chain
+ * @seealso librecrypt_decompose_chain1
+ * @seealso librecrypt_next_algorithm
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__ LIBRECRYPT_PURE__
+inline size_t
+librecrypt_chain_length(const char *hash)
+{
+ size_t n = 1u;
+
+ for (; *hash; hash++)
+ if (*hash == LIBRECRYPT_ALGORITHM_LINK_DELIMITER)
+ n += 1u;
+
+ return n;
+}
+
+
+/**
+ * Decompose the chain of hash algorithms specified in
+ * a password hash string
+ *
+ * @param hash The password hash string to decompose; may be modified; must not be `NULL`
+ * @param chain_out_array Output array for the hash algorithms; each element will be an offset of `hash`
+ * @param size The number of elements `chain_out_buffer` fit
+ * @return The number of chained hashes (same as `librecrypt_chain_length(hash)`)
+ *
+ * Algorithms are delimited using `LIBRECRYPT_ALGORITHM_LINK_DELIMITER`
+ * (which is `'>'`). The first `size - 1u` occurences will be set to `NULL`.
+ * `hash` can be restored by setting the terminating NUL byte in each but
+ * the last string, that is stored to `chain_out_array`, to
+ * `LIBRECRYPT_ALGORITHM_LINK_DELIMITER`
+ *
+ * If positive `size` is smaller than the returned value,
+ * `chain_out_array[size - 1u]` will contain all algorithms
+ * (still delimited by `LIBRECRYPT_ALGORITHM_LINK_DELIMITER`)
+ * that where not placed in earlier slots
+ *
+ * Unless already stripped out before input,
+ * `chain_out_array[size - 1u]` (provided that `size` is
+ * positive) will end with the resulting hash
+ *
+ * @seealso librecrypt_chain_length
+ * @seealso librecrypt_decompose_chain1
+ * @seealso librecrypt_next_algorithm
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_WRITE_STR__(1) LIBRECRYPT_NONNULL_1__ LIBRECRYPT_WUR__
+inline size_t
+librecrypt_decompose_chain(char *hash, char **chain_out_array, size_t size)
+{
+ size_t n = 1u;
+
+ if (size)
+ chain_out_array[0u] = hash;
+
+ for (; *hash; hash++) {
+ if (*hash == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) {
+ if (n < size) {
+ *hash = '\0';
+ chain_out_array[n] = &hash[1u];
+ }
+ n += 1u;
+ }
+ }
+
+ return n;
+}
+
+
+/**
+ * Replace each `LIBRECRYPT_ALGORITHM_LINK_DELIMITER`
+ * (which is `'>'`) in a password hash string with NUL
+ * bytes, effectively decomposing it to one string per
+ * hash algorithm, with the hashing result still attached
+ * to the last one if it as in the string
+ *
+ * @param hash The password hash string to decompose; may be modified; must not be `NULL`
+ * @return The number of chained hashes (same as `librecrypt_chain_length(hash)`)
+ *
+ * @seealso librecrypt_decompose_chain
+ * @seealso librecrypt_next_algorithm
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_WRITE_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__
+inline size_t
+librecrypt_decompose_chain1(char *hash)
+{
+ size_t n = 1u;
+
+ for (; *hash; hash++) {
+ if (*hash == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) {
+ *hash = '\0';
+ n += 1u;
+ }
+ }
+
+ return n;
+}
+
+
+/**
+ * This function is called repeatedly to get each
+ * hash algorithm (with parameters) that shall
+ * be chained together according to a provided
+ * hash string
+ *
+ * @param hash On the initial call, this argument shall point
+ * to the password hash string; on each call the
+ * function will update it to the current parsing
+ * state, meaning it will set `*hash` to point to
+ * an offset of `*hash` or point to `NULL`;
+ * additionally at each call, except on the final
+ * call, the string will be modified
+ * @return On the first call, this will be the first hash
+ * algorithm to use, and the second call it will
+ * be the second algorithm to use (if there is a
+ * second one), and so on. Once all algorithms have
+ * been extracted and returned, `NULL` is returned.
+ *
+ * Except once the function has returned `NULL`, overwriting
+ * the terminating NUL byte in the previous returned string
+ * with `LIBRECRYPT_ALGORITHM_LINK_DELIMITER` (which is `'>'`)
+ * will restore the password hash string to its original content
+ *
+ * Unless already stripped out before input, the last returned
+ * string will end with the resulting hash
+ *
+ * @seealso librecrypt_decompose_chain
+ * @seealso librecrypt_decompose_chain1
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__
+inline char *
+librecrypt_next_algorithm(char **hash)
+{
+ char *ret;
+
+ if (!*hash)
+ return NULL;
+
+ ret = *hash;
+ *hash = strchr(*hash, LIBRECRYPT_ALGORITHM_LINK_DELIMITER);
+ if (*hash)
+ *(*hash)++ = '\0';
+ return ret;
+}
+
+
+/**
+ * Encode a hash result or salt in ASCII, from binary
+ *
+ * This is the inverse of `librecrypt_decode`
+ *
+ * @param out_buffer Output buffer for the ASCII representation
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param binary The raw binary data to encode
+ * @param len The number of bytes in `binary`
+ * @param lut The encoding alphabet, consisting of 64 characters,
+ * repeated 4 times
+ * @param pad The padding character to use at the end; the NUL byte if none
+ * @return The number of bytes that would have been written to `out_buffer`,
+ * excluding the terminating NUL byte, if `size` was sufficiently large
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and 1 in
+ * excess of the return value (this count includes the
+ * terminating NUL byte)
+ *
+ * On successful completion, if `size` is positive,
+ * the output to `out_buffer` is NUL terminated even
+ * if this truncates the string
+ *
+ * @seealso librecrypt_get_encoding
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__
+size_t librecrypt_encode(char *out_buffer, size_t size, const void *binary, size_t len,
+ const char lut[restrict static 256], char pad);
+
+
+/**
+ * Encode a hash result or salt in ASCII, from binary
+ *
+ * This is the inverse of `librecrypt_decode`
+ *
+ * @param out_buffer Output buffer for the raw binary data
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param ascii The ASCII encoding of the data, the text to decode
+ * @param len The number of bytes in `ascii`
+ * @param lut Alphabet reverse lookup table, shall map any valid
+ * character (except the padding character) to the value
+ * of that character in the encoding alphabet, and map
+ * any other character to the value `0xFF`
+ * @param pad The padding character to used at the end; the NUL byte if none
+ * @param strict_pad Zero if the padding at the end is optional, non-zero otherwise
+ * @return The number of bytes that would have been written to `out_buffer`,
+ * if `size` was sufficiently large, -1 on failure
+ * @throws EINVAL `ascii` uses an invalid encoding
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and the
+ * return value
+ *
+ * @seealso librecrypt_get_encoding
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__
+ssize_t librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len,
+ const unsigned char lut[restrict static 256], char pad, int strict_pad);
+
+
+/**
+ * Get the ASCII (base-64) encoding used for the
+ * last algorithm in a chain
+ *
+ * @param settings The algorithm chain
+ * @param len The number of bytes in `settings`
+ * @param pad_out Output parameter for the padding character,
+ * will be set to the NUL byte if there is none
+ * @param strict_pad_out Set to 1 if encodings must be padded to a
+ * multiple of 4 bytes using `*pad_out` at the end,
+ * 0 otherwise; set arbitrarily if `*pad_out` is
+ * set to the NUL byte
+ * @param decoding 0 if the returned pointer should be useful for,
+ * `librecrypt_encode`, otherwise it will be useful
+ * for `librecrypt_decode`
+ * @return If `decoding` is 0:
+ * the encoding alphabet, consisting of 64 characters,
+ * repeated 4 times;
+ * otherwise:
+ * alphabet reverse lookup table, mapping any valid
+ * character (except the padding character) to the
+ * value of that character in the encoding alphabet,
+ * and any other character to the value `0xFF`;
+ * but `NULL` on failure
+ * @throws ENOSYS The last algorithm in `settings` is not recognised
+ * or was disabled at compile-time
+ *
+ * The return type is `const char *` if `decoding` is 0,
+ * and `const unsigned char *` otherwise
+ *
+ * `*pad_out` shall be used for the `pad` parameter of the `librecrypt_encode`
+ * and `librecrypt_decode` functions, however the NUL byte may also be used
+ * for the `librecrypt_encode` function if `*strict_pad_out` is set to 0
+ *
+ * `*strict_pad_out` shall be used for the `strict_pad` parameter of the
+ * `librecrypt_decode` function
+ *
+ * The returned pointer (unless `NULL`) shall be used for the `lut` parameter
+ * of the `librecrypt_encode` function if `decoding` is 0, and for the `lut`
+ * parameter of the `librecrypt_decode` function otherwise
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_STR__(1) LIBRECRYPT_NONNULL__ LIBRECRYPT_WUR__
+const void *librecrypt_get_encoding(const char *settings, size_t len, char *pad_out,
+ int *strict_pad_out, int decoding);
+
+
+/**
+ * Securely erase a memory buffer
+ *
+ * @param buffer The memory to erase
+ * @param size The number of bytes in `buffer` to erase
+ *
+ * @seealso librecrypt_wipe_str
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2)
+void librecrypt_wipe(void *buffer, size_t size);
+
+
+/**
+ * Securely erase a string
+ *
+ * @param string The string to erase
+ *
+ * @seealso librecrypt_wipe
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_WRITE_STR__(1)
+inline void
+librecrypt_wipe_str(char *string)
+{
+ if (string)
+ librecrypt_wipe(string, strlen(string));
+}
+
+
+/**
+ * Compare two memory segments in constant-time
+ *
+ * @param a One of the segments to compare
+ * @param b The other segments to compare
+ * @param len The number of bytes to compare
+ * @return 1 if the `a` and `b` are equal in their
+ * first `len` bytes, 0 otherwise
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_MEM__(1, 3) LIBRECRYPT_READ_MEM__(2, 3) LIBRECRYPT_WUR__
+int librecrypt_equal_binary(const void *a, const void *b, size_t len);
+
+
+/**
+ * Compare two strings in constant-time
+ *
+ * If the strings are of different length,
+ * it will stop immediately after measurement;
+ * this should not happen in the real world as
+ * this function is used to compare password
+ * hashes, whose length depends strictly on the
+ * configuration, not the input password
+ *
+ * @param a One of the strings to compare
+ * @param b The other string to compare
+ * @return 1 if the `a` and `b` are equal, 0 otherwise
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_STR__(1) LIBRECRYPT_READ_STR__(2) LIBRECRYPT_WUR__ LIBRECRYPT_NONNULL__
+inline int
+librecrypt_equal(const char *a, const char *b)
+{
+ size_t n = strlen(a);
+ size_t m = strlen(b);
+ return n == m && librecrypt_equal_binary(a, b, n);
+}
+
+
+/**
+ * Locate all asteriskes followed by a non-negative
+ * decimal number and replace each with ASCII-encded
+ * random bytes (as many bytes as the number specifies)
+ *
+ * @param out_buffer Output buffer for the ASCII representation
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param settings The string to replace the asterisk-encoding in
+ * (the asterisk-free version is written to `out_buffer`)
+ * @param rng Random number generating function, `NULL` can be used
+ * for a default cryptographic random number generator;
+ * the function shall generate between 1 and `n` (inclusively)
+ * bytes and write them to the front of `out`, and return
+ * the number of generated bytes, or -1 on failure
+ * @param user Passed as the third argument to `*rng` for user-defined
+ * purposes, ignored if `rng` is `NULL`
+ * @return The number of bytes that would have been written to `out_buffer`,
+ * if `size` was sufficiently large, -1 on failure
+ * @throws ERANGE The expected return value is greater than {SSIZE_MAX}
+ * @throws EINVAL Asterisk-encoding was used in an invalid context
+ * @throws ENOSYS `settings` contain an algorithm that is not recognised
+ * or was disabled at compile-time
+ *
+ * If `rng` is `NULL`, any encountered `EINTR` is ignored,
+ * however, if it is encountered `errno` will be set to `EINTR`,
+ * but `errno` will remain unmodified otherwise, except if
+ * the function fails due to one of the above listed reasons
+ *
+ * Failure can only happen through `*rng`, it is
+ * responsible for setting `errno` if desired, or
+ * because of the conditions listed above`
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and 1 in
+ * excess of the return value (this count includes the
+ * terminating NUL byte)
+ *
+ * On successful completion, if `size` is positive,
+ * the output to `out_buffer` is NUL terminated even
+ * if this truncates the string
+ *
+ * On failure, `out_buffer` may be partially written
+ *
+ * The encoding depend on the algorithm, which is why
+ * it can fail with `EINVAL` or `ENOSYS`
+ *
+ * When `rng` is non-`NULL`, this function inherits any
+ * MT-Unsafe and AS-Unsafe properties from `*rng`, being
+ * is MT-Safe and AS-Safe as a baseline; however when
+ * `rng` is `NULL`, this function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_STR__(3) LIBRECRYPT_WUR__
+ssize_t librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *settings,
+ ssize_t (*rng)(void *out, size_t n, void *user), void *user);
+
+
+/**
+ * Generate a password hash setting string
+ *
+ * @param out_buffer Output buffer for the ASCII representation
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param algorithm Hash algorithm string; it need not specify any parameters
+ * however any unspecified parameter that cannot be determined
+ * by `memcost` or `timecost` will be set to an arbitrary,
+ * valid value. If `NULL`, a default, strong algorithm is
+ * selected. Note that the empty string represents the
+ * deprecated DES algorithm. Algorithm chains are disallowed.
+ * @param memcost Approximate memory cost in bytes, ignored if not supported;
+ * a default value is selected if 0 is specified
+ * @param timecost Total time cost, in an arbitrary, algorithm-dependent,
+ * implementation-dependent, monotonic (may have flat sections)
+ * unit and scale that is approximately linear; a default value
+ * is selected if 0 is specified
+ * @param gensalt Zero if salts shall specified to be randomly generated
+ * (reusable password hash setting string),
+ * non-zero if random salts shall be included in the generated
+ * output (non-reusable password hash setting string)
+ * @param rng Random number generating function, `NULL` can be used
+ * for a default cryptographic random number generator;
+ * the function shall generate between 1 and `n` (inclusively)
+ * bytes and write them to the front of `out`, and return
+ * the number of generated bytes, or -1 on failure;
+ * ignored if `gensalt` is zero
+ * @param user Passed as the third argument to `*rng` for user-defined
+ * purposes, ignored if `rng` is `NULL` or if `gensalt` is zero
+ * @return The number of bytes that would have been written to
+ * `out_buffer`, if `size` was sufficiently large, -1 on failure
+ * @throws EINVAL `algorithm` represents a chain of algorithms
+ * @throws ENOSYS `algorithm` represents an algorithm that is not
+ * recognised or was disabled at compile-time
+ * @throws ENOSYS `algorithm` is `NULL` but all algorithms were disabled at
+ * compile-time
+ *
+ * If `rng` is `NULL`, any encountered `EINTR` is ignored,
+ * however, if it is encountered `errno` will be set to `EINTR`,
+ * but `errno` will remain unmodified otherwise, except if
+ * the function fails due to `EINVAL` or `ENOSYS`
+ *
+ * Failure can only happen through `*rng`, it is
+ * responsible for setting `errno` if desired, or
+ * because of the conditions for `EINVAL` or `ENOSYS`
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and 1 in
+ * excess of the return value (this count includes the
+ * terminating NUL byte)
+ *
+ * On successful completion, if `size` is positive,
+ * the output to `out_buffer` is NUL terminated even
+ * if this truncates the string
+ *
+ * On failure, `out_buffer` may be partially written
+ *
+ * This function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_STR__(3) LIBRECRYPT_WUR__
+ssize_t librecrypt_make_settings(char *out_buffer, size_t size, const char *algorithm,
+ size_t memcost, uintmax_t timecost, int gensalt,
+ ssize_t (*rng)(void *out, size_t n, void *user), void *user);
+
+
+/**
+ * Compute the hash of a password
+ *
+ * The hash will be stored in raw binary format without any settings
+ *
+ * @param out_buffer Output buffer for the hash result
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param phrase The password to hash, may contain NUL bytes
+ * @param len The number of bytes in `phrase`
+ * @param settings The password hash configuration string,
+ * may contain resulting hash, which will be ignored
+ * @param reserved Reserved for future use, should be `NULL`
+ * @return The number of bytes that would have been written to `out_buffer`
+ * if `size` was sufficiently large; -1 on failure
+ *
+ * @throws EINVAL `reserved` is non-`NULL` (this case will be removed
+ * once `reserved` as being used by the library)
+ * @throws EINVAL `settings` is invalid (invalid algorithm configuration,
+ * invalid configuration syntax, or the output from one
+ * chained hash algorithm cannot be input the next algorithm
+ * in the chain (either because of format or length issues))
+ * @throws EINVAL `settings` uses asterisk-encoding to specify random salts
+ * @throws ERANGE `len` is too large or too small for the the selected
+ * initial algorithm in the algorithm chain
+ * @throws ENOMEM Failed to allocate internal scratch memory
+ * @throws ENOSYS A selected hash algorithm is either not recognised
+ * disabled at compile-time
+ *
+ * Any encountered `EINTR` is ignored
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and the
+ * return value
+ *
+ * On failure, `out_buffer` will remain unmodified
+ *
+ * @seealso librecrypt_hash
+ * @seealso librecrypt_crypt
+ * @seealso librecrypt_test_supported
+ *
+ * This function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_READ_STR__(5)
+LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__
+ssize_t librecrypt_hash_binary(char *restrict out_buffer, size_t size, const char *phrase, size_t len,
+ const char *settings, void *reserved);
+
+
+/**
+ * Compute the hash of a password
+ *
+ * The hash will be encoded with `librecrypt_encode`
+ * but stored without any settings
+ *
+ * @param out_buffer Output buffer for the hash result
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param phrase The password to hash, may contain NUL bytes
+ * @param len The number of bytes in `phrase`
+ * @param settings The password hash configuration string,
+ * may contain resulting hash, which will be ignored
+ * @param reserved Reserved for future use, should be `NULL`
+ * @return The number of bytes that would have been written to `out_buffer`
+ * if `size` was sufficiently large, excluding a terminating
+ * NUL byte; -1 on failure
+ *
+ * @throws EINVAL `reserved` is non-`NULL` (this case will be removed
+ * once `reserved` as being used by the library)
+ * @throws EINVAL `settings` is invalid (invalid algorithm configuration,
+ * invalid configuration syntax, or the output from one
+ * chained hash algorithm cannot be input the next algorithm
+ * in the chain (either because of format or length issues))
+ * @throws EINVAL `settings` uses asterisk-encoding to specify random salts
+ * @throws ERANGE `len` is too large or too small for the the selected
+ * initial algorithm in the algorithm chain
+ * @throws ENOMEM Failed to allocate internal scratch memory
+ * @throws ENOSYS A selected hash algorithm is either not recognised
+ * disabled at compile-time
+ *
+ * Any encountered `EINTR` is ignored
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and 1 in
+ * excess of the return value (this count includes the
+ * terminating NUL byte)
+ *
+ * On successful completion, if `size` is positive,
+ * the output to `out_buffer` is NUL terminated even
+ * if this truncates the string
+ *
+ * On failure, `out_buffer` will remain unmodified
+ *
+ * @seealso librecrypt_hash_binary
+ * @seealso librecrypt_crypt
+ * @seealso librecrypt_test_supported
+ *
+ * This function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_READ_STR__(5)
+LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__
+ssize_t librecrypt_hash(char *restrict out_buffer, size_t size, const char *phrase, size_t len,
+ const char *settings, void *reserved);
+
+
+/**
+ * Compute the hash of a password
+ *
+ * The hash will be encoded with `librecrypt_encode`;
+ * and the settings will be included in the front
+ *
+ * @param out_buffer Output buffer for the hash result
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param phrase The password to hash, may contain NUL bytes
+ * @param len The number of bytes in `phrase`
+ * @param settings The password hash configuration string,
+ * may contain resulting hash, which will be ignored
+ * @param reserved Reserved for future use, should be `NULL`
+ * @return The number of bytes that would have been written to `out_buffer`
+ * if `size` was sufficiently large, excluding a terminating
+ * NUL byte; -1 on failure
+ *
+ * @throws EINVAL `reserved` is non-`NULL` (this case will be removed
+ * once `reserved` as being used by the library)
+ * @throws EINVAL `settings` is invalid (invalid algorithm configuration,
+ * invalid configuration syntax, or the output from one
+ * chained hash algorithm cannot be input the next algorithm
+ * in the chain (either because of format or length issues))
+ * @throws ERANGE `len` is too large or too small for the the selected
+ * initial algorithm in the algorithm chain
+ * @throws ENOMEM Failed to allocate internal scratch memory
+ * @throws ENOSYS A selected hash algorithm is either not recognised
+ * disabled at compile-time
+ *
+ * Any encountered `EINTR` is ignored
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and 1 in
+ * excess of the return value (this count includes the
+ * terminating NUL byte)
+ *
+ * On successful completion, if `size` is positive,
+ * the output to `out_buffer` is NUL terminated even
+ * if this truncates the string
+ *
+ * On failure, `out_buffer` may be partially written
+ *
+ * @seealso librecrypt_hash_binary
+ * @seealso librecrypt_hash
+ * @seealso librecrypt_test_supported
+ *
+ * This function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_MEM__(3, 4) LIBRECRYPT_READ_STR__(5)
+LIBRECRYPT_NONNULL_I__(5) LIBRECRYPT_WUR__
+ssize_t librecrypt_crypt(char *restrict out_buffer, size_t size, const char *phrase, size_t len,
+ const char *settings, void *reserved);
+
+
+/**
+ * Check whether a hash algorithm chain is supported,
+ * for the given input, and that each algorithm
+ * configuration is valid
+ *
+ * @param phrase The password to hash, may contain NUL bytes;
+ * may be `NULL` even if `len` is non-zero
+ * @param len The number of bytes in `phrase`, if `phrase` is `NULL`,
+ * the function will check that the specified number of
+ * bytes is supported as well as any byte sequence unless
+ * `text` is non-zero
+ * @param text Assume the password is valid UTF-8 text (without NUL bytes)
+ * iff non-zero; ignored if `phrase` is non-`NULL`
+ * @param settings The password hash string; it is allowed for algorithm
+ * tuning parameters, and the hash result, to be omitted
+ * @return 1 if the configuration is supported, 0 otherwise, which
+ * means part of the string is invalid: the algorithm does
+ * not exist, supported was disabled at compile-time, or an
+ * algorithm it does not support the output of the previous
+ * algorithm in the chain as input; additionally a small
+ * number of legacy algorithms require the passphrase to
+ * be within a specific length range or be valid text,
+ * if the selected word does not match such constraints
+ * for the first algorithm in the chain, 0 is returned
+ *
+ * This function is MT-Safe and AS-Safe
+ */
+LIBRECRYPT_READ_STR__(4) LIBRECRYPT_NONNULL_I__(4) LIBRECRYPT_WUR__
+int librecrypt_test_supported(const char *phrase, size_t len, int text, const char *settings);
+
+
+/**
+ * Chain togather another set of hash algorithms
+ *
+ * @param out_buffer Output buffer for the new password hash string
+ * @param size The number bytes the function may write to `out_buffer`
+ * @param augend THe existing password hash string; if it contains a
+ * hash result, a new hash result will be computed and
+ * any random number generation specification in `augment`
+ * will be realised
+ * @param augment Password hash setting string describing the additional
+ * hashing to perform; if it contains a hash result, that
+ * part will be ignored
+ * @param reserved Reserved for future use, should be `NULL`
+ * @return The number of bytes that would have been written to `out_buffer`
+ * if `size` was sufficiently large, excluding a terminating
+ * NUL byte; -1 on failure
+ *
+ * @throws EINVAL `reserved` is non-`NULL` (this case will be removed
+ * once `reserved` as being used by the library)
+ * @throws EINVAL `settings` is invalid (invalid algorithm configuration,
+ * invalid configuration syntax, or the output from one
+ * chained hash algorithm cannot be input the next algorithm
+ * in the chain (either because of format or length issues))
+ * @throws ERANGE `len` is too large or too small for the the selected
+ * initial algorithm in the algorithm chain
+ * @throws ENOMEM Failed to allocate internal scratch memory
+ * @throws ENOSYS A selected hash algorithm is either not recognised
+ * disabled at compile-time
+ *
+ * On successful completion, the N bytes is written to
+ * `out_buffer` where N is the lesser of `size` and 1 in
+ * excess of the return value (this count includes the
+ * terminating NUL byte)
+ *
+ * On successful completion, if `size` is positive,
+ * the output to `out_buffer` is NUL terminated even
+ * if this truncates the string
+ *
+ * On failure, `out_buffer` will remain unmodified
+ *
+ * This function is MT-Safe but AS-Unsafe
+ */
+LIBRECRYPT_WRITE_MEM__(1, 2) LIBRECRYPT_READ_WRITE_STR__(3) LIBRECRYPT_READ_STR__(4)
+LIBRECRYPT_NONNULL_I__(3) LIBRECRYPT_NONNULL_I__(4) LIBRECRYPT_WUR__
+ssize_t librecrypt_add_algorithm(char *out_buffer, size_t size, char *augend,
+ const char *restrict augment, void *reserved);
+
+
+#endif
diff --git a/librecrypt_add_algorithm.3 b/librecrypt_add_algorithm.3
new file mode 100644
index 0000000..77867e8
--- /dev/null
+++ b/librecrypt_add_algorithm.3
@@ -0,0 +1,134 @@
+.TH LIBRECRYPT_ADD_ALGORITHM 3 LIBRECRYPT
+.SH NAME
+librecrypt_add_algorithm - Append an algorithm chain to a password hash string
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+ssize_t \fBlibrecrypt_add_algorithm\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP,
+ char *\fIaugend\fP, const char *restrict \fIaugment\fP,
+ void *\fIreserved\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+Static linking may require additional flags
+depending on enabled hash algorithms.
+
+.SH DESCRIPTION
+The
+.BR librecrypt_add_algorithm ()
+function chains together another set of hash algorithms.
+.PP
+The
+.I augend
+argument is the existing password hash string.
+If it already contains a hash result,
+a new hash result will be computed and
+any random number generation specification in
+.I augment
+will be realised.
+.PP
+The
+.I augment
+argument is a password hash setting string describing
+the additional hashing to perform.
+If it contains a hash result, that part is ignored.
+.PP
+The
+.I reserved
+parameter is reserved for future use and should be
+.IR NULL .
+.PP
+On successful completion, if
+.I size
+is positive, the output is null byte-terminated even
+if truncated.
+.PP
+On failure,
+.I out_buffer
+remains unmodified.
+.PP
+The
+.I augend
+and
+.I augment
+parameters must not be
+.IR NULL .
+The
+.I out_buffer
+parameter may only be
+.I NULL
+if if
+.I size
+is 0.
+
+.SH RETURN VALUES
+The
+.BR librecrypt_add_algorithm ()
+function returns the number of bytes that would have
+been written to
+.IR out_buffer
+if
+.I size
+was sufficiently large, excluding the terminating
+null byte.
+On failure, -1 is returned and
+.I errno
+is set to describe the error.
+
+.SH ERRORS
+The
+.BR librecrypt_add_algorithm ()
+function will fail if:
+.TP
+.B EINVAL
+.I reserved
+is not
+.IR NULL .
+.TP
+.B EINVAL
+.I augend
+or
+.I augment
+is invalid.
+.TP
+.B ERANGE
+The selected initial algorithm requires a different password length.
+.TP
+.B ENOMEM
+Failed to allocate internal scratch memory.
+.TP
+.B ENOSYS
+A selected hash algorithm is not recognised or was disabled at compile-time.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_add_algorithm ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_add_algorithm ()
+T} Async-signal safety AS-Unsafe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_add_algorithm ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_crypt (3),
+.BR librecrypt_make_settings (3),
+.BR librecrypt_realise_salts (3)
diff --git a/librecrypt_add_algorithm.c b/librecrypt_add_algorithm.c
new file mode 100644
index 0000000..a2d8aa4
--- /dev/null
+++ b/librecrypt_add_algorithm.c
@@ -0,0 +1,96 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_add_algorithm(char *out_buffer, size_t size, char *augend, const char *restrict augment, void *reserved)
+{
+ size_t prefix1 = librecrypt_settings_prefix(augend);
+ size_t prefix2, min, ret, len, phraselen;
+ char *phrase, pad;
+ int strict_pad;
+ const unsigned char *lut;
+ ssize_t r;
+
+#define COPY_PREFIX()\
+ do {\
+ size -= 1u;\
+ min = prefix1 < size ? prefix1 : size;\
+ if (out_buffer != augend)\
+ memmove(out_buffer, augend, min);\
+ out_buffer = &out_buffer[min];\
+ size -= min;\
+ if (size) {\
+ *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER;\
+ size -= 1u;\
+ }\
+ } while (0)
+
+ if (!augend[prefix1]) {
+ prefix2 = librecrypt_settings_prefix(augment);
+ ret = prefix1 + 1u + prefix2;
+ if (size) {
+ COPY_PREFIX();
+ min = prefix2 < size ? prefix2 : size;
+ memcpy(out_buffer, augment, min);
+ out_buffer[min] = '\0';
+ }
+ return (ssize_t)ret;
+ }
+
+ if (size <= prefix1 + 2u) {
+ phrase = NULL;
+ phraselen = 0u;
+ } else {
+ lut = librecrypt_get_encoding(augend, strlen(augend), &pad, &strict_pad, 1);
+ if (!lut)
+ return -1;
+
+ len = strlen(&augend[prefix1]);
+ r = librecrypt_decode(NULL, 0, &augend[prefix1], len, lut, pad, strict_pad);
+ if (r <= 0) {
+ if (!r)
+ abort();
+ return -1;
+ }
+ phraselen = (size_t)r;
+ phrase = malloc(phraselen);
+ if (!phrase)
+ return -1;
+ if (librecrypt_decode(phrase, phraselen, &augend[prefix1], len, lut, pad, strict_pad) != r)
+ abort();
+ }
+
+ COPY_PREFIX();
+ ret = prefix1 + 1u;
+ r = librecrypt_crypt(out_buffer, size + 1u, phrase, phraselen, augment, reserved);
+ if (r <= 0) {
+ librecrypt_wipe(phrase, phraselen);
+ free(phrase);
+ if (!r)
+ abort();
+ return -1;
+ }
+ ret += (size_t)r;
+
+ librecrypt_wipe(phrase, phraselen);
+ free(phrase);
+ return (ssize_t)ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_algorithms_.c b/librecrypt_algorithms_.c
new file mode 100644
index 0000000..34c6b3f
--- /dev/null
+++ b/librecrypt_algorithms_.c
@@ -0,0 +1,69 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#define ENTRY(ALGO)\
+ {\
+ .get_prefix = &librecrypt__##ALGO##__get_prefix,\
+ .is_algorithm = &librecrypt__##ALGO##__is_algorithm,\
+ .hash = librecrypt__##ALGO##__hash,\
+ .test_supported = &librecrypt__##ALGO##__test_supported,\
+ .make_settings = &librecrypt__##ALGO##__make_settings,\
+ .encoding_lut = &librecrypt__##ALGO##__encoding_lut,\
+ .decoding_lut = &librecrypt__##ALGO##__decoding_lut,\
+ .hash_size = ALGO##__HASH_SIZE,\
+ .strict_pad = ALGO##__STRICT_PAD,\
+ .pad = ALGO##__PAD\
+ }
+
+
+#define X(ALGO) IF__##ALGO##__SUPPORTED(ENTRY(ALGO) COMMA)
+struct algorithm librecrypt_algorithms_[] = {
+ LIST_ALGORITHMS(X)
+ END_OF_ALGORITHMS
+};
+#undef X
+
+
+#else
+
+
+int
+main(void)
+{
+#define X(ALGO) IF__##ALGO##__SUPPORTED(+ 1u)
+ const size_t count = 0u LIST_ALGORITHMS(X);
+#undef X
+ size_t i;
+
+ SET_UP_ALARM();
+
+ for (i = 0u; i < count; i++) {
+ CHECK(!IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[i]));
+ CHECK(librecrypt_algorithms_[i].get_prefix != NULL);
+ CHECK(librecrypt_algorithms_[i].is_algorithm != NULL);
+ CHECK(librecrypt_algorithms_[i].hash != NULL);
+ CHECK(librecrypt_algorithms_[i].test_supported != NULL);
+ CHECK(librecrypt_algorithms_[i].make_settings != NULL);
+ CHECK(librecrypt_algorithms_[i].encoding_lut != NULL);
+ CHECK(librecrypt_algorithms_[i].decoding_lut != NULL);
+ CHECK(librecrypt_algorithms_[i].hash_size > 0u);
+ CHECK(librecrypt_algorithms_[i].strict_pad >= 0);
+ CHECK(librecrypt_algorithms_[i].strict_pad <= 1);
+ if (librecrypt_algorithms_[i].pad) {
+ CHECK(librecrypt_algorithms_[i].pad > ' ');
+ CHECK(librecrypt_algorithms_[i].pad < 0x7F);
+ CHECK(librecrypt_algorithms_[i].pad != LIBRECRYPT_HASH_COMPOSITION_DELIMITER);
+ CHECK(librecrypt_algorithms_[i].pad != LIBRECRYPT_ALGORITHM_LINK_DELIMITER);
+ CHECK(librecrypt_algorithms_[i].pad != '*');
+ }
+ }
+ assert(i == count);
+ CHECK(IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[count]));
+
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_chain_length.3 b/librecrypt_chain_length.3
new file mode 100644
index 0000000..eb0b1cc
--- /dev/null
+++ b/librecrypt_chain_length.3
@@ -0,0 +1,69 @@
+.TH LIBRECRYPT_CHAIN_LENGTH 3 LIBRECRYPT
+.SH NAME
+librecrypt_chain_length - Get number of algorithms in a chained password hash string
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+size_t \fBlibrecrypt_chain_length\fP(const char *\fIhash\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_chain_length ()
+function returns the number of hash algorithms
+specified in the chain contained in
+.IR hash .
+Algorithms are delimited by
+.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER
+(which is
+.BR "\(aq>\(aq" ).
+.PP
+.I hash
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_chain_length ()
+function returns the number of chained hash algorithms.
+The return value is always non-zero.
+
+.SH ERRORS
+The
+.BR librecrypt_chain_length ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_chain_length ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_chain_length ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_chain_length ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_decompose_chain (3),
+.BR librecrypt_decompose_chain1 (3),
+.BR librecrypt_next_algorithm (3)
diff --git a/librecrypt_chain_length.c b/librecrypt_chain_length.c
new file mode 100644
index 0000000..64b9472
--- /dev/null
+++ b/librecrypt_chain_length.c
@@ -0,0 +1,27 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline size_t librecrypt_chain_length(const char *hash);
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+ EXPECT(librecrypt_chain_length("") == 1u);
+ EXPECT(librecrypt_chain_length("a$b") == 1u);
+ EXPECT(librecrypt_chain_length(">") == 2u);
+ EXPECT(librecrypt_chain_length(">>") == 3u);
+ EXPECT(librecrypt_chain_length("a$b>c$d>e$f") == 3u);
+ EXPECT(librecrypt_chain_length("a$b>c$d>e$f>") == 4u);
+ EXPECT(librecrypt_chain_length(">a$b>c$d>e$f>") == 5u);
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_crypt.3 b/librecrypt_crypt.3
new file mode 100644
index 0000000..0237862
--- /dev/null
+++ b/librecrypt_crypt.3
@@ -0,0 +1,131 @@
+.TH LIBRECRYPT_CRYPT 3 LIBRECRYPT
+.SH NAME
+librecrypt_crypt - Compute password hash encoded in ASCII with settings prefix
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+ssize_t \fBlibrecrypt_crypt\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP,
+ const char *\fIphrase\fP, size_t \fIlen\fP,
+ const char *\fIsettings\fP, void *\fIreserved\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+Static linking may require additional flags
+depending on enabled hash algorithms.
+
+.SH DESCRIPTION
+The
+.BR librecrypt_crypt ()
+function computes the hash of a password.
+The resulting hash is encoded with
+.BR librecrypt_encode (3)
+and the settings string is included in the front
+of the output.
+.PP
+The password is provided in
+.IR phrase
+and can contain null bytes; its length is
+specified, in bytes, by
+.IR len .
+.PP
+The password hash configuration string is
+provided in
+.IR settings .
+If
+.I settings
+contains a resulting hash, it is ignored.
+.PP
+The
+.I reserved
+parameter is reserved for future use and
+should be
+.IR NULL .
+.PP
+On successful completion, if
+.I size
+is positive, the output is
+null byte-terminated even if truncated.
+.PP
+Any encountered
+.BR EINTR
+is ignored.
+.PP
+On failure,
+.I out_buffer
+may be partially written.
+.PP
+.I settings
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_crypt ()
+function returns the number of bytes that would
+have been written to
+.IR out_buffer
+if
+.I size
+was sufficiently large, excluding the terminating
+null byte. On failure, -1 is returned and
+.I errno
+is set to describe the error.
+
+.SH ERRORS
+The
+.BR librecrypt_crypt ()
+function will fail if:
+.TP
+.B EINVAL
+.I reserved
+is not
+.IR NULL .
+.TP
+.B EINVAL
+.I settings
+is invalid.
+.TP
+.B ERANGE
+.I len
+is too large or too small for the selected
+initial algorithm.
+.TP
+.B ENOMEM
+Failed to allocate internal scratch memory.
+.TP
+.B ENOSYS
+A selected hash algorithm is not recognised
+or was disabled at compile-time.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_crypt ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_crypt ()
+T} Async-signal safety AS-Unsafe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_crypt ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_hash_binary (3),
+.BR librecrypt_hash (3),
+.BR librecrypt_test_supported (3)
diff --git a/librecrypt_crypt.c b/librecrypt_crypt.c
new file mode 100644
index 0000000..6192650
--- /dev/null
+++ b/librecrypt_crypt.c
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_crypt(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved)
+{
+ return librecrypt_hash_(out_buffer, size, phrase, len, settings, reserved, ASCII_CRYPT);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_decode.3 b/librecrypt_decode.3
new file mode 100644
index 0000000..cab9aad
--- /dev/null
+++ b/librecrypt_decode.3
@@ -0,0 +1,104 @@
+.TH LIBRECRYPT_DECODE 3 LIBRECRYPT
+.SH NAME
+librecrypt_decode - Decode ASCII encoding of a salt or hash result into binary
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+ssize_t \fBlibrecrypt_decode\fP(void *\fIout_buffer\fP, size_t \fIsize\fP,
+ const char *\fIascii\fP, size_t \fIlen\fP,
+ const unsigned char \fIlut\fP[restrict static 256],
+ char \fIpad\fP, int \fIstrict_pad\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_decode ()
+function decodes
+.I len
+bytes of ASCII text from
+.IR ascii
+into raw binary data written to
+.IR out_buffer .
+This is used for decoding hash results and salts.
+.PP
+The
+.I lut
+argument is a reverse lookup table that maps any
+valid character (except the padding character) to
+its value in the encoding alphabet and maps any
+invalid character to
+.BR 0xFFu .
+.PP
+The
+.I pad
+argument specifies the padding character to use
+at the end, or the null byte if none.
+.PP
+If
+.I strict_pad
+is non-zero, the padding at the end is mandatory.
+.PP
+On successful completion, up to
+.I size
+bytes are written to
+.IR out_buffer .
+The return value is the number of bytes that would
+have been written if
+.I size
+was sufficiently large.
+
+.SH RETURN VALUES
+The
+.BR librecrypt_decode ()
+function returns the number of bytes that would
+have been written to
+.IR out_buffer
+if
+.I size
+was sufficiently large.
+On failure, -1 is returned and
+.IR errno
+is set to describe the error.
+
+.SH ERRORS
+The
+.BR librecrypt_decode ()
+function will fail if:
+.TP
+.B EINVAL
+.I ascii
+uses an invalid encoding.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_decode ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_decode ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_decode ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_encode (3),
+.BR librecrypt_get_encoding (3)
diff --git a/librecrypt_decode.c b/librecrypt_decode.c
new file mode 100644
index 0000000..ee451a7
--- /dev/null
+++ b/librecrypt_decode.c
@@ -0,0 +1,257 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len,
+ const unsigned char lut[restrict static 256], char pad, int strict_pad)
+{
+ const unsigned char *str = (const unsigned char *)ascii;
+ unsigned char *data = out_buffer;
+ unsigned char a, b, c, d;
+ size_t n = 0u;
+
+ for(; len >= 4u; str += 4u) {
+ len -= 4u;
+
+ a = lut[str[0u]];
+ b = lut[str[1u]];
+ if ((a | b) == 0xFFu)
+ goto einval;
+ if (n < size)
+ data[n] = (unsigned char)((a << 2) | (b >> 4));
+ n++;
+
+ c = lut[str[2u]];
+ if (c == 0xFFu) {
+ if (len || !pad || str[2u] != pad || str[3u] != pad)
+ goto einval;
+ break;
+ }
+ if (n < size)
+ data[n] = (unsigned char)((b << 4u) | (c >> 2u));
+ n++;
+
+ d = lut[str[3u]];
+ if (d == 0xFFu) {
+ if (len || !pad || str[3u] != pad)
+ goto einval;
+ break;
+ }
+ if (n < size)
+ data[n] = (unsigned char)((c << 6) | (d >> 0));
+ n++;
+ }
+
+ if (len && strict_pad && pad)
+ goto einval;
+
+ switch (len) {
+ case 0u:
+ goto out;
+ case 1u:
+ goto einval;
+
+ default:
+ a = lut[str[0u]];
+ b = lut[str[1u]];
+ if ((a | b) == 0xFFu)
+ goto einval;
+ if (n < size)
+ data[n] = (unsigned char)((a << 2) | (b >> 4));
+ n++;
+ if (len == 2u)
+ break;
+
+ c = lut[str[2u]];
+ if (c == 0xFFu) {
+ if (!pad || str[2u] != pad)
+ goto einval;
+ break;
+ }
+ if (n < size)
+ data[n] = (unsigned char)((b << 4u) | (c >> 2u));
+ n++;
+ break;
+ }
+
+out:
+ return (ssize_t)n;
+
+einval:
+ errno = EINVAL;
+ return -1;
+}
+
+
+#else
+
+
+static const unsigned char lut[256u] = {
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, 62, XX, XX, XX, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, XX, XX, XX, XX, XX, XX,
+ XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, XX, XX, XX, XX, XX,
+ XX, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX,
+ XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX, XX
+};
+
+
+#define CHECK(BINARY, ASCII, PAD)\
+ check((BINARY), sizeof(BINARY) - 1u, (ASCII PAD), sizeof(ASCII) - 1u, sizeof(ASCII PAD) - 1u)
+
+
+static void
+check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_len, size_t padded_len)
+{
+ char buf[16u];
+ size_t i, j;
+
+ assert(binary_len <= sizeof(buf));
+
+ assert(padded_len % 4u == 0u);
+ assert(padded_len / 4u == (unpadded_len + 3u) / 4u);
+ assert(padded_len >= unpadded_len);
+
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len);
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 1) == (ssize_t)binary_len);
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 0) == (ssize_t)binary_len);
+
+ if (padded_len == unpadded_len) {
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == (ssize_t)binary_len);
+ } else {
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 0) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 1) == -1);
+ EXPECT(errno == EINVAL);
+ }
+
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 0) == (ssize_t)binary_len);
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len);
+
+ for (i = 0u; i < sizeof(buf); i++) {
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len);
+ j = i < binary_len ? i : binary_len;
+ EXPECT(!memcmp(buf, binary, j));
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '\0', 1) == (ssize_t)binary_len);
+ j = i < binary_len ? i : binary_len;
+ EXPECT(!memcmp(buf, binary, j));
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '=', 0) == (ssize_t)binary_len);
+ j = i < binary_len ? i : binary_len;
+ EXPECT(!memcmp(buf, binary, j));
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+
+ if (padded_len == unpadded_len) {
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '=', 1) == (ssize_t)binary_len);
+ j = i < binary_len ? i : binary_len;
+ EXPECT(!memcmp(buf, binary, j));
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+ } else {
+ errno = 0;
+ EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '=', 1) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '\0', 0) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '\0', 1) == -1);
+ EXPECT(errno == EINVAL);
+ }
+
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 0) == (ssize_t)binary_len);
+ j = i < binary_len ? i : binary_len;
+ EXPECT(!memcmp(buf, binary, j));
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len);
+ j = i < binary_len ? i : binary_len;
+ EXPECT(!memcmp(buf, binary, j));
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+
+ }
+}
+
+
+int
+main(void)
+{
+ int i;
+
+ SET_UP_ALARM();
+
+ CHECK("", "", "");
+ CHECK("\x00", "AA", "==");
+ CHECK("\x00\x00", "AAA", "=");
+ CHECK("\x00\x00\x00", "AAAA", "");
+ CHECK("12345678", "MTIzNDU2Nzg", "=");
+ CHECK("testtest", "dGVzdHRlc3Q", "=");
+ CHECK("zy[]y21 !", "enlbXXkyMSAh", "");
+ CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw", "==");
+
+ for (i = 0; i <= 1; i++) {
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "AA=*", 4u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "AA*=", 4u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "AA**", 4u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "A===", 4u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "A", 1u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "====", 4u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+ }
+
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_decompose_chain.3 b/librecrypt_decompose_chain.3
new file mode 100644
index 0000000..fc9f928
--- /dev/null
+++ b/librecrypt_decompose_chain.3
@@ -0,0 +1,98 @@
+.TH LIBRECRYPT_DECOMPOSE_CHAIN 3 LIBRECRYPT
+.SH NAME
+librecrypt_decompose_chain - Split a chained password hash string into algorithm components
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+size_t \fBlibrecrypt_decompose_chain\fP(char *\fIhash\fP, char **\fIchain_out_array\fP, size_t \fIsize\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_decompose_chain ()
+function decomposes the chain of hash algorithms
+specified in
+.IR hash
+into null byte-terminated substrings. The
+.IR hash
+string is modified by replacing up to
+.I size-1
+instances of
+.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER
+(which is
+.BR \(aq>\(aq )
+by null bytes and storing pointers to the
+start of each substring in
+.IR chain_out_array .
+.PP
+If
+.I size
+is positive and smaller than the number of
+algorithms, then
+.I chain_out_array[size-1]
+will contain the remainder of the chain
+(still delimited by
+.IR LIBRECRYPT_ALGORITHM_LINK_DELIMITER ).
+.PP
+Unless already stripped out before input, the
+last substring ends with the resulting hash.
+.PP
+The original
+.IR hash
+string can be restored by overwriting each
+terminating null byte in all but the last
+substring stored in
+.IR chain_out_array
+with
+.IR LIBRECRYPT_ALGORITHM_LINK_DELIMITER .
+.PP
+.I hash
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_decompose_chain ()
+function returns the number of chained hashes
+(the same value as returned by
+.BR librecrypt_chain_length (3)).
+
+.SH ERRORS
+The
+.BR librecrypt_decompose_chain ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_decompose_chain ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_decompose_chain ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_decompose_chain ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_chain_length (3),
+.BR librecrypt_decompose_chain1 (3),
+.BR librecrypt_next_algorithm (3)
diff --git a/librecrypt_decompose_chain.c b/librecrypt_decompose_chain.c
new file mode 100644
index 0000000..9b7aede
--- /dev/null
+++ b/librecrypt_decompose_chain.c
@@ -0,0 +1,153 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline size_t librecrypt_decompose_chain(char *hash, char **chain_out_array, size_t size);
+
+
+#else
+
+
+#define HASH_1 "a$b"
+
+#define HASH_2_A "a$b"
+#define HASH_2_B "c$d"
+#define HASH_2_C "e$f"
+#define HASH_2_BC HASH_2_B">"HASH_2_C
+#define HASH_2 HASH_2_A">"HASH_2_BC
+
+#define HASH_3_A ""
+#define HASH_3_B "a$b"
+#define HASH_3_C "c$d"
+#define HASH_3_D "e$f"
+#define HASH_3_E ""
+#define HASH_3_DE HASH_3_D">"HASH_3_E
+#define HASH_3_CDE HASH_3_C">"HASH_3_DE
+#define HASH_3_BCDE HASH_3_B">"HASH_3_CDE
+#define HASH_3 HASH_3_A">"HASH_3_BCDE
+
+
+#define NULL_OUT(ARRAY)\
+ do {\
+ for (i = 0u; i < ELEMSOF(ARRAY); i++)\
+ (ARRAY)[i] = NULL;\
+ } while (0)
+
+
+int
+main(void)
+{
+ char buf[64u];
+ char *chain[10u];
+ size_t i;
+
+ SET_UP_ALARM();
+
+ stpcpy(buf, HASH_1);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 1u);
+ EXPECT(!strcmp(buf, HASH_1));
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 1u) == 1u);
+ EXPECT(!strcmp(buf, HASH_1));
+ EXPECT(chain[0u] == buf);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, ELEMSOF(chain)) == 1u);
+ EXPECT(!strcmp(buf, HASH_1));
+ EXPECT(chain[0u] == buf);
+
+ stpcpy(buf, HASH_2);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 3u);
+ EXPECT(!strcmp(buf, HASH_2));
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 1u) == 3u);
+ EXPECT(!strcmp(buf, HASH_2));
+ EXPECT(chain[0u] == buf);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 2u) == 3u);
+ EXPECT(chain[0u] != NULL);
+ EXPECT(chain[1u] != NULL);
+ EXPECT(chain[2u] == NULL);
+ EXPECT(!strcmp(chain[0u], HASH_2_A));
+ EXPECT(!strcmp(chain[1u], HASH_2_BC));
+ stpcpy(buf, HASH_2);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, ELEMSOF(chain)) == 3u);
+ EXPECT(chain[0u] != NULL);
+ EXPECT(chain[1u] != NULL);
+ EXPECT(chain[2u] != NULL);
+ EXPECT(chain[3u] == NULL);
+ EXPECT(!strcmp(chain[0u], HASH_2_A));
+ EXPECT(!strcmp(chain[1u], HASH_2_B));
+ EXPECT(!strcmp(chain[2u], HASH_2_C));
+
+ stpcpy(buf, HASH_3);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 0u) == 5u);
+ EXPECT(!strcmp(buf, HASH_3));
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 1u) == 5u);
+ EXPECT(!strcmp(buf, HASH_3));
+ EXPECT(chain[0u] == buf);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 2u) == 5u);
+ EXPECT(chain[0u] != NULL);
+ EXPECT(chain[1u] != NULL);
+ EXPECT(chain[2u] == NULL);
+ EXPECT(!strcmp(chain[0u], HASH_3_A));
+ EXPECT(!strcmp(chain[1u], HASH_3_BCDE));
+ stpcpy(buf, HASH_3);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 3u) == 5u);
+ EXPECT(chain[0u] != NULL);
+ EXPECT(chain[1u] != NULL);
+ EXPECT(chain[2u] != NULL);
+ EXPECT(chain[3u] == NULL);
+ EXPECT(!strcmp(chain[0u], HASH_3_A));
+ EXPECT(!strcmp(chain[1u], HASH_3_B));
+ EXPECT(!strcmp(chain[2u], HASH_3_CDE));
+ stpcpy(buf, HASH_3);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 4u) == 5u);
+ EXPECT(chain[0u] != NULL);
+ EXPECT(chain[1u] != NULL);
+ EXPECT(chain[2u] != NULL);
+ EXPECT(chain[3u] != NULL);
+ EXPECT(chain[4u] == NULL);
+ EXPECT(!strcmp(chain[0u], HASH_3_A));
+ EXPECT(!strcmp(chain[1u], HASH_3_B));
+ EXPECT(!strcmp(chain[2u], HASH_3_C));
+ EXPECT(!strcmp(chain[3u], HASH_3_DE));
+ stpcpy(buf, HASH_3);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 5u) == 5u);
+ EXPECT(chain[0u] != NULL);
+ EXPECT(chain[1u] != NULL);
+ EXPECT(chain[2u] != NULL);
+ EXPECT(chain[3u] != NULL);
+ EXPECT(chain[4u] != NULL);
+ EXPECT(chain[5u] == NULL);
+ EXPECT(!strcmp(chain[0u], HASH_3_A));
+ EXPECT(!strcmp(chain[1u], HASH_3_B));
+ EXPECT(!strcmp(chain[2u], HASH_3_C));
+ EXPECT(!strcmp(chain[3u], HASH_3_D));
+ EXPECT(!strcmp(chain[4u], HASH_3_E));
+ stpcpy(buf, HASH_3);
+ NULL_OUT(chain);
+ EXPECT(librecrypt_decompose_chain(buf, chain, 6u) == 5u);
+ EXPECT(chain[0u] != NULL);
+ EXPECT(chain[1u] != NULL);
+ EXPECT(chain[2u] != NULL);
+ EXPECT(chain[3u] != NULL);
+ EXPECT(chain[4u] != NULL);
+ EXPECT(chain[5u] == NULL);
+ EXPECT(!strcmp(chain[0u], HASH_3_A));
+ EXPECT(!strcmp(chain[1u], HASH_3_B));
+ EXPECT(!strcmp(chain[2u], HASH_3_C));
+ EXPECT(!strcmp(chain[3u], HASH_3_D));
+ EXPECT(!strcmp(chain[4u], HASH_3_E));
+
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_decompose_chain1.3 b/librecrypt_decompose_chain1.3
new file mode 100644
index 0000000..8a6e3b0
--- /dev/null
+++ b/librecrypt_decompose_chain1.3
@@ -0,0 +1,75 @@
+.TH LIBRECRYPT_DECOMPOSE_CHAIN1 3 LIBRECRYPT
+.SH NAME
+librecrypt_decompose_chain1 - Replace algorithm link delimiters with null bytes
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+size_t \fBlibrecrypt_decompose_chain1\fP(char *\fIhash\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_decompose_chain1 ()
+function replaces each
+.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER
+(which is
+.BR \(aq>\(aq )
+in
+.I hash
+with a null byte. This effectively decomposes
+the string into one string per hash algorithm.
+.PP
+Unless already stripped out before input, the
+last resulting string ends with the resulting
+hash, if it was present in the input.
+.PP
+.I hash
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_decompose_chain1 ()
+function returns the number of chained hashes
+(the same value as returned by
+.BR librecrypt_chain_length (3)).
+
+.SH ERRORS
+The
+.BR librecrypt_decompose_chain1 ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_decompose_chain1 ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_decompose_chain1 ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_decompose_chain1 ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_chain_length (3),
+.BR librecrypt_decompose_chain (3),
+.BR librecrypt_next_algorithm (3)
diff --git a/librecrypt_decompose_chain1.c b/librecrypt_decompose_chain1.c
new file mode 100644
index 0000000..718e54c
--- /dev/null
+++ b/librecrypt_decompose_chain1.c
@@ -0,0 +1,37 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline size_t librecrypt_decompose_chain1(char *hash);
+
+
+#else
+
+
+#define CHECK(IN, OUT, N)\
+ do {\
+ assert(sizeof(IN) <= sizeof(buf));\
+ assert(sizeof(IN) == sizeof(OUT));\
+ stpcpy(buf, (IN));\
+ n = librecrypt_decompose_chain1(buf);\
+ EXPECT(n == (N));\
+ EXPECT(n == librecrypt_chain_length(IN));\
+ EXPECT(!memcmp(buf, (OUT), sizeof(IN)));\
+ } while (0)
+
+
+int
+main(void)
+{
+ char buf[64u];
+ size_t n;
+ SET_UP_ALARM();
+ CHECK("", "", 1u);
+ CHECK(">", "\0", 2u);
+ CHECK("a$b>c$d>e$f", "a$b\0c$d\0e$f", 3u);
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_encode.3 b/librecrypt_encode.3
new file mode 100644
index 0000000..95862f8
--- /dev/null
+++ b/librecrypt_encode.3
@@ -0,0 +1,93 @@
+.TH LIBRECRYPT_ENCODE 3 LIBRECRYPT
+.SH NAME
+librecrypt_encode - Encode binary salt or hash result into ASCII
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+size_t \fBlibrecrypt_encode\fP(char *\fIout_buffer\fP, size_t \fIsize\fP,
+ const void *\fIbinary\fP, size_t \fIlen\fP,
+ const char \fIlut\fP[restrict static 256], char \fIpad\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_encode ()
+function encodes
+.I len
+bytes of binary data from
+.IR binary
+into an ASCII representation written to
+.IR out_buffer .
+This is used for encoding hash results and salts.
+.PP
+The
+.I lut
+argument is an encoding alphabet, consisting
+of 64 characters, repeated 4 times.
+.PP
+The
+.I pad
+argument specifies the padding character to
+use at the end, or the null byte if none.
+.PP
+On successful completion, up to
+.I size
+bytes are written to
+.IR out_buffer ;
+if
+.I size
+is positive, the output is always
+null byte-terminated even if truncated.
+.PP
+The return value is the number of bytes
+that would have been written, excluding
+the terminating null byte, if
+.I size
+was sufficiently large.
+
+.SH RETURN VALUES
+The
+.BR librecrypt_encode ()
+function returns the number of bytes that
+would have been written to
+.IR out_buffer ,
+excluding the terminating null byte.
+
+.SH ERRORS
+The
+.BR librecrypt_encode ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_encode ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_encode ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_encode ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_decode (3),
+.BR librecrypt_get_encoding (3)
diff --git a/librecrypt_encode.c b/librecrypt_encode.c
new file mode 100644
index 0000000..2199ac1
--- /dev/null
+++ b/librecrypt_encode.c
@@ -0,0 +1,177 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#endif
+
+
+#define O1(I1, I2, I3) ((I1) >> 2)
+#define O2(I1, I2, I3) (((I1) << 4) | ((I2) >> 4))
+#define O3(I1, I2, I3) (((I2) << 2) | ((I3) >> 6))
+#define O4(I1, I2, I3) (I3)
+
+
+size_t
+librecrypt_encode(char *out_buffer, size_t size, const void *binary, size_t len,
+ const char lut[restrict static 256], char pad)
+{
+ const unsigned char *data = binary;
+ unsigned char a, b, c;
+ size_t q, r, i, j, n;
+
+ q = len / 3u;
+ r = len % 3u;
+ n = q * 4u + (!r ? 0u : pad ? 4u : r + 1u);
+
+ if (!size)
+ return n;
+
+ size -= 1u;
+ out_buffer[n < size ? n : size] = '\0';
+
+ if (r == 1u) {
+ a = data[q * 3u + 0u];
+ switch (size > q * 4u ? size - q * 4u : 0u) { /* fall-through */
+ default:
+ if (pad)
+ out_buffer[q * 4u + 3u] = pad;
+ case 3u:
+ if (pad)
+ out_buffer[q * 4u + 2u] = pad;
+ case 2u:
+ out_buffer[q * 4u + 1u] = lut[O2(a, 0, 0) & 255];
+ case 1u:
+ out_buffer[q * 4u + 0u] = lut[O1(a, 0, 0) & 255];
+ case 0u:
+ break;
+ }
+ } else if (r == 2u) {
+ a = data[q * 3u + 0u];
+ b = data[q * 3u + 1u];
+ switch (size > q * 4u ? size - q * 4u : 0u) { /* fall-through */
+ default:
+ if (pad)
+ out_buffer[q * 4u + 3u] = pad;
+ case 3u:
+ out_buffer[q * 4u + 2u] = lut[O3(a, b, 0) & 255];
+ case 2u:
+ out_buffer[q * 4u + 1u] = lut[O2(a, b, 0) & 255];
+ case 1u:
+ out_buffer[q * 4u + 0u] = lut[O1(a, b, 0) & 255];
+ case 0u:
+ break;
+ }
+ }
+
+ i = q * 4u;
+ j = q * 3u;
+ if (i > size) {
+ q = size / 4u;
+ r = size % 4u;
+ i = q * 4u;
+ j = q * 3u;
+ c = data[j + 2u];
+ b = data[j + 1u];
+ a = data[j + 0u];
+ switch (r) { /* fall-through */
+ case 3u:
+ out_buffer[i + 2u] = lut[O3(a, b, c) & 255];
+ case 2u:
+ out_buffer[i + 1u] = lut[O2(a, b, c) & 255];
+ case 1u:
+ out_buffer[i + 0u] = lut[O1(a, b, c) & 255];
+ default:
+ break;
+ }
+ }
+ while (i) {
+ i -= 4u;
+ j -= 3u;
+ c = data[j + 2u];
+ b = data[j + 1u];
+ a = data[j + 0u];
+ out_buffer[i + 3u] = lut[O4(a, b, c) & 255];
+ out_buffer[i + 2u] = lut[O3(a, b, c) & 255];
+ out_buffer[i + 1u] = lut[O2(a, b, c) & 255];
+ out_buffer[i + 0u] = lut[O1(a, b, c) & 255];
+ }
+
+ return n;
+}
+
+
+#else
+
+
+NONSTRING static const char lut[256u] = MAKE_ENCODING_LUT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
+
+
+#define CHECK(BINARY, ASCII)\
+ check((BINARY), sizeof(BINARY) - 1u, (ASCII), sizeof(ASCII) - 1u)
+
+
+static void
+check(const char *binary, size_t binary_len, const char *ascii, size_t ascii_len)
+{
+ size_t padded_ascii_len = ascii_len;
+ char buf[256u];
+ size_t i, j, n;
+
+ if (padded_ascii_len & 3u) {
+ padded_ascii_len |= 3u;
+ padded_ascii_len += 1u;
+ }
+ assert(padded_ascii_len % 4u == 0u);
+ assert(padded_ascii_len / 4u == (ascii_len + 3u) / 4u);
+ assert(padded_ascii_len >= ascii_len);
+
+ EXPECT(librecrypt_encode(NULL, 0u, binary, binary_len, lut, '\0') == ascii_len);
+ EXPECT(librecrypt_encode(buf, 0u, binary, binary_len, lut, '\0') == ascii_len);
+
+ EXPECT(librecrypt_encode(NULL, 0u, binary, binary_len, lut, '=') == padded_ascii_len);
+ EXPECT(librecrypt_encode(buf, 0u, binary, binary_len, lut, '=') == padded_ascii_len);
+
+ for (i = 0u; i <= ascii_len; i++) {
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_encode(buf, i + 1u, binary, binary_len, lut, '\0') == ascii_len);
+ EXPECT(!memcmp(buf, ascii, i));
+ EXPECT(buf[i] == '\0');
+ for (j = i + 1u; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+ }
+
+ for (i = 0u; i <= padded_ascii_len; i++) {
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_encode(buf, i + 1u, binary, binary_len, lut, '=') == padded_ascii_len);
+ j = i < ascii_len ? i : ascii_len;
+ n = i < padded_ascii_len ? i : padded_ascii_len;
+ EXPECT(!memcmp(buf, ascii, j));
+ for (; j < n; j++)
+ EXPECT(buf[j] == '=');
+ EXPECT(buf[j++] == '\0');
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+ }
+}
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+ CHECK("", "");
+ CHECK("\x00", "AA");
+ CHECK("\x00\x00", "AAA");
+ CHECK("\x00\x00\x00", "AAAA");
+ CHECK("12345678", "MTIzNDU2Nzg");
+ CHECK("testtest", "dGVzdHRlc3Q");
+ CHECK("zy[]y21 !", "enlbXXkyMSAh");
+ CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw");
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_equal.3 b/librecrypt_equal.3
new file mode 100644
index 0000000..cc3a387
--- /dev/null
+++ b/librecrypt_equal.3
@@ -0,0 +1,70 @@
+.TH LIBRECRYPT_EQUAL 3 LIBRECRYPT
+.SH NAME
+librecrypt_equal - Compare strings in constant time
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+int \fBlibrecrypt_equal\fP(const char *\fIa\fP, const char *\fIb\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_equal ()
+function compares two null byte-terminated strings
+in constant time.
+.PP
+If the strings are of different length, the function
+returns 0 after the lengths have been measured.
+This should not happen when comparing password hashes,
+since their length is determined by the selected
+configuration.
+.PP
+.I a
+and
+.I b
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_equal ()
+function returns 1 if the strings are equal,
+and 0 otherwise.
+
+.SH ERRORS
+The
+.BR librecrypt_equal ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_equal ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_equal ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_equal ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_equal_binary (3)
diff --git a/librecrypt_equal.c b/librecrypt_equal.c
new file mode 100644
index 0000000..22e2c05
--- /dev/null
+++ b/librecrypt_equal.c
@@ -0,0 +1,39 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline int librecrypt_equal(const char *a, const char *b);
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+ EXPECT(librecrypt_equal("", "") == 1);
+ EXPECT(librecrypt_equal("", "a") == 0);
+ EXPECT(librecrypt_equal("a", "") == 0);
+ EXPECT(librecrypt_equal("a", "b") == 0);
+ EXPECT(librecrypt_equal("a", "a") == 1);
+ EXPECT(librecrypt_equal("abcdef", "Abcdef") == 0);
+ EXPECT(librecrypt_equal("abcdef", "ABcdef") == 0);
+ EXPECT(librecrypt_equal("abcdef", "aBcdef") == 0);
+ EXPECT(librecrypt_equal("abcdef", "abCdef") == 0);
+ EXPECT(librecrypt_equal("abcdef", "abCdeF") == 0);
+ EXPECT(librecrypt_equal("abcdef", "abcdeF") == 0);
+ EXPECT(librecrypt_equal("abcdef", "abcdex") == 0);
+ EXPECT(librecrypt_equal("abcdef", "abcxef") == 0);
+ EXPECT(librecrypt_equal("abcdef", "xbcdef") == 0);
+ EXPECT(librecrypt_equal("abcdef", "abcdef") == 1);
+ EXPECT(librecrypt_equal("abcdef", "abcdefg") == 0);
+ EXPECT(librecrypt_equal("abcdefg", "abcdef") == 0);
+ EXPECT(librecrypt_equal("abcdef", "") == 0);
+ EXPECT(librecrypt_equal("", "abcdef") == 0);
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_equal_binary.3 b/librecrypt_equal_binary.3
new file mode 100644
index 0000000..d441c5b
--- /dev/null
+++ b/librecrypt_equal_binary.3
@@ -0,0 +1,65 @@
+.TH LIBRECRYPT_EQUAL_BINARY 3 LIBRECRYPT
+.SH NAME
+librecrypt_equal_binary - Compare memory segments in constant time
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+int \fBlibrecrypt_equal_binary\fP(const void *\fIa\fP, const void *\fIb\fP, size_t \fIlen\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_equal_binary ()
+function compares the first
+.I len
+bytes of
+.I a
+and
+.I b
+in constant time.
+
+.SH RETURN VALUES
+The
+.BR librecrypt_equal_binary ()
+function returns 1 if the memory segments are
+equal in their first
+.I len
+bytes, and 0 otherwise.
+
+.SH ERRORS
+The
+.BR librecrypt_equal_binary ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_equal_binary ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_equal_binary ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_equal_binary ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_equal (3)
diff --git a/librecrypt_equal_binary.c b/librecrypt_equal_binary.c
new file mode 100644
index 0000000..e1d17cd
--- /dev/null
+++ b/librecrypt_equal_binary.c
@@ -0,0 +1,77 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=const"
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
+#endif
+
+
+static void
+explicit(unsigned char r)
+{
+ (void) r;
+#if defined(__GNUC__)
+ __asm__ volatile ("" :: "g" (r) : "memory");
+#endif
+}
+
+
+void (*volatile librecrypt_explicit_____)(unsigned char) = &explicit;
+
+
+int
+librecrypt_equal_binary(const void *a, const void *b, size_t len)
+{
+ const unsigned char *x = a;
+ const unsigned char *y = b;
+ size_t i;
+ unsigned char r = 0u;
+
+ for (i = 0u; i < len; i++)
+ r = (unsigned char)(r | (*x++ ^ *y++));
+
+ (*librecrypt_explicit_____)(r);
+
+ return r ? 0 : 1;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+ EXPECT(librecrypt_equal_binary("", "", 0u) == 1);
+ EXPECT(librecrypt_equal_binary("", "", 1u) == 1);
+ EXPECT(librecrypt_equal_binary("a", "", 1u) == 0);
+ EXPECT(librecrypt_equal_binary("", "a", 1u) == 0);
+ EXPECT(librecrypt_equal_binary("a", "a", 1u) == 1);
+ EXPECT(librecrypt_equal_binary("a", "a", 2u) == 1);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdef", 6u) == 1);
+ EXPECT(librecrypt_equal_binary("abcdef", "Abcdef", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "AbCdef", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "abCdef", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "abCdeF", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdeF", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "xbcdef", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "xbxdef", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "abxdef", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "abxdex", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 6u) == 0);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 5u) == 1);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 4u) == 1);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 3u) == 1);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 2u) == 1);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 1u) == 1);
+ EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 0u) == 1);
+ EXPECT(librecrypt_equal_binary(NULL, NULL, 0u) == 1);
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_fill_with_random_.c b/librecrypt_fill_with_random_.c
new file mode 100644
index 0000000..475a156
--- /dev/null
+++ b/librecrypt_fill_with_random_.c
@@ -0,0 +1,142 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+librecrypt_fill_with_random_(void *out, size_t n, ssize_t (*rng)(void *out, size_t n, void *user), void *user)
+{
+ char *buf = out;
+ ssize_t r;
+
+ if (!rng)
+ rng = &librecrypt_rng_;
+
+ while (n) {
+ r = (*rng)(buf, n, user);
+ if (r <= 0) {
+ if (!r)
+ abort();
+ return -1;
+ }
+ buf = &buf[(size_t)r];
+ n -= (size_t)r;
+ }
+
+ return 0;
+}
+
+
+#else
+
+
+static ssize_t
+zero(void *out, size_t n, void *user)
+{
+ (void) user;
+ memset(out, 0, n);
+ return (ssize_t)n;
+}
+
+
+static ssize_t
+seq(void *out, size_t n, void *user)
+{
+ unsigned char *restrict buf = out;
+ unsigned char *restrict num = user;
+ size_t i;
+ if (n > 64u)
+ n = 64u;
+ for (i = 0u; i < n; i++)
+ buf[i] = (*num)++;
+ return (ssize_t)n;
+}
+
+
+static ssize_t
+next(void *out, size_t n, void *user)
+{
+ unsigned char *restrict buf = out;
+ unsigned char *restrict num = user;
+ assert(n);
+ *buf = (*num)++;
+ return 1;
+}
+
+
+static ssize_t
+failer(void *out, size_t n, void *user)
+{
+ (void) out;
+ (void) n;
+ (void) user;
+ errno = EDOM;
+ return -1;
+}
+
+
+static ssize_t
+zero_ret(void *out, size_t n, void *user)
+{
+ (void) out;
+ (void) n;
+ (void) user;
+ return 0;
+}
+
+
+int
+main(void)
+{
+ unsigned char buf1[1024u];
+ unsigned char buf2[sizeof(buf1)];
+ unsigned char s;
+ size_t i;
+ int rv = 0;
+
+ INIT_TEST_ABORT();
+
+ SET_UP_ALARM();
+
+ EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), NULL, NULL) == 0);
+ EXPECT(librecrypt_fill_with_random_(buf2, sizeof(buf1), NULL, NULL) == 0);
+ EXPECT(memcmp(buf1, buf2, sizeof(buf1)));
+
+ memset(buf1, 99, sizeof(buf1));
+ errno = 0;
+ EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &zero, NULL) == 0);
+ EXPECT(errno == 0);
+ for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++)
+ EXPECT(!buf1[i]);
+
+ memset(buf1, 99, sizeof(buf1));
+ s = 0u;
+ errno = 0;
+ EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &seq, &s) == 0);
+ EXPECT(errno == 0);
+ for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++)
+ EXPECT(buf1[i] == s);
+
+ memset(buf1, 99, sizeof(buf1));
+ s = 0u;
+ errno = 0;
+ EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &next, &s) == 0);
+ EXPECT(errno == 0);
+ for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++)
+ EXPECT(buf1[i] == s);
+
+ memset(buf1, 99, sizeof(buf1));
+ s = 0u;
+ errno = 0;
+ EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), &failer, NULL) == -1);
+ EXPECT(errno == EDOM);
+ for (s = 0u, i = 0u; i < sizeof(buf1); i++, s++)
+ EXPECT(buf1[i] == 99);
+
+ EXPECT_ABORT(rv = librecrypt_fill_with_random_(buf1, sizeof(buf1), &zero_ret, NULL));
+
+ return rv;
+}
+
+
+#endif
diff --git a/librecrypt_find_first_algorithm_.c b/librecrypt_find_first_algorithm_.c
new file mode 100644
index 0000000..406f5ac
--- /dev/null
+++ b/librecrypt_find_first_algorithm_.c
@@ -0,0 +1,41 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+const struct algorithm *
+librecrypt_find_first_algorithm_(const char *settings, size_t len)
+{
+ unsigned r, priority = 0;
+ const struct algorithm *algo, *found = NULL;
+ size_t i;
+
+ for (i = 0u;; i++) {
+ algo = &librecrypt_algorithms_[i];
+ if (IS_END_OF_ALGORITHMS(algo))
+ break;
+ r = (*algo->is_algorithm)(settings, len);
+ if (r > priority) {
+ priority = r;
+ found = algo;
+ }
+ }
+
+ return found;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_get_encoding.3 b/librecrypt_get_encoding.3
new file mode 100644
index 0000000..e9f7e83
--- /dev/null
+++ b/librecrypt_get_encoding.3
@@ -0,0 +1,112 @@
+.TH LIBRECRYPT_GET_ENCODING 3 LIBRECRYPT
+.SH NAME
+librecrypt_get_encoding - Get encoding alphabet for the last algorithm in a chain
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+const void *\fBlibrecrypt_get_encoding\fP(const char *\fIsettings\fP, size_t \fIlen\fP,
+ char *\fIpad_out\fP, int *\fIstrict_pad_out\fP, int \fIdecoding\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_get_encoding ()
+function returns the ASCII encoding used for
+the last algorithm in the algorithm chain
+specified by
+.IR settings .
+.PP
+If
+.I decoding
+is zero, the returned pointer is suitable for
+use as the
+.I lut
+parameter to
+.BR librecrypt_encode (3)
+and points to an encoding alphabet consisting
+of 64 characters repeated 4 times.
+.PP
+If
+.I decoding
+is non-zero, the returned pointer is suitable
+for use as the
+.I lut
+parameter to
+.BR librecrypt_decode (3)
+and points to a reverse lookup table mapping
+any valid character (except the padding
+character) to its value in the encoding
+alphabet and any other character to
+.BR 0xFFu .
+.PP
+On success,
+.IR *pad_out
+is set to the padding character, or to the
+null byte if there is no padding.
+.IR *strict_pad_out
+is set to 1 if padding is mandatory,
+0 otherwise.
+.IR *strict_pad_out
+is set arbitrarily to 0 or 1 if
+.IR *pad_out
+is set to the null byte.
+.PP
+.I pad_out
+and
+.I strict_pad_out
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_get_encoding ()
+function returns a pointer to the encoding
+data, or
+.IR NULL
+on failure. On failure,
+.IR errno
+is set to describe the error.
+
+.SH ERRORS
+The
+.BR librecrypt_get_encoding ()
+function will fail if:
+.TP
+.B ENOSYS
+The last algorithm in
+.I settings
+is not recognised or was disabled at compile-time.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_get_encoding ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_get_encoding ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_get_encoding ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_encode (3),
+.BR librecrypt_decode (3)
diff --git a/librecrypt_get_encoding.c b/librecrypt_get_encoding.c
new file mode 100644
index 0000000..dd5d33d
--- /dev/null
+++ b/librecrypt_get_encoding.c
@@ -0,0 +1,46 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+const void *
+librecrypt_get_encoding(const char *settings, size_t len, char *pad_out, int *strict_pad_out, int decoding)
+{
+ size_t i, start = 0u;
+ const struct algorithm *algo;
+
+ for (i = 0u; i < len; i++)
+ if (settings[i] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER)
+ start = i + 1u;
+ settings = &settings[start];
+ len -= start;
+
+ algo = librecrypt_find_first_algorithm_(settings, len);
+ if (!algo) {
+ errno = ENOSYS;
+ return NULL;
+ }
+
+ *pad_out = algo->pad;
+ *strict_pad_out = algo->strict_pad;
+ if (decoding)
+ return algo->decoding_lut;
+ else
+ return algo->encoding_lut;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_hash.3 b/librecrypt_hash.3
new file mode 100644
index 0000000..5bf32ea
--- /dev/null
+++ b/librecrypt_hash.3
@@ -0,0 +1,135 @@
+.TH LIBRECRYPT_HASH 3 LIBRECRYPT
+.SH NAME
+librecrypt_hash - Compute password hash encoded in ASCII without settings prefix
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+ssize_t \fBlibrecrypt_hash\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP,
+ const char *\fIphrase\fP, size_t \fIlen\fP,
+ const char *\fIsettings\fP, void *\fIreserved\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+Static linking may require additional flags
+depending on enabled hash algorithms.
+
+.SH DESCRIPTION
+The
+.BR librecrypt_hash ()
+function computes the hash of a password.
+The resulting hash is encoded with
+.BR librecrypt_encode (3)
+but stored without any settings prefix.
+.PP
+The password is provided in
+.IR phrase
+and can contain null bytes; its length is specified by
+.IR len .
+.PP
+The password hash configuration string is provided in
+.IR settings .
+If
+.I settings
+contains a resulting hash, it is ignored.
+If
+.I settings
+uses asterisk-encoding to specify random salts,
+the function fails.
+.PP
+The
+.I reserved
+parameter is reserved for future use and should be
+.IR NULL .
+.PP
+On successful completion, if
+.I size
+is positive, the output is
+null byte-terminated even if truncated.
+.PP
+Any encountered
+.BR EINTR
+is ignored.
+.PP
+On failure,
+.I out_buffer
+remains unmodified.
+.PP
+.I settings
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_hash ()
+function returns the number of bytes that would
+have been written to
+.IR out_buffer
+if
+.I size
+was sufficiently large, excluding the terminating
+null byte. On failure, -1 is returned and
+.I errno
+is set to describe the error.
+
+.SH ERRORS
+The
+.BR librecrypt_hash ()
+function will fail if:
+.TP
+.B EINVAL
+.I reserved
+is not
+.IR NULL .
+.TP
+.B EINVAL
+.I settings
+is invalid.
+.TP
+.B EINVAL
+.I settings
+uses asterisk-encoding to specify random salts.
+.TP
+.B ERANGE
+.I len
+is too large or too small for the selected
+initial algorithm.
+.TP
+.B ENOMEM
+Failed to allocate internal scratch memory.
+.TP
+.B ENOSYS
+A selected hash algorithm is not recognised or
+was disabled at compile-time.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_hash ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_hash ()
+T} Async-signal safety AS-Unsafe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_hash ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_hash_binary (3),
+.BR librecrypt_crypt (3),
+.BR librecrypt_test_supported (3)
diff --git a/librecrypt_hash.c b/librecrypt_hash.c
new file mode 100644
index 0000000..267aa31
--- /dev/null
+++ b/librecrypt_hash.c
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_hash(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved)
+{
+ return librecrypt_hash_(out_buffer, size, phrase, len, settings, reserved, ASCII_HASH);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_hash_.c b/librecrypt_hash_.c
new file mode 100644
index 0000000..ed842e9
--- /dev/null
+++ b/librecrypt_hash_.c
@@ -0,0 +1,187 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+static ssize_t
+zero_generator(void *out, size_t n, void *user)
+{
+ (void) user;
+ if (n > (size_t)SSIZE_MAX)
+ n = (size_t)SSIZE_MAX;
+ memset(out, 0, n);
+ return (ssize_t)n;
+}
+
+
+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)
+{
+ const struct algorithm *algo;
+ ssize_t (*rng)(void *out, size_t n, void *user) = NULL;
+ 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;
+ int has_next, phrase_scratch_i = 0;
+ ssize_t r_len;
+ int r;
+ void *new;
+
+ if (reserved != NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!size)
+ rng = &zero_generator;
+
+ if (strchr(settings, '*')) {
+ if (action != ASCII_CRYPT) {
+ errno = EINVAL;
+ return -1;
+ }
+ r_len = librecrypt_realise_salts(out_buffer, size, settings, rng, NULL);
+ if (r_len < 0) {
+ if (errno == ERANGE)
+ errno = ENOMEM;
+ return -1;
+ } else if ((size_t)r_len >= size) {
+ settings_scratch = malloc((size_t)r_len + 1u);
+ if (!settings_scratch)
+ return -1;
+ if (librecrypt_realise_salts(settings_scratch, (size_t)r_len + 1u, settings, rng, NULL) != r_len)
+ abort();
+ settings = settings_scratch;
+ }
+ }
+
+next:
+ has_next = 0;
+ for (n = 0u; settings[n]; n++) {
+ if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) {
+ has_next = 1;
+ break;
+ }
+ }
+
+ 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;
+ }
+
+ if (action == ASCII_CRYPT) {
+ min = size ? size - 1u < prefix ? size - 1u : prefix : 0u;
+ size -= min;
+ memcpy(out_buffer, settings, min);
+ out_buffer = &out_buffer[min];
+ ret += prefix;
+ }
+
+ if (size && phrase_scratch_sizes[phrase_scratch_i] < algo->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);
+ if (!new) {
+ free(phrase_scratches[phrase_scratch_i]);
+ phrase_scratches[phrase_scratch_i] = NULL;
+ phrase_scratch_sizes[phrase_scratch_i] = 0u;
+ goto fail;
+ }
+ phrase_scratches[phrase_scratch_i] = new;
+ phrase_scratch_sizes[phrase_scratch_i] = algo->hash_size;
+ }
+
+ if (has_next) {
+ 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);
+ } else if (action == BINARY_HASH) {
+ hash_to_output:
+ r = (*algo->hash)(out_buffer, size, phrase, len, settings, prefix, reserved);
+ } else if (size < algo->hash_size) {
+ goto hash_to_scratch;
+ } else {
+ goto hash_to_output;
+ }
+ if (r < 0)
+ goto fail;
+
+ if (!has_next) {
+ if (action == BINARY_HASH) {
+ ret += algo->hash_size;
+ } else if (!size) {
+ ascii_len = algo->hash_size % 3u;
+ if (ascii_len) {
+ if (algo->pad && algo->strict_pad)
+ ascii_len = 4u;
+ else
+ ascii_len += 1u;
+ }
+ ascii_len += algo->hash_size / 3u * 4u;
+ goto include_ascii;
+ } else {
+ 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');
+ include_ascii:
+ min = size ? size - 1u < ascii_len ? size - 1u : ascii_len : 0u;
+ out_buffer = &out_buffer[min];
+ size -= min;
+ ret += ascii_len;
+ }
+ } else {
+ phrase = size ? phrase_scratches[phrase_scratch_i] : NULL;
+ phrase_scratch_i ^= 1;
+ len = algo->hash_size;
+
+ settings = &settings[n];
+ if (action == ASCII_CRYPT) {
+ ret += 1u;
+ if (size) {
+ *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER;
+ size -= 1u;
+ }
+ }
+ settings++;
+ goto next;
+ }
+
+ librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]);
+ librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]);
+ librecrypt_wipe_str(settings_scratch);
+ free(phrase_scratches[0u]);
+ free(phrase_scratches[1u]);
+ free(settings_scratch);
+
+ if (size && action != BINARY_HASH)
+ out_buffer[0] = '\0';
+ return (ssize_t)ret;
+
+fail:
+ librecrypt_wipe(phrase_scratches[0u], phrase_scratch_sizes[0u]);
+ librecrypt_wipe(phrase_scratches[1u], phrase_scratch_sizes[1u]);
+ librecrypt_wipe_str(settings_scratch);
+ free(phrase_scratches[0u]);
+ free(phrase_scratches[1u]);
+ free(settings_scratch);
+ return -1;
+}
+
+
+#else
+
+
+/* Tested via librecrypt_hash_binary, librecrypt_hash, and librecrypt_crypt */
+CONST int main(void) { return 0; }
+
+
+#endif
diff --git a/librecrypt_hash_binary.3 b/librecrypt_hash_binary.3
new file mode 100644
index 0000000..720b82d
--- /dev/null
+++ b/librecrypt_hash_binary.3
@@ -0,0 +1,138 @@
+.TH LIBRECRYPT_HASH_BINARY 3 LIBRECRYPT
+.SH NAME
+librecrypt_hash_binary - Compute password hash in raw binary form
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+ssize_t \fBlibrecrypt_hash_binary\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP,
+ const char *\fIphrase\fP, size_t \fIlen\fP,
+ const char *\fIsettings\fP, void *\fIreserved\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+Static linking may require additional flags
+depending on enabled hash algorithms.
+
+.SH DESCRIPTION
+The
+.BR librecrypt_hash_binary ()
+function computes the hash of a password.
+The hash is stored in raw binary format without any
+settings prefix.
+.PP
+The password is provided in
+.IR phrase
+and can contain null bytes; its length is specified by
+.IR len .
+.PP
+The password hash configuration string is provided in
+.IR settings .
+If
+.I settings
+contains a resulting hash, it is ignored.
+If
+.I settings
+uses asterisk-encoding to specify random salts,
+the function fails.
+.PP
+The
+.I reserved
+parameter is reserved for future use and should be
+.IR NULL .
+.PP
+On successful completion, up to
+.I size
+bytes are written to
+.IR out_buffer ;
+the return value describes how many bytes would have
+been written if
+.I size
+was sufficiently large.
+.PP
+Any encountered
+.BR EINTR
+is ignored.
+.PP
+On failure,
+.I out_buffer
+remains unmodified.
+.PP
+.I settings
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_hash_binary ()
+function returns the number of bytes that would
+have been written to
+.IR out_buffer
+if
+.I size
+was sufficiently large.
+On failure, -1 is returned and
+.I errno
+is set to describe the error.
+
+.SH ERRORS
+The
+.BR librecrypt_hash_binary ()
+function will fail if:
+.TP
+.B EINVAL
+.I reserved
+is not
+.IR NULL .
+.TP
+.B EINVAL
+.I settings
+is invalid.
+.TP
+.B EINVAL
+.I settings
+uses asterisk-encoding to specify random salts.
+.TP
+.B ERANGE
+.I len
+is too large or too small for the selected
+initial algorithm.
+.TP
+.B ENOMEM
+Failed to allocate internal scratch memory.
+.TP
+.B ENOSYS
+A selected hash algorithm is not recognised or
+was disabled at compile-time.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_hash_binary ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_hash_binary ()
+T} Async-signal safety AS-Unsafe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_hash_binary ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_hash (3),
+.BR librecrypt_crypt (3),
+.BR librecrypt_test_supported (3)
diff --git a/librecrypt_hash_binary.c b/librecrypt_hash_binary.c
new file mode 100644
index 0000000..c119c78
--- /dev/null
+++ b/librecrypt_hash_binary.c
@@ -0,0 +1,26 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_hash_binary(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, void *reserved)
+{
+ return librecrypt_hash_(out_buffer, size, phrase, len, settings, reserved, BINARY_HASH);
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_make_settings.3 b/librecrypt_make_settings.3
new file mode 100644
index 0000000..f742df6
--- /dev/null
+++ b/librecrypt_make_settings.3
@@ -0,0 +1,177 @@
+.TH LIBRECRYPT_MAKE_SETTINGS 3 LIBRECRYPT
+.SH NAME
+librecrypt_make_settings - Generate a password hash settings string
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+ssize_t \fBlibrecrypt_make_settings\fP(char *\fIout_buffer\fP, size_t \fIsize\fP, const char *\fIalgorithm\fP,
+ size_t \fImemcost\fP, uintmax_t \fItimecost\fP, int \fIgensalt\fP,
+ ssize_t (*\fIrng\fP)(void *\fIout\fP, size_t \fIn\fP, void *\fIuser\fP), void *\fIuser\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+Static linking may require additional flags
+depending on enabled hash algorithms.
+
+.SH DESCRIPTION
+The
+.BR librecrypt_make_settings ()
+function generates a password hash setting string
+and writes it to
+.IR out_buffer .
+.PP
+If
+.I algorithm
+is
+.IR NULL ,
+a default strong algorithm is selected.
+The empty string selects the deprecated DES algorithm.
+Algorithm chains are disallowed for this function.
+.PP
+The
+.I memcost
+parameter specifies an approximate memory cost in bytes.
+A default value is selected if 0 is specified.
+.PP
+The
+.I timecost
+parameter specifies a total time cost in an
+implementation-dependent, monotonic unit.
+A default value is selected if 0 is specified.
+.PP
+If
+.I gensalt
+is non-zero, random salts are included in the
+generated output, otherwise the output will only
+specify the sizes the salts shall have.
+.PP
+If
+.I gensalt
+is non-zero,
+.IR *rng ,
+or a default implementation (which tries to use
+cryptographic random number generation but has a
+non-cryptographic random number generation as a
+last resort) if
+.I rng
+is
+.IR NULL ,
+used to generate random bytes.
+.I *rng
+shall fill
+.I out
+with at least 1, and up to
+.IR n ,
+random bytes, and return the number of bytes
+written, or return -1 on failure.
+.PP
+The
+.I data
+argument is passed into the
+.IR *rng -parameter
+with the same name, as is and may be used by
+.IR *rng
+for user-defined purposes.
+.PP
+On successful completion, if
+.I size
+is positive, the output is null byte terminated
+even if truncated.
+.PP
+If
+.I rng
+is
+.IR NULL ,
+any encountered
+.BR EINTR
+is ignored (cannot happen if
+.I gensalt
+is zero); if it is encountered,
+.I errno
+will be set to
+.BR EINTR ,
+but is otherwise left unmodified unless the function
+fails due to one of the errors listed below.
+
+.SH RETURN VALUES
+The
+.BR librecrypt_make_settings ()
+function returns the number of bytes that would have
+been written to
+.IR out_buffer
+if
+.I size
+was sufficiently large, or -1 on failure and
+.I errno
+is set to describe the error.
+
+.SH ERRORS
+The
+.BR librecrypt_make_settings ()
+function will fail if:
+.TP
+.B EINVAL
+.I algorithm
+represents a chain of algorithms.
+.TP
+.B ENOSYS
+.I algorithm
+represents an algorithm that is not recognised or
+was disabled at compile-time.
+.TP
+.B ENOSYS
+.I algorithm
+is
+.IR NULL
+but all algorithms were disabled at compile-time.
+.PP
+Additionally, unless
+.I rng
+is
+.IR NULL ,
+.I *rng
+may set
+.I errno
+(if
+.I gensalt
+is non-zero).
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_make_settings ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_make_settings ()
+T} Async-signal safety AS-Unsafe
+.TE
+.sp
+.PP
+If
+.I gensalt
+is non-zero and
+.I rng
+is
+.RI non- NULL ,
+.I *rng
+may weaken the safety attributes.
+
+.SH HISTORY
+The
+.BR librecrypt_make_settings ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_realise_salts (3)
diff --git a/librecrypt_make_settings.c b/librecrypt_make_settings.c
new file mode 100644
index 0000000..99c7e3b
--- /dev/null
+++ b/librecrypt_make_settings.c
@@ -0,0 +1,50 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memcost, uintmax_t timecost,
+ int gensalt, ssize_t (*rng)(void *out, size_t n, void *user), void *user)
+{
+ const struct algorithm *algo;
+
+ if (!algorithm) {
+ algo = &librecrypt_algorithms_[0];
+ if (IS_END_OF_ALGORITHMS(algo))
+ goto enosys;
+ } else {
+ if (strchr(algorithm, LIBRECRYPT_ALGORITHM_LINK_DELIMITER)) {
+ errno = EINVAL;
+ return -1;
+ }
+ algo = librecrypt_find_first_algorithm_(algorithm, strlen(algorithm));
+ if (!algo)
+ goto enosys;
+ }
+
+ if (!rng)
+ rng = &librecrypt_rng_;
+
+ return (*algo->make_settings)(out_buffer, size, algorithm, memcost, timecost, gensalt, rng, user);
+
+enosys:
+ errno = ENOSYS;
+ return -1;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_next_algorithm.3 b/librecrypt_next_algorithm.3
new file mode 100644
index 0000000..ca8bdf3
--- /dev/null
+++ b/librecrypt_next_algorithm.3
@@ -0,0 +1,96 @@
+.TH LIBRECRYPT_NEXT_ALGORITHM 3 LIBRECRYPT
+.SH NAME
+librecrypt_next_algorithm - Iterate over algorithms in a chained password hash string
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+.PP
+char *\fBlibrecrypt_next_algorithm\fP(char **\fIhash\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_next_algorithm ()
+function is called repeatedly to extract each hash
+algorithm (including its parameters) that shall be
+chained together according to a provided hash string.
+.PP
+On the initial call,
+.I *hash
+shall point to the password hash string.
+On each call the function updates
+.I *hash
+to its current parsing state. On each call except
+the final one, the string is modified by replacing
+the next instance of
+.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER
+(which is
+.BR \(aq>\(aq )
+with a null byte.
+.PP
+Each call returns the next algorithm substring.
+Once all algorithms have been extracted and returned,
+.IR NULL
+is returned.
+.PP
+Except once the function has returned
+.IR NULL ,
+overwriting the terminating null byte in the previously
+returned substring
+with
+.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER
+restores the original hash string.
+.PP
+Unless already stripped out before input, the last
+returned substring ends with the resulting hash.
+.PP
+.I hash
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_next_algorithm ()
+function returns a pointer to the next algorithm
+substring, or
+.IR NULL
+when no more algorithms remain.
+
+.SH ERRORS
+The
+.BR librecrypt_next_algorithm ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_next_algorithm ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_next_algorithm ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_next_algorithm ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_chain_length (3),
+.BR librecrypt_decompose_chain (3),
+.BR librecrypt_decompose_chain1 (3)
diff --git a/librecrypt_next_algorithm.c b/librecrypt_next_algorithm.c
new file mode 100644
index 0000000..b825582
--- /dev/null
+++ b/librecrypt_next_algorithm.c
@@ -0,0 +1,82 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline char *librecrypt_next_algorithm(char **hash);
+
+
+#else
+
+
+static void
+testcase_1(void)
+{
+ char hash[] = ">a$b>c$d>e$f$";
+ char *s = hash, *a;
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s != NULL);
+ EXPECT(!strcmp(a, ""));
+ EXPECT(!strcmp(s, "a$b>c$d>e$f$"));
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s != NULL);
+ EXPECT(!strcmp(a, "a$b"));
+ EXPECT(!strcmp(s, "c$d>e$f$"));
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s != NULL);
+ EXPECT(!strcmp(a, "c$d"));
+ EXPECT(!strcmp(s, "e$f$"));
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s == NULL);
+ EXPECT(!strcmp(a, "e$f$"));
+
+ EXPECT(librecrypt_next_algorithm(&s) == NULL);
+ EXPECT(s == NULL);
+}
+
+
+static void
+testcase_2(void)
+{
+ char hash[] = "a$b>c$d>e$f$>";
+ char *s = hash, *a;
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s != NULL);
+ EXPECT(!strcmp(a, "a$b"));
+ EXPECT(!strcmp(s, "c$d>e$f$>"));
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s != NULL);
+ EXPECT(!strcmp(a, "c$d"));
+ EXPECT(!strcmp(s, "e$f$>"));
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s != NULL);
+ EXPECT(!strcmp(a, "e$f$"));
+ EXPECT(!strcmp(s, ""));
+
+ EXPECT((a = librecrypt_next_algorithm(&s)));
+ EXPECT(s == NULL);
+ EXPECT(!strcmp(a, ""));
+
+ EXPECT(librecrypt_next_algorithm(&s) == NULL);
+ EXPECT(s == NULL);
+}
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+ testcase_1();
+ testcase_2();
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_realise_salts.3 b/librecrypt_realise_salts.3
new file mode 100644
index 0000000..8a743cc
--- /dev/null
+++ b/librecrypt_realise_salts.3
@@ -0,0 +1,161 @@
+.TH LIBRECRYPT_REALISE_SALTS 3 LIBRECRYPT
+.SH NAME
+librecrypt_realise_salts - Realise asterisk-encoded random salts in a settings string
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+ssize_t \fBlibrecrypt_realise_salts\fP(char *restrict \fIout_buffer\fP, size_t \fIsize\fP, const char *\fIsettings\fP,
+ ssize_t (*\fIrng\fP)(void *\fIout\fP, size_t \fIn\fP, void *\fIuser\fP), void *\fIuser\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+Static linking may require additional flags
+depending on enabled hash algorithms.
+
+.SH DESCRIPTION
+The
+.BR librecrypt_realise_salts ()
+function locates all asterisks followed by a
+non-negative decimal number in
+.IR settings
+and replaces each with ASCII-encoded random
+bytes (as many bytes as the number specifies),
+writing the result to
+.IR out_buffer .
+.PP
+.IR *rng ,
+or a default implementation (which tries to use
+cryptographic random number generation but has
+a non-cryptographic random number generation as
+a last resort) if
+.I rng
+is
+.IR NULL ,
+used to generate random bytes.
+.I *rng
+shall fill out with at least 1, and up to
+.IR n ,
+random bytes, and return the number of bytes
+written, or return -1 on failure.
+.PP
+The
+.I data
+argument is passed into the
+.IR *rng -parameter
+with the same name, as is and may be used by
+.I *rng
+for user-defined purposes.
+.PP
+If
+.I rng
+is
+.IR NULL ,
+a default cryptographic random number generator
+is used. The
+.I *rng
+function shall generate between 1 and
+.I n
+bytes (inclusively), write them to the front of
+.IR out ,
+and return the number of generated bytes, or
+-1 on failure.
+.PP
+On successful completion, if
+.I size
+is positive, the output is
+null byte-terminated even if truncated.
+.PP
+If
+.I rng
+is
+.IR NULL ,
+any encountered
+.BR EINTR
+is ignored; if it is encountered,
+.I errno
+will be set to
+.BR EINTR ,
+but is otherwise left unmodified unless the
+function fails due to one of the
+errors listed in the
+.B ERRORS
+section.
+.PP
+.I settings
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_realise_salts ()
+function returns the number of bytes that
+would have been written to
+.IR out_buffer
+if
+.I size
+was sufficiently large, or -1 on failure.
+
+.SH ERRORS
+The
+.BR librecrypt_realise_salts ()
+function will fail if:
+.TP
+.B ERANGE
+The expected return value is greater than {SSIZE_MAX}.
+.TP
+.B EINVAL
+Asterisk-encoding was used in an invalid context.
+.TP
+.B ENOSYS
+.I settings
+contain an algorithm that is not recognised or was
+disabled at compile-time.
+.PP
+Additionally, unless
+.I rng
+is
+.IR NULL ,
+.I *rng
+may set
+.IR errno .
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_realise_salts ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_realise_salts ()
+T} Async-signal safety T{
+AS-Safe /\fIrng\fP
+.br
+AS-Unsafe /\fI!rng\fP
+T}
+.TE
+.sp
+.PP
+.I rng
+is
+.RI non- NULL ,
+.I *rng
+may weaken the safety attributes.
+
+.SH HISTORY
+The
+.BR librecrypt_realise_salts ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_make_settings (3)
diff --git a/librecrypt_realise_salts.c b/librecrypt_realise_salts.c
new file mode 100644
index 0000000..a4204a6
--- /dev/null
+++ b/librecrypt_realise_salts.c
@@ -0,0 +1,151 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *settings,
+ ssize_t (*rng)(void *out, size_t n, void *user), void *user)
+{
+ const char *lut;
+ char pad;
+ int strict_pad, has_asterisk, nul_term = 0;
+ size_t i, j, n, min, ret = 0u;
+ size_t count, digit, q, r, left, mid, right;
+
+ if (size) {
+ nul_term = 1;
+ size -= 1u;
+ }
+
+ for (;;) {
+ for (; *settings == LIBRECRYPT_ALGORITHM_LINK_DELIMITER; settings++) {
+ if (size) {
+ *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER;
+ size -= 1u;
+ }
+ ret += 1u;
+ }
+
+ if (!*settings)
+ break;
+
+ has_asterisk = 0;
+ for (n = 0u; settings[n] && settings[n] != LIBRECRYPT_ALGORITHM_LINK_DELIMITER; n++)
+ if (settings[n] == '*')
+ has_asterisk = 1;
+
+ if (!has_asterisk) {
+ min = size < n ? size : n;
+ memcpy(out_buffer, settings, min);
+ out_buffer += min;
+ settings = &settings[n];
+ ret += n;
+ continue;
+ }
+
+ if (*settings == '*') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ lut = librecrypt_get_encoding(settings, n, &pad, &strict_pad, 0);
+ if (!lut)
+ return -1;
+ pad = strict_pad ? pad : '\0';
+
+ for (i = 0; i < n;) {
+ if (settings[i] != '*') {
+ if (size) {
+ *out_buffer++ = settings[i];
+ size -= 1u;
+ }
+ ret += 1u;
+ i++;
+ continue;
+ }
+
+ if (++i == n) {
+ if (size) {
+ *out_buffer++ = '*';
+ size -= 1u;
+ }
+ ret += 1u;
+ break;
+ }
+ if ('0' > settings[i] || settings[i] > '9') {
+ if (size) {
+ *out_buffer++ = '*';
+ size -= 1u;
+ }
+ ret += 1u;
+ continue;
+ }
+
+ count = 0;
+ for (; i < n && '0' <= settings[i] && settings[i] <= '9'; i++) {
+ digit = (size_t)(settings[i] - '0');
+ if (count > ((size_t)SSIZE_MAX - digit) / 10u)
+ goto erange;
+ count *= 10u;
+ count += digit;
+ }
+
+ q = count / 3u;
+ r = count % 3u;
+ mid = r ? r + 1u : 0u;
+ right = (!r ? 0u : pad ? 4u : r + 1u);
+ if (q > ((size_t)SSIZE_MAX - right) / 4u)
+ goto erange;
+ left = q * 4u;
+ ret += left + mid + right;
+
+ left += mid;
+ if (left > size) {
+ left = size;
+ mid = 0;
+ }
+ if (librecrypt_fill_with_random_(out_buffer, left, rng, user))
+ return -1;
+ for (j = 0; j < left; j++)
+ out_buffer[j] = lut[((unsigned char *)out_buffer)[j]];
+ if (mid)
+ out_buffer[j] = lut[((unsigned char *)out_buffer)[j] & (r == 1u ? ~15u : ~3u)];
+ out_buffer = &out_buffer[left];
+ size -= left;
+
+ if (right > size)
+ right = size;
+ for (j = 0; j < right; j++)
+ out_buffer[right] = pad;
+ out_buffer = &out_buffer[right];
+ size -= right;
+ }
+ }
+
+ if (nul_term)
+ *out_buffer = '\0';
+ if (ret > (size_t)SSIZE_MAX)
+ goto erange;
+ return (ssize_t)ret;
+
+erange:
+ errno = ERANGE;
+ return -1;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_rng_.c b/librecrypt_rng_.c
new file mode 100644
index 0000000..8b9f138
--- /dev/null
+++ b/librecrypt_rng_.c
@@ -0,0 +1,146 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+ssize_t
+librecrypt_rng_(void *out, size_t n, void *user)
+{
+ static unsigned int seed = 0u;
+ unsigned char *buf = out;
+ int v, fd, saved_errno = errno;
+ unsigned int state;
+ size_t i, min, ret = 0u;
+ ssize_t r;
+ struct timespec ts;
+#if defined(__linux__) && defined(AT_RANDOM)
+ const unsigned char *at_random;
+ uintptr_t at_random_addr;
+#endif
+
+ (void) user;
+
+ if (!n)
+ return 0;
+
+ if (n > (size_t)SSIZE_MAX)
+ n = (size_t)SSIZE_MAX;
+
+#if defined(__linux__)
+ for (;;) {
+ r = getrandom(buf, n, 0u);
+ if (r < 0) {
+ if (errno != EINTR)
+ break;
+ saved_errno = EINTR;
+ } else if (r > 0) {
+ ret += (size_t)r;
+ buf = &buf[(size_t)r];
+ n -= (size_t)r;
+ if (!n)
+ goto out;
+ } else {
+ break;
+ }
+ }
+#endif
+
+ for (;;) {
+ min = n < 256u ? n : 256u;
+ if (getentropy(buf, min)) {
+ if (errno != EINTR)
+ break;
+ saved_errno = EINTR;
+ } else {
+ ret += min;
+ buf = &buf[min];
+ n -= min;
+ if (!n)
+ goto out;
+ }
+ }
+
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
+ fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
+ if (fd >= 0) {
+ for (;;) {
+ r = read(fd, buf, n);
+ if (r < 0) {
+ if (errno != EINTR) {
+ close(fd);
+ break;
+ }
+ saved_errno = EINTR;
+ } else if (r > 0) {
+ ret += (size_t)r;
+ buf = &buf[(size_t)r];
+ n -= (size_t)r;
+ if (!n) {
+ close(fd);
+ goto out;
+ }
+ } else {
+ close(fd);
+ break;
+ }
+ }
+ }
+
+ state = seed;
+ if (!state) {
+ state = (unsigned int)rand();
+ state ^= (unsigned int)time(NULL);
+ if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
+ state ^= (unsigned int)ts.tv_sec;
+ state ^= (unsigned int)ts.tv_nsec;
+ }
+#if defined(__linux__) && defined(AT_RANDOM)
+ at_random_addr = (uintptr_t)getauxval(AT_RANDOM);
+ if (at_random_addr) {
+ at_random = (const void *)at_random_addr;
+ for (i = 0u; i < 16u; i++)
+ state ^= (unsigned int)at_random[i] << (i % sizeof(unsigned int) * 8u);
+ }
+ }
+#endif
+ for (i = 0u; i < n; i++) {
+ v = rand_r(&state);
+ v = (int)((uintmax_t)v * 255u / (uintmax_t)RAND_MAX);
+ buf[i] = (unsigned char)v;
+ }
+ ret += n;
+ seed = state;
+
+out:
+ errno = saved_errno;
+ return (ssize_t)ret;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ /* TODO test failure cases */
+
+ unsigned char buf1[1024u];
+ unsigned char buf2[sizeof(buf1)];
+ ssize_t n1, n2;
+
+ SET_UP_ALARM();
+
+ n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL);
+ n2 = librecrypt_rng_(buf2, sizeof(buf2), NULL);
+ EXPECT(n1 >= 128 && (size_t)n1 <= sizeof(buf1));
+ EXPECT(n2 >= 128 && (size_t)n2 <= sizeof(buf2));
+ EXPECT(memcmp(buf1, buf2, (size_t)(n1 < n2 ? n1 : n2)));
+
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_settings_prefix.3 b/librecrypt_settings_prefix.3
new file mode 100644
index 0000000..622af0e
--- /dev/null
+++ b/librecrypt_settings_prefix.3
@@ -0,0 +1,80 @@
+.TH LIBRECRYPT_SETTINGS_PREFIX 3 LIBRECRYPT
+.SH NAME
+librecrypt_settings_prefix - Get length of settings prefix in a password hash string
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+size_t \fBlibrecrypt_settings_prefix\fP(const char *\fIhash\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_settings_prefix ()
+function returns the number of bytes, from the front of
+.IR hash ,
+that make up the algorithm configuration.
+.PP
+The string is scanned for the delimiters
+.I LIBRECRYPT_HASH_COMPOSITION_DELIMITER
+(which is
+.BR \(aq$\(aq )
+and
+.I LIBRECRYPT_ALGORITHM_LINK_DELIMITER
+(which is
+.BR \(aq>\(aq ),
+and the returned value is one byte past the
+last occurrence, or 0 if none was found.
+.PP
+.I hash
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_settings_prefix ()
+function returns the number of bytes that make up the
+settings prefix. The return value may be 0, which shall
+be treated as any other valid return value.
+.PP
+.IR &hash[r] ,
+where
+.I r
+is the return value, points to the hash result proper.
+
+.SH ERRORS
+The
+.BR librecrypt_settings_prefix ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_settings_prefix ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_settings_prefix ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_settings_prefix ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_chain_length (3)
diff --git a/librecrypt_settings_prefix.c b/librecrypt_settings_prefix.c
new file mode 100644
index 0000000..edc7222
--- /dev/null
+++ b/librecrypt_settings_prefix.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline size_t librecrypt_settings_prefix(const char *hash);
+
+
+#else
+
+
+#define CHECK(PREFIX, SUFFIX)\
+ do {\
+ EXPECT(librecrypt_settings_prefix(PREFIX SUFFIX) == sizeof(PREFIX) - 1u);\
+ EXPECT(librecrypt_settings_prefix(PREFIX) == sizeof(PREFIX) - 1u);\
+ } while (0)
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+ CHECK("", "result");
+ CHECK(">", "double-des result");
+ CHECK("$", "x");
+ CHECK("y$", "x");
+ CHECK("y>", "x");
+ CHECK("a$b$c>d$e$", "x");
+ CHECK("a$b$c>", "x");
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_test_supported.3 b/librecrypt_test_supported.3
new file mode 100644
index 0000000..eb51e2d
--- /dev/null
+++ b/librecrypt_test_supported.3
@@ -0,0 +1,98 @@
+.TH LIBRECRYPT_TEST_SUPPORTED 3 LIBRECRYPT
+.SH NAME
+librecrypt_test_supported - Check whether an algorithm chain is supported
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+int \fBlibrecrypt_test_supported\fP(const char *\fIphrase\fP, size_t \fIlen\fP, int \fItext\fP, const char *\fIsettings\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+Static linking may require additional flags
+depending on enabled hash algorithms.
+
+.SH DESCRIPTION
+The
+.BR librecrypt_test_supported ()
+function checks whether a hash algorithm chain is
+supported for the given input, and that each
+algorithm configuration is valid.
+.PP
+The
+.I settings
+argument is a password hash string. Algorithm
+tuning parameters and the hash result may be omitted.
+.PP
+The
+.I phrase
+argument is the password to hash.
+It may contain null bytes and may be
+.IR NULL
+even if
+.I len
+is non-zero. If
+.I phrase
+is
+.IR NULL ,
+the function checks whether the specified length
+is supported and assumes an arbitrary byte
+sequence if
+.I text
+is zero. If
+.I text
+is non-zero, UTF-8 without null bytes is assumed.
+.PP
+If
+.I phrase
+is not
+.IR NULL ,
+.I text
+is ignored.
+.PP
+.I settings
+must not be
+.IR NULL .
+
+.SH RETURN VALUES
+The
+.BR librecrypt_test_supported ()
+function returns 1 if the configuration is supported,
+and 0 otherwise.
+
+.SH ERRORS
+The
+.BR librecrypt_test_supported ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_test_supported ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_test_supported ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_test_supported ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_hash_binary (3),
+.BR librecrypt_hash (3),
+.BR librecrypt_crypt (3)
diff --git a/librecrypt_test_supported.c b/librecrypt_test_supported.c
new file mode 100644
index 0000000..fad8bd9
--- /dev/null
+++ b/librecrypt_test_supported.c
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+int
+librecrypt_test_supported(const char *phrase, size_t len, int text, const char *settings)
+{
+ const struct algorithm *algo;
+ size_t n, prefix;
+ int has_next;
+
+ for (;;) {
+ has_next = 0;
+ for (n = 0u; settings[n]; n++) {
+ if (settings[n] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) {
+ has_next = 1;
+ break;
+ }
+ }
+
+ algo = librecrypt_find_first_algorithm_(settings, n);
+ if (!algo)
+ return 0;
+
+ prefix = (*algo->get_prefix)(settings, n);
+ if (has_next && prefix < n)
+ return 0;
+
+ if (!(*algo->test_supported)(phrase, len, text, settings, prefix))
+ return 0;
+
+ if (!has_next)
+ return 1;
+
+ phrase = NULL;
+ len = algo->hash_size;
+ text = 0;
+
+ settings = &settings[n];
+ settings++;
+ }
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ return 0;
+}
+
+
+#endif
+/* TODO test */
diff --git a/librecrypt_wipe.3 b/librecrypt_wipe.3
new file mode 100644
index 0000000..48f5829
--- /dev/null
+++ b/librecrypt_wipe.3
@@ -0,0 +1,57 @@
+.TH LIBRECRYPT_WIPE 3 LIBRECRYPT
+.SH NAME
+librecrypt_wipe - Securely erase a memory buffer
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+void \fBlibrecrypt_wipe\fP(void *\fIbuffer\fP, size_t \fIsize\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_wipe ()
+function securely erases
+.I size
+bytes from
+.IR buffer .
+
+.SH RETURN VALUES
+None.
+
+.SH ERRORS
+The
+.BR librecrypt_wipe ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_wipe ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_wipe ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_wipe ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_wipe_str (3)
diff --git a/librecrypt_wipe.c b/librecrypt_wipe.c
new file mode 100644
index 0000000..a704ef0
--- /dev/null
+++ b/librecrypt_wipe.c
@@ -0,0 +1,65 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=const"
+# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
+#endif
+
+
+void *(*volatile librecrypt_explicit_memset_____)(void *, int, size_t) = &memset;
+
+
+#if defined(__clang__) /* before __GNUC__ because that is also set in clang */
+# if __has_attribute(optnone)
+__attribute__((optnone))
+# endif
+#elif defined(__GNUC__)
+# if __GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ >= 40400
+__attribute__((optimize("-O0")))
+# endif
+#endif
+void
+librecrypt_wipe(void *buffer, size_t size)
+{
+ if (buffer && size) {
+ buffer = (*librecrypt_explicit_memset_____)(buffer, 0, size);
+#if defined(__GNUC__)
+ __asm__ volatile ("" :: "g" (buffer) : "memory");
+#else
+ (void) buffer;
+#endif
+ }
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ char *buf;
+
+ SET_UP_ALARM();
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wnonnull"
+#endif
+
+ librecrypt_wipe(NULL, 0u);
+ librecrypt_wipe(NULL, 4094u);
+ librecrypt_wipe((void *)(intptr_t)-1, 0u);
+
+ buf = malloc(256u);
+ memset(buf, 99, 256u);
+ librecrypt_wipe(buf, 256u);
+ free(buf); /* TODO should test memory is wiped */
+
+ return 0;
+}
+
+
+#endif
diff --git a/librecrypt_wipe_str.3 b/librecrypt_wipe_str.3
new file mode 100644
index 0000000..80aa301
--- /dev/null
+++ b/librecrypt_wipe_str.3
@@ -0,0 +1,61 @@
+.TH LIBRECRYPT_WIPE_STR 3 LIBRECRYPT
+.SH NAME
+librecrypt_wipe_str - Securely erase a string
+
+.SH SYNOPSIS
+.nf
+#include <librecrypt.h>
+
+void \fBlibrecrypt_wipe_str\fP(char *\fIstring\fP);
+.fi
+.PP
+Link with
+.IR -lrecrypt .
+
+.SH DESCRIPTION
+The
+.BR librecrypt_wipe_str ()
+function securely erases the string referenced by
+.IR string .
+.PP
+If
+.I string
+is
+.IR NULL ,
+no action is taken.
+
+.SH RETURN VALUES
+None.
+
+.SH ERRORS
+The
+.BR librecrypt_wipe_str ()
+function cannot fail.
+
+.SH ATTRIBUTES
+For an explanation of the terms used in this section, see
+.BR attributes (7).
+.PP
+.TS
+allbox;
+lb lb lb
+l l l.
+Interface Attribute Value
+T{
+.BR librecrypt_wipe_str ()
+T} Thread safety MT-Safe
+T{
+.BR librecrypt_wipe_str ()
+T} Async-signal safety AS-Safe
+.TE
+.sp
+
+.SH HISTORY
+The
+.BR librecrypt_wipe_str ()
+function was introduced in version 1.0 of
+.BR librecrypt .
+
+.SH SEE ALSO
+.BR librecrypt (7),
+.BR librecrypt_wipe (3)
diff --git a/librecrypt_wipe_str.c b/librecrypt_wipe_str.c
new file mode 100644
index 0000000..8c31cdc
--- /dev/null
+++ b/librecrypt_wipe_str.c
@@ -0,0 +1,39 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline void librecrypt_wipe_str(char *string);
+
+
+#else
+
+
+#define CHECK(TEXT)\
+ do {\
+ memset(buf, 0, sizeof(buf));\
+ stpcpy(buf, (TEXT));\
+ assert(!strcmp(buf, (TEXT)));\
+ librecrypt_wipe_str(buf);\
+ for (i = 0u; i < sizeof(buf); i++)\
+ EXPECT(!buf[i]);\
+ } while (0)
+
+
+int
+main(void)
+{
+ char buf[64u];
+ size_t i;
+ SET_UP_ALARM();
+ librecrypt_wipe_str(NULL);
+ CHECK("");
+ CHECK("hello");
+ CHECK("hello developer");
+ CHECK(" hello developer ");
+ CHECK("\1 hello developer \1");
+ return 0;
+}
+
+
+#endif
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..ad58f69
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,6 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
+
+FIX_INSTALL_NAME = :
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..1b172e6
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,6 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION)
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/librecrypt.$(LIBMAJOREXT)"
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..ed5ec8d
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,6 @@
+LIBEXT = dll
+LIBFLAGS = -shared
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = :