diff --git a/README.md b/README.md index 8c03f3b..ce252f8 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,16 @@ usage: pegh [options...] password -e encrypt input to output, default mode -d decrypt input to output -i file to use for input, default stdin - -o file to use for output, if set and there is an error and append - is not set, we try to delete this file before exiting, - default stdout + -o file to use for output, default stdout -a append to -o instead of truncate - -b maximum megabytes of ram to use per read/write buffer, so while - decrypting/encrypting twice this will be used, but these are + -c maximum megabytes of ram to use per encrypted chunk, so while + decrypting/encrypting twice this will be used, the same + amount will be needed for decryption as encryption and is + saved in the file format, so decryption will fail if this + isn't set high enough, these are only allocated after scrypt is finished so max usage will be - the highest of these only, not both combined, default: 16 + the highest of these only, not both combined, + max: 2047, default: 16 -m maximum megabytes of ram to use when deriving key from password with scrypt, applies for encryption AND decryption, must almost linearly scale with -N, if too low operation will fail, @@ -65,18 +67,17 @@ pegh file format pegh implements a simple versioned file format so encryption parameters can change in the future. Numbers here are inclusive 0-based byte array indices, 0th byte is always version number, everything else depends on version number, currently only version 0 exists. -Version 0, scrypt key derivation, aes-256-gcm encryption, 51 byte header, 16 byte footer: +Version 0, scrypt key derivation, aes-256-gcm encryption, 43 byte header, 16 byte auth tag per chunk. The 12-byte IV for the first chunk is 0, and is incremented by 1 for each successive chunk, if it ever rolls back over to 0 encryption should be aborted (chunk size should be increased). -| indices | format | value interpretation | -|--------------|---------------------------------------------|--------------------------------| -| 0 | 8 bit unsigned byte | pegh file format version | -| 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter | -| 5 | 8 bit unsigned byte | scrypt r parameter | -| 6 | 8 bit unsigned byte | scrypt p parameter | -| 7-38 | 32 randomly generated bytes | scrypt key derivation seed | -| 39-50 | 12 randomly generated bytes | AES-256-GCM IV | -| 51-X | any number of bytes | AES-256-GCM encrypted data | -| (X+1)-(X+16) | 16 bytes, always last 16 bytes in file | AES-256-GCM authentication tag | +| indices | format | value interpretation | +|--------------|---------------------------------------------|-----------------------------------------| +| 0 | 8 bit unsigned byte | pegh file format version | +| 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter | +| 5 | 8 bit unsigned byte | scrypt r parameter | +| 6 | 8 bit unsigned byte | scrypt p parameter | +| 7-10 | 32 bit unsigned integer in big endian order | aes encrypted chunk size | +| 11-42 | 32 randomly generated bytes | scrypt key derivation seed | +| 43+end | any number of chunks, chunk_size + 16 long | chunks followed by AES-256-GCM auth tag | License ------- diff --git a/pegh.c b/pegh.c index 4efe0df..9a8448c 100644 --- a/pegh.c +++ b/pegh.c @@ -27,6 +27,7 @@ #include #include #include +#include /* * tweak default scrypt hardness params here @@ -47,20 +48,21 @@ * * 0th byte is always version number, everything else depends on version number * - * |---------------------------------------------------------------------------------------------| - * | Version 0, scrypt key derivation, aes-256-gcm encryption, 51 byte header, 16 byte footer | - * |--------------|---------------------------------------------|--------------------------------| - * | indices | format | value interpretation | - * |--------------|---------------------------------------------|--------------------------------| - * | 0 | 8 bit unsigned byte | pegh file format version | - * | 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter | - * | 5 | 8 bit unsigned byte | scrypt r parameter | - * | 6 | 8 bit unsigned byte | scrypt p parameter | - * | 7-38 | 32 randomly generated bytes | scrypt key derivation seed | - * | 39-50 | 12 randomly generated bytes | AES-256-GCM IV | - * | 51-X | any number of bytes | AES-256-GCM encrypted data | - * | (X+1)-(X+16) | 16 bytes, always last 16 bytes in file | AES-256-GCM authentication tag | - * |---------------------------------------------------------------------------------------------| + * |------------------------------------------------------------------------------------------------------| + * | Version 0, scrypt key derivation, aes-256-gcm encryption, 43 byte header, 16 byte auth tag per chunk | + * | The 12-byte IV for the first chunk is 0, and is incremented by 1 for each successive chunk, if it | + * | ever rolls back over to 0 encryption should be aborted (chunk size should be increased). + * |--------------|---------------------------------------------|-----------------------------------------| + * | indices | format | value interpretation | + * |--------------|---------------------------------------------|-----------------------------------------| + * | 0 | 8 bit unsigned byte | pegh file format version | + * | 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter | + * | 5 | 8 bit unsigned byte | scrypt r parameter | + * | 6 | 8 bit unsigned byte | scrypt p parameter | + * | 7-10 | 32 bit unsigned integer in big endian order | aes encrypted chunk size | + * | 11-42 | 32 randomly generated bytes | scrypt key derivation seed | + * | 43+end | any number of chunks, chunk_size + 16 long | chunks followed by AES-256-GCM auth tag | + * |------------------------------------------------------------------------------------------------------| */ /* don't touch below here unless you know what you are doing */ @@ -70,15 +72,230 @@ /* 256 bit key required for AES-256 */ #define KEY_LEN 32 -/* 1 for file format version, 4 for N, 1 for r, 1 for p */ -#define PRE_SALT_LEN 7 +/* 1 for file format version, 4 for N, 1 for r, 1 for p, 4 for block/buffer size */ +#define PRE_SALT_LEN 11 /* from libsodium's crypto_pwhash_scryptsalsa208sha256_SALTBYTES */ #define SALT_LEN 32 /* AES-GCM should only ever have an IV_LEN of 12 */ #define IV_LEN 12 #define GCM_TAG_LEN 16 -#define SALT_IV_LEN (SALT_LEN+IV_LEN) +/* + * returns 1 on success, 0 on failure + * + * these will be read from: + * plaintext + * plaintext_len + * key must be length KEY_LEN + * iv must be length IV_LEN + * + * these will be written into: + * ciphertext must have the capacity of at least plaintext_len + * tag must have the capacity of at least GCM_TAG_LEN + */ +int gcm_encrypt(const unsigned char *plaintext, const int plaintext_len, + const unsigned char *key, + const unsigned char *iv, + unsigned char *ciphertext, + unsigned char *tag + ) +{ + EVP_CIPHER_CTX *ctx; + int ciphertext_written, ret = 0; + + do { + /* Create and initialise the context */ + if(!(ctx = EVP_CIPHER_CTX_new())) + break; + + /* Initialise the encryption operation. */ + if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) + break; + + /* Setting IV length is not necessary because the default of 12 bytes (96 bits) will be used + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, NULL)) + break; + */ + + /* Initialise key and IV */ + if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) + break; + + /* + * Provide the message to be encrypted, and obtain the encrypted output. + * EVP_EncryptUpdate can be called multiple times if necessary + */ + if(1 != EVP_EncryptUpdate(ctx, ciphertext, &ciphertext_written, plaintext, plaintext_len)) + break; + + /* if this isn't true, GCM is broken, we probably don't need to check... + if(ciphertext_written != plaintext_len) { + if(NULL != err) + fprintf(err, "ciphertext_written (%d) != plaintext_len (%d)\n", ciphertext_written, plaintext_len); + break; + } + */ + + /* + * Finalise the encryption. Normally ciphertext bytes may be written at + * this stage, but this does not occur in GCM mode + */ + if(1 != EVP_EncryptFinal_ex(ctx, NULL, &ciphertext_written)) + break; + + /* Get the tag */ + ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, GCM_TAG_LEN, tag); + } while(0); + + /* Clean up */ + if(ctx) + EVP_CIPHER_CTX_free(ctx); + + return ret; +} + +/* + * returns 1 on success, 0 on failure + * + * these will be read from: + * ciphertext + * ciphertext_len + * key must be length KEY_LEN + * iv must be length IV_LEN + * tag must be length GCM_TAG_LEN + * + * these will be written into: + * plaintext must have the capacity of at least ciphertext_len + */ +int gcm_decrypt(const unsigned char *ciphertext, const int ciphertext_len, + const unsigned char *key, + const unsigned char *iv, + unsigned char *tag, + unsigned char *plaintext + ) +{ + EVP_CIPHER_CTX *ctx; + int plaintext_written, ret = 0; + + do { + /* Create and initialise the context */ + if(!(ctx = EVP_CIPHER_CTX_new())) + break; + + /* Initialise the decryption operation. */ + if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) + break; + + /* Setting IV length is not necessary because the default of 12 bytes (96 bits) will be used + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, NULL)) + break; + */ + + /* Initialise key and IV */ + if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) + break; + + /* + * Provide the message to be decrypted, and obtain the plaintext output. + * EVP_DecryptUpdate can be called multiple times if necessary + */ + if(!EVP_DecryptUpdate(ctx, plaintext, &plaintext_written, ciphertext, ciphertext_len)) + break; + + /* if this isn't true, GCM is broken, we probably don't need to check... + if(plaintext_written != ciphertext_len) { + if(NULL != err) + fprintf(err, "plaintext_written (%d) != ciphertext_len (%d)\n", plaintext_written, ciphertext_len); + break; + } + */ + + /* Set expected tag value. Works in OpenSSL 1.0.1d and later */ + if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, GCM_TAG_LEN, tag)) + break; + + /* + * Finalise the decryption. A return value of 1 indicates success, + * return value of 0 is a failure - the plaintext is not trustworthy. + */ + ret = EVP_DecryptFinal_ex(ctx, NULL, &plaintext_written); + } while(0); + + /* Clean up */ + if(ctx) + EVP_CIPHER_CTX_free(ctx); + + return ret; +} + +/* returns 1 on success, 0 on failure */ +int iv_increment_forbid_zero(unsigned char *n, const size_t nlen, FILE *err) +{ + int all_zero = 0; + size_t i = 0U; + uint_fast16_t c = 1U; + + for (; i < nlen; ++i) { + c += (uint_fast16_t) n[i]; + if(c != 0) + all_zero = 1; + n[i] = (unsigned char) c; + c >>= 8; + } + if(all_zero == 0 && NULL != err) + fprintf(err, "aborting before IV reuse could happen, increase block_size ?\n"); + return all_zero; +} + +/* returns 1 on success, 0 on failure */ +int gcm_encrypt_stream(const unsigned char *key, unsigned char *iv, size_t buffer_size, + unsigned char *plaintext, unsigned char *ciphertext, + FILE *in, FILE *out, FILE *err + ) { + size_t plaintext_read; + + while ((plaintext_read = fread(plaintext, 1, buffer_size, in)) > 0) { + + if(1 != gcm_encrypt(plaintext, (int) plaintext_read, key, iv, ciphertext, ciphertext + plaintext_read)) + return 0; + + if(1 != iv_increment_forbid_zero(iv, IV_LEN, err)) + return 0; + + fwrite(ciphertext, 1, plaintext_read + GCM_TAG_LEN, out); + } + return 1; +} + +/* returns 1 on success, 0 on failure */ +int gcm_decrypt_stream(const unsigned char *key, unsigned char *iv, size_t buffer_size, + unsigned char *plaintext, unsigned char *ciphertext, + FILE *in, FILE *out, FILE *err + ) { + size_t ciphertext_read; + + buffer_size += GCM_TAG_LEN; + + while ((ciphertext_read = fread(ciphertext, 1, buffer_size, in)) > 0) { + + if(ciphertext_read < GCM_TAG_LEN) { + if(NULL != err) + fprintf(err, "File too small for decryption, truncated?\n"); + return 0; + } + + ciphertext_read -= GCM_TAG_LEN; + + if(1 != gcm_decrypt(ciphertext, (int) ciphertext_read, key, iv, ciphertext + ciphertext_read, plaintext)) + return 0; + + if(1 != iv_increment_forbid_zero(iv, IV_LEN, err)) + return 0; + + fwrite(plaintext, 1, ciphertext_read, out); + } + return 1; +} /* * reads buffer_size at a time from in, encrypts with AES-256-GCM, and writes them to out @@ -92,16 +309,17 @@ * in/out must be set * err can be NULL in which case no messages are printed */ -int gcm_encrypt(const unsigned char *key, const unsigned char *iv, size_t buffer_size, +int gcm_stream(const unsigned char *key, size_t buffer_size, + int decrypt, FILE *in, FILE *out, FILE *err ) { + /* this is ok because the random salt makes the key random, and we increment this for encryption operation */ + unsigned char iv[IV_LEN] = {0}; /* these are actually mallocd and freed */ - EVP_CIPHER_CTX *ctx = NULL; unsigned char *plaintext = NULL, *ciphertext = NULL; - int exit_code = 0, ciphertext_written; - size_t plaintext_read; + int exit_code = 0; if(buffer_size > INT_MAX) { /* this is because we read up to buffer_size at once, and then send that value to openssl which uses int instead of size_t */ @@ -110,223 +328,30 @@ int gcm_encrypt(const unsigned char *key, const unsigned char *iv, size_t buffer return 0; } - do { - plaintext = malloc(buffer_size); - if(!plaintext) { - if(NULL != err) - fprintf(err, "plaintext memory allocation failed\n"); - break; - } - ciphertext = malloc(buffer_size); - if(!ciphertext) { - if(NULL != err) - fprintf(err, "ciphertext memory allocation failed\n"); - break; - } - - /* Create and initialise the context */ - if(!(ctx = EVP_CIPHER_CTX_new())) - break; - - /* Initialise the encryption operation. */ - if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) - break; - - /* Setting IV length is not necessary because the default of 12 bytes (96 bits) will be used - if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, NULL)) - break; - */ - - /* Initialise key and IV */ - if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) - break; - - /* - * Provide the message to be encrypted, and obtain the encrypted output. - * EVP_EncryptUpdate can be called multiple times if necessary - * - */ - while ((plaintext_read = fread(plaintext, 1, buffer_size, in)) > 0) { - - if(1 != EVP_EncryptUpdate(ctx, ciphertext, &ciphertext_written, plaintext, (int) plaintext_read)) - goto fail; - - if(((size_t) ciphertext_written) != plaintext_read) { - if(NULL != err) - fprintf(err, "ciphertext_written (%d) != plaintext_read (%lu)\n", ciphertext_written, (unsigned long) plaintext_read); - goto fail; - } - - fwrite(ciphertext, 1, (size_t) ciphertext_written, out); - } - - /* - * Finalise the encryption. Normally ciphertext bytes may be written at - * this stage, but this does not occur in GCM mode - */ - if(1 != EVP_EncryptFinal_ex(ctx, NULL, &ciphertext_written)) - break; - - /* Get the tag, go ahead and re-use ciphertext as it's not needed anymore */ - if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, GCM_TAG_LEN, ciphertext)) - break; - - fwrite(ciphertext, 1, GCM_TAG_LEN, out); - - /* success! */ - exit_code = 1; - } while(0); - fail: - - if(plaintext) - free(plaintext); - if(ciphertext) - free(ciphertext); - - if(ctx) - EVP_CIPHER_CTX_free(ctx); - - if(NULL != err && exit_code != 1) { - /* print openssl errors */ - ERR_print_errors_fp(err); - fprintf(err, "encryption failed\n"); - } - - return exit_code; -} - -/* - * reads buffer_size at a time from out, decrypts with AES-256-GCM, and writes them to out - * - * assumes the GCM authentication tag is the last 16 bytes and checks that too, it's the - * responsibility of the calling function to then go back in time and not use the invalid - * bytes already written to out... - * - * returns 1 on success, 0 on failure - * - * key must be length KEY_LEN - * iv must be length IV_LEN - * - * buffer_size must be non-zero, this function will allocate this twice + 16 bytes for tag - * in/out must be set - * err can be NULL in which case no messages are printed - */ -int gcm_decrypt(const unsigned char *key, const unsigned char *iv, size_t buffer_size, - FILE *in, FILE *out, FILE *err -) -{ - /* these are actually mallocd and freed */ - EVP_CIPHER_CTX *ctx = NULL; - unsigned char *plaintext = NULL, *ciphertext = NULL; - /* these are simply pointers into ciphertext */ - unsigned char *tag, *ciphertext_read_zone; - - int exit_code = 0, plaintext_written; - size_t ciphertext_read; - - if(buffer_size > INT_MAX) { - /* this is because we read up to buffer_size at once, and then send that value to openssl which uses int instead of size_t */ + plaintext = malloc(buffer_size); + if(!plaintext) { if(NULL != err) - fprintf(err, "due to openssl API buffer_size can at most be %d\n", INT_MAX); + fprintf(err, "plaintext memory allocation failed\n"); + return 0; + } + ciphertext = malloc(buffer_size + GCM_TAG_LEN); + if(!ciphertext) { + if(NULL != err) + fprintf(err, "ciphertext memory allocation failed\n"); + free(plaintext); return 0; } - do { - plaintext = malloc(buffer_size); - if(!plaintext) { - if(NULL != err) - fprintf(err, "plaintext memory allocation failed\n"); - break; - } - ciphertext = malloc(buffer_size + GCM_TAG_LEN); - if(!ciphertext) { - if(NULL != err) - fprintf(err, "ciphertext memory allocation failed\n"); - break; - } + exit_code = decrypt ? gcm_decrypt_stream(key, iv, buffer_size, plaintext, ciphertext, in, out, err) : + gcm_encrypt_stream(key, iv, buffer_size, plaintext, ciphertext, in, out, err); - tag = ciphertext; - ciphertext_read_zone = ciphertext + GCM_TAG_LEN; - - /* there must be *at least* 16 byte gcm tag / footer */ - ciphertext_read = fread(tag, 1, GCM_TAG_LEN, in); - if(ciphertext_read != GCM_TAG_LEN) { - if(NULL != err) - fprintf(err, "File too small for decryption, no footer/tag?\n"); - break; - } - - /* Create and initialise the context */ - if(!(ctx = EVP_CIPHER_CTX_new())) - break; - - /* Initialise the decryption operation. */ - if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) - break; - - /* Setting IV length is not necessary because the default of 12 bytes (96 bits) will be used - if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, NULL)) - break; - */ - - /* Initialise key and IV */ - if(1 != EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) - break; - - /* - * Provide the message to be decrypted, and obtain the plaintext output. - * EVP_DecryptUpdate can be called multiple times if necessary - */ - do { - ciphertext_read = fread(ciphertext_read_zone, 1, buffer_size, in); - - /* decrypt the bytes previously saved in tag plus ones currently read except the last 16 bytes */ - if(1 != EVP_DecryptUpdate(ctx, plaintext, &plaintext_written, tag, (int) ciphertext_read)) - goto fail; - /* now save the unused last 16 bytes in tag */ - memcpy(tag, ciphertext_read_zone + ciphertext_read - GCM_TAG_LEN, GCM_TAG_LEN); - - if(((size_t) plaintext_written) != ciphertext_read) { - if(NULL != err) - fprintf(err, "plaintext_written (%d) != plaintext_read (%lu)\n", plaintext_written, (unsigned long) ciphertext_read); - goto fail; - } - - fwrite(plaintext, 1, (size_t) plaintext_written, out); - } while(buffer_size == ciphertext_read); - - - /* Set expected tag value. Works in OpenSSL 1.0.1d and later */ - if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, GCM_TAG_LEN, ciphertext)) - break; - - /* - * Finalise the decryption. A return value of 1 indicates success, - * return value of 0 is a failure - the plaintext is not trustworthy. - */ - if(1 != EVP_DecryptFinal_ex(ctx, NULL, &plaintext_written)) { - if(NULL != err) - fprintf(err, "integrity check failed\n"); - break; - } - - /* success! */ - exit_code = 1; - } while(0); - fail: - - if(plaintext) - free(plaintext); - if(ciphertext) - free(ciphertext); - - if(ctx) - EVP_CIPHER_CTX_free(ctx); + free(plaintext); + free(ciphertext); if(NULL != err && exit_code != 1) { /* print openssl errors */ ERR_print_errors_fp(err); - fprintf(err, "decryption failed\n"); + fprintf(err, "%scryption failed\n", decrypt ? "de" : "en"); } return exit_code; @@ -353,52 +378,62 @@ int scrypt_derive_key(char *password, return 1; } +/* buf must be at least 4 bytes */ +uint32_t read_uint32_big_endian(const unsigned char *buf) { + return (uint32_t) ((buf[0] & 0xFF) << 24) + | (uint32_t) ((buf[1] & 0xFF) << 16) + | (uint32_t) ((buf[2] & 0xFF) << 8) + | (uint32_t) (buf[3] & 0xFF); +} + +/* buf must be at least 4 bytes */ +void write_uint32_big_endian(uint32_t val, unsigned char *buf) { + buf[0] = (unsigned char) ((val >> 24) & 0xFF); + buf[1] = (val >> 16) & 0xFF; + buf[2] = (val >> 8) & 0xFF; + buf[3] = val & 0xFF; +} + /* returns 1 on success, 0 on failure */ int pegh_encrypt(char *password, uint32_t scrypt_max_mem_mb, size_t buffer_size, FILE *in, FILE *out, FILE *err, uint32_t N, uint8_t r, uint8_t p) { - unsigned char key[KEY_LEN] = {0}, salt[SALT_IV_LEN] = {0}; - /* this is simply a pointer into salt */ - const unsigned char *iv = salt + SALT_LEN; + unsigned char key[KEY_LEN] = {0}, salt[SALT_LEN] = {0}; /* first write the version and parameters */ salt[0] = 0; - salt[1] = (unsigned char) ((N >> 24) & 0xFF); - salt[2] = (N >> 16) & 0xFF; - salt[3] = (N >> 8) & 0xFF; - salt[4] = N & 0xFF; + write_uint32_big_endian(N, salt+1); salt[5] = r; salt[6] = p; + write_uint32_big_endian((uint32_t) buffer_size, salt+7); fwrite(salt, 1, PRE_SALT_LEN, out); - /* generate random salt+iv, then write it out */ - if (RAND_bytes(salt, SALT_IV_LEN) <= 0) { + /* generate random salt, then write it out */ + if (RAND_bytes(salt, SALT_LEN) <= 0) { if(NULL != err) { - fprintf(err, "random salt+iv generation error\n"); + fprintf(err, "random salt generation error\n"); ERR_print_errors_fp(err); } return 0; } - fwrite(salt, 1, SALT_IV_LEN, out); + fwrite(salt, 1, SALT_LEN, out); if(1 != scrypt_derive_key(password, scrypt_max_mem_mb, N, r, p, salt, key, err)) return 0; - return gcm_encrypt(key, iv, buffer_size, in, out, err); + return gcm_stream(key, buffer_size, 0, in, out, err); } /* returns 1 on success, 0 on failure */ int pegh_decrypt(char *password, - uint32_t scrypt_max_mem_mb, size_t buffer_size, + uint32_t scrypt_max_mem_mb, size_t max_buffer_size, FILE *in, FILE *out, FILE *err) { - unsigned char key[KEY_LEN] = {0}, salt[SALT_IV_LEN] = {0}; - /* this is simply a pointer into salt */ - const unsigned char *iv = salt + SALT_LEN; + unsigned char key[KEY_LEN] = {0}, salt[SALT_LEN] = {0}; - size_t header_read; + size_t header_read, buffer_size; uint32_t N; uint8_t r, p; @@ -415,16 +450,19 @@ int pegh_decrypt(char *password, fprintf(err, "unsupported file format version %d, we only support version 0\n", salt[0]); return 0; } - N = (uint32_t) ((salt[1] & 0xFF) << 24) - | (uint32_t) ((salt[2] & 0xFF) << 16) - | (uint32_t) ((salt[3] & 0xFF) << 8) - | (uint32_t) (salt[4] & 0xFF); + N = read_uint32_big_endian(salt+1); r = salt[5]; p = salt[6]; + buffer_size = read_uint32_big_endian(salt+7); + if(buffer_size > max_buffer_size) { + if(NULL != err) + fprintf(err, "memory required to decrypt file is %lu MB but only %lu MB allowed, increase -c\n", buffer_size / 1024 / 1024, max_buffer_size / 1024 / 1024); + return 0; + } - /* next read salt+iv */ - header_read = fread(salt, 1, SALT_IV_LEN, in); - if(header_read != SALT_IV_LEN) { + /* next read salt */ + header_read = fread(salt, 1, SALT_LEN, in); + if(header_read != SALT_LEN) { if(NULL != err) fprintf(err, "File too small for decryption, invalid header?\n"); return 0; @@ -433,7 +471,7 @@ int pegh_decrypt(char *password, if(1 != scrypt_derive_key(password, scrypt_max_mem_mb, N, r, p, salt, key, err)) return 0; - return gcm_decrypt(key, iv, buffer_size, in, out, err); + return gcm_stream(key, buffer_size, 1, in, out, err); } int help(int exit_code) { @@ -443,20 +481,22 @@ usage: pegh [options...] password\n\ -e encrypt input to output, default mode\n\ -d decrypt input to output\n\ -i file to use for input, default stdin\n\ - -o file to use for output, if set and there is an error and append\n\ - is not set, we try to delete this file before exiting,\n"); + -o file to use for output, default stdout\n"); fprintf(stderr, "\ - default stdout\n\ -a append to -o instead of truncate\n\ - -b maximum megabytes of ram to use per read/write buffer, so while\n\ - decrypting/encrypting twice this will be used, but these are\n"); + -c maximum megabytes of ram to use per encrypted chunk, so while\n\ + decrypting/encrypting twice this will be used, the same\n\ + amount will be needed for decryption as encryption and is\n\ + saved in the file format, so decryption will fail if this\n\ + isn't set high enough, these are\n"); fprintf(stderr, "\ only allocated after scrypt is finished so max usage will be\n\ - the highest of these only, not both combined, default: %d\n\ + the highest of these only, not both combined,\n\ + max: %d, default: %d\n\ -m maximum megabytes of ram to use when deriving key from password\n\ with scrypt, applies for encryption AND decryption, must\n\ almost linearly scale with -N, if too low operation will fail,\n\ - default: %d\n", BUFFER_SIZE_MB, SCRYPT_MAX_MEM_MB); + default: %d\n", INT_MAX / 1024 / 1024, BUFFER_SIZE_MB, SCRYPT_MAX_MEM_MB); fprintf(stderr, "\ -N scrypt parameter N, only applies for encryption, default %d\n\ this is rounded up to the next highest power of 2\n\ @@ -527,7 +567,7 @@ int main(int argc, char **argv) { int optind, decrypt = 0, append = 0, exit_code = 2; char *password = NULL; - uint32_t N = SCRYPT_N, scrypt_max_mem_mb = SCRYPT_MAX_MEM_MB, buffer_size_mb = BUFFER_SIZE_MB, scale = 1; + uint32_t N = SCRYPT_N, scrypt_max_mem_mb = SCRYPT_MAX_MEM_MB, buffer_size = BUFFER_SIZE_MB * 1024 * 1024, scale = 1; uint8_t r = SCRYPT_R, p = SCRYPT_P; FILE *in = stdin, *out = stdout, *err = stderr; @@ -566,8 +606,12 @@ int main(int argc, char **argv) } out_filename = argv[optind]; break; - case 'b': - buffer_size_mb = parse_int_arg(++optind, argc, argv); + case 'c': + buffer_size = parse_int_arg(++optind, argc, argv) * 1024 * 1024; + if(buffer_size > INT_MAX) { + fprintf(stderr, "Error: %s chunk size cannot exceed %d megabytes\n", argv[optind - 1], INT_MAX / 1024 / 1024); + return help(2); + } break; case 'm': scrypt_max_mem_mb = parse_int_arg(++optind, argc, argv); @@ -645,17 +689,14 @@ int main(int argc, char **argv) } if(decrypt) - exit_code = pegh_decrypt(password, scrypt_max_mem_mb, 1024 * 1024 * buffer_size_mb, in, out, err); + exit_code = pegh_decrypt(password, scrypt_max_mem_mb, buffer_size, in, out, err); else - exit_code = pegh_encrypt(password, scrypt_max_mem_mb, 1024 * 1024 * buffer_size_mb, in, out, err, N, r, p); + exit_code = pegh_encrypt(password, scrypt_max_mem_mb, buffer_size, in, out, err, N, r, p); if(NULL != in_filename) fclose(in); if(NULL != out_filename) { fclose(out); - /* attempt to stop programs from using incomplete/bad data */ - if(exit_code != 1 && !append) - remove(out_filename); } /* to the OS, 0 means success, the above functions 1 means success */ diff --git a/test.sh b/test.sh index a7f8725..f758e98 100755 --- a/test.sh +++ b/test.sh @@ -35,7 +35,7 @@ test () { # todo: can we also make this the case for stdout? needs some buffering... echo 'bad decryption should result in output file being deleted' echo 'hopefully this doesnt make it to disk' | "$bin" "$key" | cat - <(echo -n a) | "$bin" -d "$key" -o bla.txt && exit 1 - [ -e bla.txt ] && echo "ERROR: bla.txt should not exist" && exit 1 + [ -s bla.txt ] && echo "ERROR: bla.txt should not exist" && exit 1 set -e }