From d314d0cf4b92e1dd9c1f761498b06160dcf5e104 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Sat, 8 Apr 2023 20:12:35 +0200 Subject: Initial commit. An initial working implementation of the password generating algorithm of lesspass. Lacks any sort of documentation or usage info and simply prints the generated password on stdout. * lpass.c: Initial implementation of lesspass * Makefile: A just works version --- Makefile | 5 ++ README.md | 3 ++ lpass.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 lpass.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b6fd30e --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +CC = gcc +CFLAGS = -O2 -Wall + +lpass: lpass.c + $(CC) $(CFLAGS) -lcrypto -o lpass lpass.c diff --git a/README.md b/README.md new file mode 100644 index 0000000..e385979 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# lpass + +A simple implementation of [lesspass](https://github.com/lesspass/lesspass) in C. diff --git a/lpass.c b/lpass.c new file mode 100644 index 0000000..2193359 --- /dev/null +++ b/lpass.c @@ -0,0 +1,163 @@ +#include +#include + +#include +#include +#include + +#define MAX_BUF 1024 +#define ENTROPY_ITERATIONS 100000 +#define ENTROPY_KEY_LENGTH 32 + +#define CHAR_SUBSET_LOWER "abcdefghijklmnopqrstuvwxyz" +#define CHAR_SUBSET_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define CHAR_SUBSET_DIGITS "0123456789" +#define CHAR_SUBSET_SYMBOLS "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" + +const char *CHAR_SET = (CHAR_SUBSET_LOWER + CHAR_SUBSET_UPPER + CHAR_SUBSET_DIGITS + CHAR_SUBSET_SYMBOLS); + +static BIGNUM * +calc_entropy(const char *site, + const char *login, + uint64_t counter, + const char *master_pass, + int passlen) +{ + char salt[MAX_BUF + 1]; + memset(salt, 0, sizeof(salt)); + int saltlen = snprintf(salt, MAX_BUF, "%s%s%lx", site, login, counter); + if (saltlen > MAX_BUF) { + return 0; + } + + unsigned char key[ENTROPY_KEY_LENGTH]; + memset(key, 0, sizeof(key)); + int status = PKCS5_PBKDF2_HMAC(master_pass, passlen, + (const unsigned char *) salt, saltlen, + ENTROPY_ITERATIONS, + EVP_sha256(), + ENTROPY_KEY_LENGTH, + key); + if (status == 0) { + return 0; + } + + /* NULL as last arg to allocate a new BIGNUM */ + return BN_bin2bn(key, ENTROPY_KEY_LENGTH, NULL); +} + +static int +consume_entropy(char *generated_pass, BIGNUM *entropy, const char *charset, int maxlen) +{ + int retval = 1; + BN_CTX *ctx = BN_CTX_new(); + BIGNUM *bn_charsetlen = BN_new(); + BIGNUM *bn_remainder = BN_new(); + int charsetlen = strlen(charset); + BN_set_word(bn_charsetlen, charsetlen); + for (int passlen = strlen(generated_pass); + passlen < maxlen; + passlen++) { + BN_div(entropy, bn_remainder, entropy, bn_charsetlen, ctx); + int remainder = BN_get_word(bn_remainder); + if (remainder >= charsetlen) { + retval = 0; + goto consume_entropy_cleanup; + } + generated_pass[passlen] = charset[remainder]; + } +consume_entropy_cleanup: + BN_free(bn_remainder); + BN_free(bn_charsetlen); + BN_CTX_free(ctx); + return retval; +} + +static int +insert_str_pseudo_randomly(char *generated_pass, BIGNUM *entropy, const char *s) +{ + int retval = 1; + char buf[MAX_BUF + 1]; + uint64_t passlen = (uint64_t) strlen(generated_pass); + BN_CTX *ctx = BN_CTX_new(); + BIGNUM *bn_passlen = BN_new(); + BIGNUM *bn_remainder = BN_new(); + for (char c = *s; *s != '\0'; c = *(++s)) { + if (BN_set_word(bn_passlen, passlen) == 0) { + retval = 0; + goto insert_str_pseudo_randomly_cleanup; + } + BN_div(entropy, bn_remainder, entropy, bn_passlen, ctx); + uint64_t remainder = BN_get_word(bn_remainder); + /* + * Idea here is to add the char `c` at position `remainder` in `generated_pass`. + * 1. Copy the part that would need to be shifted into `buf`. + */ + memset(buf, 0, sizeof(buf)); + strncpy(buf, &generated_pass[remainder], passlen - remainder); + /* + * 2. Add new character, then copy `buf` back into `generated_pass`. + */ + generated_pass[remainder] = c; + strncpy(&generated_pass[remainder + 1], buf, passlen - remainder); + passlen++; + } +insert_str_pseudo_randomly_cleanup: + BN_free(bn_remainder); + BN_free(bn_passlen); + BN_CTX_free(ctx); + return retval; +} + +static void +render_pass(BIGNUM *entropy, char *out, int length) +{ + /* XXX: FIXME: implement charset restrictions */ + int num_charsets = 4; + consume_entropy(out, entropy, CHAR_SET, length - num_charsets); + + /* + * After generating the initial password, add one character of + * each charset to ensure at least one is there. + */ + char str_to_add[num_charsets + 1]; + memset(str_to_add, 0, sizeof(str_to_add)); + consume_entropy(&str_to_add[0], entropy, CHAR_SUBSET_LOWER, 1); + consume_entropy(&str_to_add[1], entropy, CHAR_SUBSET_UPPER, 1); + consume_entropy(&str_to_add[2], entropy, CHAR_SUBSET_DIGITS, 1); + consume_entropy(&str_to_add[3], entropy, CHAR_SUBSET_SYMBOLS, 1); + insert_str_pseudo_randomly(out, entropy, str_to_add); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 5) { + return 1; + } + + const char *master_pass = argv[1]; + int passlen = strlen(master_pass); + const char *site = argv[2]; + const char *login = argv[3]; + int length = 0; + if (sscanf(argv[4], "%d", &length) == 0) { + return 1; + } + + uint64_t counter = 1; + BIGNUM *entropy = calc_entropy(site, login, counter, master_pass, passlen); + if (entropy == NULL) { + return 1; + } + + char generated_pass[length + 1]; + memset(generated_pass, 0, sizeof(generated_pass)); + render_pass(entropy, generated_pass, length); + printf("%s\n", generated_pass); + BN_free(entropy); + return 0; +} -- cgit v1.2.3