Switch to encrypting chunks at a time instead

This commit is contained in:
Travis Burtrum 2019-12-28 16:39:38 -05:00
parent cf9d0dd243
commit 8a48f437d9
3 changed files with 329 additions and 287 deletions

View File

@ -31,14 +31,16 @@ usage: pegh [options...] password
-e encrypt input to output, default mode
-d decrypt input to output
-i <filename> file to use for input, default stdin
-o <filename> 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 <filename> file to use for output, default stdout
-a append to -o instead of truncate
-b <max_mb> maximum megabytes of ram to use per read/write buffer, so while
decrypting/encrypting twice this will be used, but these are
-c <max_mb> 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 <max_mb> 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
-------

579
pegh.c
View File

@ -27,6 +27,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
/*
* 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 <filename> file to use for input, default stdin\n\
-o <filename> 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 <filename> file to use for output, default stdout\n");
fprintf(stderr, "\
default stdout\n\
-a append to -o instead of truncate\n\
-b <max_mb> 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 <max_mb> 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 <max_mb> 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 <num> 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 */

View File

@ -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
}