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
|
-e encrypt input to output, default mode
|
||||||
-d decrypt input to output
|
-d decrypt input to output
|
||||||
-i <filename> file to use for input, default stdin
|
-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
|
-o <filename> file to use for output, default stdout
|
||||||
is not set, we try to delete this file before exiting,
|
|
||||||
default stdout
|
|
||||||
-a append to -o instead of truncate
|
-a append to -o instead of truncate
|
||||||
-b <max_mb> maximum megabytes of ram to use per read/write buffer, so while
|
-c <max_mb> maximum megabytes of ram to use per encrypted chunk, so while
|
||||||
decrypting/encrypting twice this will be used, but these are
|
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
|
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
|
-m <max_mb> maximum megabytes of ram to use when deriving key from password
|
||||||
with scrypt, applies for encryption AND decryption, must
|
with scrypt, applies for encryption AND decryption, must
|
||||||
almost linearly scale with -N, if too low operation will fail,
|
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.
|
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 |
|
| indices | format | value interpretation |
|
||||||
|--------------|---------------------------------------------|--------------------------------|
|
|--------------|---------------------------------------------|-----------------------------------------|
|
||||||
| 0 | 8 bit unsigned byte | pegh file format version |
|
| 0 | 8 bit unsigned byte | pegh file format version |
|
||||||
| 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter |
|
| 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter |
|
||||||
| 5 | 8 bit unsigned byte | scrypt r parameter |
|
| 5 | 8 bit unsigned byte | scrypt r parameter |
|
||||||
| 6 | 8 bit unsigned byte | scrypt p parameter |
|
| 6 | 8 bit unsigned byte | scrypt p parameter |
|
||||||
| 7-38 | 32 randomly generated bytes | scrypt key derivation seed |
|
| 7-10 | 32 bit unsigned integer in big endian order | aes encrypted chunk size |
|
||||||
| 39-50 | 12 randomly generated bytes | AES-256-GCM IV |
|
| 11-42 | 32 randomly generated bytes | scrypt key derivation seed |
|
||||||
| 51-X | any number of bytes | AES-256-GCM encrypted data |
|
| 43+end | any number of chunks, chunk_size + 16 long | chunks followed by AES-256-GCM auth tag |
|
||||||
| (X+1)-(X+16) | 16 bytes, always last 16 bytes in file | AES-256-GCM authentication tag |
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
579
pegh.c
579
pegh.c
|
@ -27,6 +27,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tweak default scrypt hardness params here
|
* tweak default scrypt hardness params here
|
||||||
|
@ -47,20 +48,21 @@
|
||||||
*
|
*
|
||||||
* 0th byte is always version number, everything else depends on version number
|
* 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 |
|
* | 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 |
|
||||||
* | indices | format | value interpretation |
|
* | ever rolls back over to 0 encryption should be aborted (chunk size should be increased).
|
||||||
* |--------------|---------------------------------------------|--------------------------------|
|
* |--------------|---------------------------------------------|-----------------------------------------|
|
||||||
* | 0 | 8 bit unsigned byte | pegh file format version |
|
* | indices | format | value interpretation |
|
||||||
* | 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter |
|
* |--------------|---------------------------------------------|-----------------------------------------|
|
||||||
* | 5 | 8 bit unsigned byte | scrypt r parameter |
|
* | 0 | 8 bit unsigned byte | pegh file format version |
|
||||||
* | 6 | 8 bit unsigned byte | scrypt p parameter |
|
* | 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter |
|
||||||
* | 7-38 | 32 randomly generated bytes | scrypt key derivation seed |
|
* | 5 | 8 bit unsigned byte | scrypt r parameter |
|
||||||
* | 39-50 | 12 randomly generated bytes | AES-256-GCM IV |
|
* | 6 | 8 bit unsigned byte | scrypt p parameter |
|
||||||
* | 51-X | any number of bytes | AES-256-GCM encrypted data |
|
* | 7-10 | 32 bit unsigned integer in big endian order | aes encrypted chunk size |
|
||||||
* | (X+1)-(X+16) | 16 bytes, always last 16 bytes in file | AES-256-GCM authentication tag |
|
* | 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 */
|
/* don't touch below here unless you know what you are doing */
|
||||||
|
@ -70,15 +72,230 @@
|
||||||
/* 256 bit key required for AES-256 */
|
/* 256 bit key required for AES-256 */
|
||||||
#define KEY_LEN 32
|
#define KEY_LEN 32
|
||||||
|
|
||||||
/* 1 for file format version, 4 for N, 1 for r, 1 for p */
|
/* 1 for file format version, 4 for N, 1 for r, 1 for p, 4 for block/buffer size */
|
||||||
#define PRE_SALT_LEN 7
|
#define PRE_SALT_LEN 11
|
||||||
/* from libsodium's crypto_pwhash_scryptsalsa208sha256_SALTBYTES */
|
/* from libsodium's crypto_pwhash_scryptsalsa208sha256_SALTBYTES */
|
||||||
#define SALT_LEN 32
|
#define SALT_LEN 32
|
||||||
/* AES-GCM should only ever have an IV_LEN of 12 */
|
/* AES-GCM should only ever have an IV_LEN of 12 */
|
||||||
#define IV_LEN 12
|
#define IV_LEN 12
|
||||||
#define GCM_TAG_LEN 16
|
#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
|
* 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
|
* in/out must be set
|
||||||
* err can be NULL in which case no messages are printed
|
* 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
|
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 */
|
/* these are actually mallocd and freed */
|
||||||
EVP_CIPHER_CTX *ctx = NULL;
|
|
||||||
unsigned char *plaintext = NULL, *ciphertext = NULL;
|
unsigned char *plaintext = NULL, *ciphertext = NULL;
|
||||||
|
|
||||||
int exit_code = 0, ciphertext_written;
|
int exit_code = 0;
|
||||||
size_t plaintext_read;
|
|
||||||
|
|
||||||
if(buffer_size > INT_MAX) {
|
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 */
|
/* 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
plaintext = malloc(buffer_size);
|
||||||
plaintext = malloc(buffer_size);
|
if(!plaintext) {
|
||||||
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 */
|
|
||||||
if(NULL != err)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
exit_code = decrypt ? gcm_decrypt_stream(key, iv, buffer_size, plaintext, ciphertext, in, out, err) :
|
||||||
plaintext = malloc(buffer_size);
|
gcm_encrypt_stream(key, iv, buffer_size, plaintext, ciphertext, in, out, err);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
tag = ciphertext;
|
free(plaintext);
|
||||||
ciphertext_read_zone = ciphertext + GCM_TAG_LEN;
|
free(ciphertext);
|
||||||
|
|
||||||
/* 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);
|
|
||||||
|
|
||||||
if(NULL != err && exit_code != 1) {
|
if(NULL != err && exit_code != 1) {
|
||||||
/* print openssl errors */
|
/* print openssl errors */
|
||||||
ERR_print_errors_fp(err);
|
ERR_print_errors_fp(err);
|
||||||
fprintf(err, "decryption failed\n");
|
fprintf(err, "%scryption failed\n", decrypt ? "de" : "en");
|
||||||
}
|
}
|
||||||
|
|
||||||
return exit_code;
|
return exit_code;
|
||||||
|
@ -353,52 +378,62 @@ int scrypt_derive_key(char *password,
|
||||||
return 1;
|
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 */
|
/* returns 1 on success, 0 on failure */
|
||||||
int pegh_encrypt(char *password,
|
int pegh_encrypt(char *password,
|
||||||
uint32_t scrypt_max_mem_mb, size_t buffer_size,
|
uint32_t scrypt_max_mem_mb, size_t buffer_size,
|
||||||
FILE *in, FILE *out, FILE *err,
|
FILE *in, FILE *out, FILE *err,
|
||||||
uint32_t N, uint8_t r, uint8_t p)
|
uint32_t N, uint8_t r, uint8_t p)
|
||||||
{
|
{
|
||||||
unsigned char key[KEY_LEN] = {0}, salt[SALT_IV_LEN] = {0};
|
unsigned char key[KEY_LEN] = {0}, salt[SALT_LEN] = {0};
|
||||||
/* this is simply a pointer into salt */
|
|
||||||
const unsigned char *iv = salt + SALT_LEN;
|
|
||||||
|
|
||||||
/* first write the version and parameters */
|
/* first write the version and parameters */
|
||||||
salt[0] = 0;
|
salt[0] = 0;
|
||||||
salt[1] = (unsigned char) ((N >> 24) & 0xFF);
|
write_uint32_big_endian(N, salt+1);
|
||||||
salt[2] = (N >> 16) & 0xFF;
|
|
||||||
salt[3] = (N >> 8) & 0xFF;
|
|
||||||
salt[4] = N & 0xFF;
|
|
||||||
salt[5] = r;
|
salt[5] = r;
|
||||||
salt[6] = p;
|
salt[6] = p;
|
||||||
|
write_uint32_big_endian((uint32_t) buffer_size, salt+7);
|
||||||
fwrite(salt, 1, PRE_SALT_LEN, out);
|
fwrite(salt, 1, PRE_SALT_LEN, out);
|
||||||
|
|
||||||
/* generate random salt+iv, then write it out */
|
/* generate random salt, then write it out */
|
||||||
if (RAND_bytes(salt, SALT_IV_LEN) <= 0) {
|
if (RAND_bytes(salt, SALT_LEN) <= 0) {
|
||||||
if(NULL != err) {
|
if(NULL != err) {
|
||||||
fprintf(err, "random salt+iv generation error\n");
|
fprintf(err, "random salt generation error\n");
|
||||||
ERR_print_errors_fp(err);
|
ERR_print_errors_fp(err);
|
||||||
}
|
}
|
||||||
return 0;
|
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))
|
if(1 != scrypt_derive_key(password, scrypt_max_mem_mb, N, r, p, salt, key, err))
|
||||||
return 0;
|
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 */
|
/* returns 1 on success, 0 on failure */
|
||||||
int pegh_decrypt(char *password,
|
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)
|
FILE *in, FILE *out, FILE *err)
|
||||||
{
|
{
|
||||||
unsigned char key[KEY_LEN] = {0}, salt[SALT_IV_LEN] = {0};
|
unsigned char key[KEY_LEN] = {0}, salt[SALT_LEN] = {0};
|
||||||
/* this is simply a pointer into salt */
|
|
||||||
const unsigned char *iv = salt + SALT_LEN;
|
|
||||||
|
|
||||||
size_t header_read;
|
size_t header_read, buffer_size;
|
||||||
|
|
||||||
uint32_t N;
|
uint32_t N;
|
||||||
uint8_t r, p;
|
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]);
|
fprintf(err, "unsupported file format version %d, we only support version 0\n", salt[0]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
N = (uint32_t) ((salt[1] & 0xFF) << 24)
|
N = read_uint32_big_endian(salt+1);
|
||||||
| (uint32_t) ((salt[2] & 0xFF) << 16)
|
|
||||||
| (uint32_t) ((salt[3] & 0xFF) << 8)
|
|
||||||
| (uint32_t) (salt[4] & 0xFF);
|
|
||||||
r = salt[5];
|
r = salt[5];
|
||||||
p = salt[6];
|
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 */
|
/* next read salt */
|
||||||
header_read = fread(salt, 1, SALT_IV_LEN, in);
|
header_read = fread(salt, 1, SALT_LEN, in);
|
||||||
if(header_read != SALT_IV_LEN) {
|
if(header_read != SALT_LEN) {
|
||||||
if(NULL != err)
|
if(NULL != err)
|
||||||
fprintf(err, "File too small for decryption, invalid header?\n");
|
fprintf(err, "File too small for decryption, invalid header?\n");
|
||||||
return 0;
|
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))
|
if(1 != scrypt_derive_key(password, scrypt_max_mem_mb, N, r, p, salt, key, err))
|
||||||
return 0;
|
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) {
|
int help(int exit_code) {
|
||||||
|
@ -443,20 +481,22 @@ usage: pegh [options...] password\n\
|
||||||
-e encrypt input to output, default mode\n\
|
-e encrypt input to output, default mode\n\
|
||||||
-d decrypt input to output\n\
|
-d decrypt input to output\n\
|
||||||
-i <filename> file to use for input, default stdin\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\
|
-o <filename> file to use for output, default stdout\n");
|
||||||
is not set, we try to delete this file before exiting,\n");
|
|
||||||
fprintf(stderr, "\
|
fprintf(stderr, "\
|
||||||
default stdout\n\
|
|
||||||
-a append to -o instead of truncate\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\
|
-c <max_mb> maximum megabytes of ram to use per encrypted chunk, so while\n\
|
||||||
decrypting/encrypting twice this will be used, but these are\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, "\
|
fprintf(stderr, "\
|
||||||
only allocated after scrypt is finished so max usage will be\n\
|
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\
|
-m <max_mb> maximum megabytes of ram to use when deriving key from password\n\
|
||||||
with scrypt, applies for encryption AND decryption, must\n\
|
with scrypt, applies for encryption AND decryption, must\n\
|
||||||
almost linearly scale with -N, if too low operation will fail,\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, "\
|
fprintf(stderr, "\
|
||||||
-N <num> scrypt parameter N, only applies for encryption, default %d\n\
|
-N <num> scrypt parameter N, only applies for encryption, default %d\n\
|
||||||
this is rounded up to the next highest power of 2\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;
|
int optind, decrypt = 0, append = 0, exit_code = 2;
|
||||||
char *password = NULL;
|
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;
|
uint8_t r = SCRYPT_R, p = SCRYPT_P;
|
||||||
|
|
||||||
FILE *in = stdin, *out = stdout, *err = stderr;
|
FILE *in = stdin, *out = stdout, *err = stderr;
|
||||||
|
@ -566,8 +606,12 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
out_filename = argv[optind];
|
out_filename = argv[optind];
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'c':
|
||||||
buffer_size_mb = parse_int_arg(++optind, argc, argv);
|
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;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
scrypt_max_mem_mb = parse_int_arg(++optind, argc, argv);
|
scrypt_max_mem_mb = parse_int_arg(++optind, argc, argv);
|
||||||
|
@ -645,17 +689,14 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(decrypt)
|
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
|
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)
|
if(NULL != in_filename)
|
||||||
fclose(in);
|
fclose(in);
|
||||||
if(NULL != out_filename) {
|
if(NULL != out_filename) {
|
||||||
fclose(out);
|
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 */
|
/* 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...
|
# todo: can we also make this the case for stdout? needs some buffering...
|
||||||
echo 'bad decryption should result in output file being deleted'
|
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
|
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
|
set -e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue