Switch to encrypting chunks at a time instead
This commit is contained in:
parent
cf9d0dd243
commit
8a48f437d9
35
README.md
35
README.md
@ -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
579
pegh.c
@ -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 */
|
||||
|
2
test.sh
2
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user