diff --git a/README.md b/README.md index d6830f3..df40db4 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ and output. ### Key management One of the core features of Enchive is the ability to derive an -asymmetric key pair from a passphrase (PBKDF2-like). This means you -can store your archive key in your brain! To access this feature, use -the `--derive` (`-d`) option with the `keygen` command. +asymmetric key pair from a passphrase. This means you can store your +archive key in your brain! To access this feature, use the `--derive` +(`-d`) option with the `keygen` command. $ enchive keygen --derive @@ -112,6 +112,26 @@ The process for decrypting a file: 6. Decrypt the ciphertext using ChaCha20. 7. Verify `HMAC(key, plaintext)`. +### Key derivation + +Enchive uses an scrypt-like algorithm for key derivation, requiring a +lot of random access memory. Derivation is controlled by a single +difficulty exponent *D*. Secret key derivation requires 512MB of +memory (D=29) by default, and protection key derivation requires 32MB +by default (D=25). The salt for the secret key is all zeros. + +1. Allocate a `(1 << D) + 32` byte buffer, *M*. +2. Compute `HMAC_SHA256(salt, passphrase)` and write this 32-byte + result to the beginning of *M*. +3. For each uninitialized 32-byte chunk in *M*, compute the SHA-256 + hash of the previous 32-byte chunk. +4. Initialize a byte pointer *P* to the last 32-byte chunk of *M*. +5. Compute the SHA-256 of the 32 bytes at *P*. +6. Take the first *D* bits of this hash and use this value to set a + new *P* pointing elsewhere into *M*. +7. Repeat from step 5 `1 << (D - 5)` times. +8. *P* points to the result. + ## Compilation To build on any unix-like system, run `make`. The resulting binary has diff --git a/config.h b/config.h index 3b7dfca..d36e3bc 100644 --- a/config.h +++ b/config.h @@ -16,11 +16,11 @@ #endif #ifndef ENCHIVE_KEY_DERIVE_ITERATIONS -# define ENCHIVE_KEY_DERIVE_ITERATIONS 20 +# define ENCHIVE_KEY_DERIVE_ITERATIONS 25 /* 32MB */ #endif #ifndef ENCHIVE_SECKEY_DERIVE_ITERATIONS -# define ENCHIVE_SECKEY_DERIVE_ITERATIONS 24 +# define ENCHIVE_SECKEY_DERIVE_ITERATIONS 29 /* 512MB */ #endif #ifndef ENCHIVE_OPTION_RANDOM_DEVICE diff --git a/src/enchive.c b/src/enchive.c index ac7aa63..98e20b4 100644 --- a/src/enchive.c +++ b/src/enchive.c @@ -333,6 +333,39 @@ secure_creat(const char *file) } #endif +/** + * Initialize a SHA-256 context for HMAC-SHA256. + * All message data will go into the resulting context. + */ +static void +hmac_init(SHA256_CTX *ctx, const u8 *key) +{ + int i; + u8 pad[SHA256_BLOCK_SIZE]; + sha256_init(ctx); + for (i = 0; i < SHA256_BLOCK_SIZE; i++) + pad[i] = key[i] ^ 0x36U; + sha256_update(ctx, pad, sizeof(pad)); +} + +/** + * Compute the final HMAC-SHA256 MAC. + * The key must be the same as used for initialization. + */ +static void +hmac_final(SHA256_CTX *ctx, const u8 *key, u8 *hash) +{ + int i; + u8 pad[SHA256_BLOCK_SIZE]; + sha256_final(ctx, hash); + sha256_init(ctx); + for (i = 0; i < SHA256_BLOCK_SIZE; i++) + pad[i] = key[i] ^ 0x5cU; + sha256_update(ctx, pad, sizeof(pad)); + sha256_update(ctx, hash, SHA256_BLOCK_SIZE); + sha256_final(ctx, hash); +} + /** * Derive a 32-byte key from null-terminated passphrase into buf. * Optionally provide an 8-byte salt. @@ -340,21 +373,50 @@ secure_creat(const char *file) static void key_derive(const char *passphrase, u8 *buf, int iexp, const u8 *salt) { + static const u8 empty[8] = {0}; size_t len = strlen(passphrase); - unsigned long i; SHA256_CTX ctx[1]; - unsigned long iterations = 1UL << iexp; - sha256_init(ctx); + unsigned long i; + unsigned long memlen = 1UL << iexp; + unsigned long mask = memlen - 1; + unsigned long iterations = 1UL << (iexp - 5); + u8 *memory, *memptr, *p; + + memory = malloc(memlen + SHA256_BLOCK_SIZE); + if (!memory) + fatal("not enough memory for key derivation"); + + if (!salt) + salt = empty; + hmac_init(ctx, salt); sha256_update(ctx, (u8 *)passphrase, len); - if (salt) - sha256_update(ctx, salt, 8); - sha256_final(ctx, buf); - for (i = 0; i < iterations; i++) { + hmac_final(ctx, salt, memory); + + for (p = memory + SHA256_BLOCK_SIZE; + p < memory + memlen + SHA256_BLOCK_SIZE; + p += SHA256_BLOCK_SIZE) { sha256_init(ctx); - sha256_update(ctx, buf, sizeof(buf)); + sha256_update(ctx, p - SHA256_BLOCK_SIZE, SHA256_BLOCK_SIZE); + sha256_update(ctx, (u8 *)passphrase, len); + sha256_final(ctx, p); + } + + memptr = memory + memlen - SHA256_BLOCK_SIZE; + for (i = 0; i < iterations; i++) { + unsigned long offset; + sha256_init(ctx); + sha256_update(ctx, memptr, SHA256_BLOCK_SIZE); sha256_update(ctx, (u8 *)passphrase, len); sha256_final(ctx, buf); + offset = ((unsigned long)buf[3] << 24 | + (unsigned long)buf[2] << 16 | + (unsigned long)buf[1] << 8 | + (unsigned long)buf[0] << 0); + memptr = memory + (offset & mask); } + + memcpy(buf, memptr, SHA256_BLOCK_SIZE); + free(memory); } /** @@ -423,39 +485,6 @@ compute_shared(u8 *sh, const u8 *s, const u8 *p) curve25519_donna(sh, s, p); } -/** - * Initialize a SHA-256 context for HMAC-SHA256. - * All message data will go into the resulting context. - */ -static void -hmac_init(SHA256_CTX *ctx, const u8 *key) -{ - int i; - u8 pad[SHA256_BLOCK_SIZE]; - sha256_init(ctx); - for (i = 0; i < SHA256_BLOCK_SIZE; i++) - pad[i] = key[i] ^ 0x36U; - sha256_update(ctx, pad, sizeof(pad)); -} - -/** - * Compute the final HMAC-SHA256 MAC. - * The key must be the same as used for initialization. - */ -static void -hmac_final(SHA256_CTX *ctx, const u8 *key, u8 *hash) -{ - int i; - u8 pad[SHA256_BLOCK_SIZE]; - sha256_final(ctx, hash); - sha256_init(ctx); - for (i = 0; i < SHA256_BLOCK_SIZE; i++) - pad[i] = key[i] ^ 0x5cU; - sha256_update(ctx, pad, sizeof(pad)); - sha256_update(ctx, hash, SHA256_BLOCK_SIZE); - sha256_final(ctx, hash); -} - /** * Encrypt from file to file using key/iv, aborting on any error. */ @@ -852,7 +881,7 @@ command_keygen(struct optparse *options) n = strtol(arg, &p, 10); if (errno || *p) fatal("invalid argument -- %s", arg); - if (n < 0 || n > 31) + if (n < 5 || n > 31) fatal("--derive argument must be 0 <= n <= 31 -- %s", arg); seckey_derive_iterations = n;