Implement pegh file format Version 1 using ChaCha20-Poly1305

This commit is contained in:
Travis Burtrum 2019-12-31 02:52:57 -05:00
parent 92b8534798
commit 7540fa7d7c
3 changed files with 256 additions and 98 deletions

View File

@ -6,15 +6,7 @@ set -exu
cd "$(dirname "$0")" cd "$(dirname "$0")"
# dependencies to build+test pegh # dependencies to build+test pegh
apk add build-base clang bash libsodium-dev libsodium-static libressl-dev apk add build-base clang bash libsodium-dev libsodium-static openssl-dev openssl-libs-static
# first build for libressl, which doesn't have "EVP_PBE_scrypt" so can only be compiled with libsodium
make clean all PEGH_LIBSODIUM=1 PEGH_OPENSSL=1 CC=clang LDFLAGS="-static"
mv pegh pegh.static.libsodium-libressl
# now remove libressl and install openssl
apk del libressl-dev
apk add openssl-dev openssl-libs-static
# gcc is apparantly incapable of building a static binary, even gcc -static helloworld.c ends up linked to libc, instead of solving, use clang # gcc is apparantly incapable of building a static binary, even gcc -static helloworld.c ends up linked to libc, instead of solving, use clang
make clean all PEGH_LIBSODIUM=1 CC=clang LDFLAGS="-static" make clean all PEGH_LIBSODIUM=1 CC=clang LDFLAGS="-static"
@ -37,7 +29,7 @@ ldd pegh.static.* || true
# libsodium only supports AES-256-GCM on certain CPUs that have hardware instructions for it # libsodium only supports AES-256-GCM on certain CPUs that have hardware instructions for it
# we can build them regardless, but we can't test them without that, pegh prints that right away # we can build them regardless, but we can't test them without that, pegh prints that right away
export TEST_BINS="./pegh.static.openssl ./pegh.openssl ./pegh.static.libsodium-openssl ./pegh.libsodium-openssl ./pegh.static.libsodium-libressl" export TEST_BINS="./pegh.static.openssl ./pegh.openssl ./pegh.static.libsodium-openssl ./pegh.libsodium-openssl"
set +e set +e
if ./pegh.static.libsodium -h 2>&1 >/dev/null | grep '^Error: libsodium' if ./pegh.static.libsodium -h 2>&1 >/dev/null | grep '^Error: libsodium'
then then

View File

@ -15,7 +15,6 @@ docker run --rm -v "$BUILD_DIR":/tmp "$DOCKER_IMAGE" /tmp/build.sh || exit 1
mv "$BUILD_DIR"pegh.static.openssl "./pegh-$ARCH-openssl" mv "$BUILD_DIR"pegh.static.openssl "./pegh-$ARCH-openssl"
mv "$BUILD_DIR"pegh.static.libsodium "./pegh-$ARCH-libsodium" mv "$BUILD_DIR"pegh.static.libsodium "./pegh-$ARCH-libsodium"
mv "$BUILD_DIR"pegh.static.libsodium-openssl "./pegh-$ARCH-libsodium-openssl" mv "$BUILD_DIR"pegh.static.libsodium-openssl "./pegh-$ARCH-libsodium-openssl"
mv "$BUILD_DIR"pegh.static.libsodium-libressl "./pegh-$ARCH-libsodium-libressl"
rm -rf "$BUILD_DIR" 2>/dev/null rm -rf "$BUILD_DIR" 2>/dev/null
exit 0 exit 0

341
pegh.c
View File

@ -92,20 +92,29 @@ const uint32_t BUFFER_SIZE_MB = 32;
const size_t PRE_SALT_LEN = 11; const size_t 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/Chacha20-Poly1305 should only ever have an IV_LEN of 12 */
#define IV_LEN 12 #define IV_LEN 12
const size_t GCM_TAG_LEN = 16; const size_t AEAD_TAG_LEN = 16;
/* libsodium only supports AES on specific platforms, this jazz is to fallback to openssl impls in those cases */ /* libsodium only supports AES on specific platforms, this jazz is to fallback to openssl impls in those cases */
typedef int (*gcm_func)(const unsigned char *, const size_t, typedef int (*aead_func)(const unsigned char *, const size_t,
const unsigned char *, const unsigned char *, const unsigned char *, const unsigned char *,
unsigned char *, unsigned char * unsigned char *, unsigned char *
); );
typedef int (*stream_func)(const aead_func, const unsigned char *, unsigned char *, size_t,
unsigned char *, unsigned char *,
FILE *, FILE *, FILE *
);
#ifdef PEGH_OPENSSL #ifdef PEGH_OPENSSL
/* 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, limit of 2gb */ /* 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, limit of 2gb */
static const size_t CHUNK_SIZE_MAX_OPENSSL = INT_MAX; static const size_t CHUNK_SIZE_MAX_OPENSSL_GCM = INT_MAX;
/* if libsodium is around we use it's chacha impl */
#ifndef PEGH_LIBSODIUM
static const size_t CHUNK_SIZE_MAX_CHACHA = INT_MAX;
#endif
/* /*
* returns 1 on success, 0 on failure * returns 1 on success, 0 on failure
@ -118,9 +127,9 @@ static const size_t CHUNK_SIZE_MAX_OPENSSL = INT_MAX;
* *
* these will be written into: * these will be written into:
* ciphertext must have the capacity of at least plaintext_len * ciphertext must have the capacity of at least plaintext_len
* tag must have the capacity of at least GCM_TAG_LEN * tag must have the capacity of at least AEAD_TAG_LEN
*/ */
int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_len, int aead_encrypt_openssl(const EVP_CIPHER *type, const unsigned char *plaintext, const size_t plaintext_len,
const unsigned char *key, const unsigned char *key,
const unsigned char *iv, const unsigned char *iv,
unsigned char *ciphertext, unsigned char *ciphertext,
@ -136,11 +145,11 @@ int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_l
break; break;
/* Initialise the encryption operation. */ /* Initialise the encryption operation. */
if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) if(1 != EVP_EncryptInit_ex(ctx, type, NULL, NULL, NULL))
break; break;
/* Setting IV length is not necessary because the default of 12 bytes (96 bits) will be used /* 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)) if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, IV_LEN, NULL))
break; break;
*/ */
@ -171,7 +180,7 @@ int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_l
break; break;
/* Get the tag */ /* Get the tag */
ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, GCM_TAG_LEN, tag); ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, AEAD_TAG_LEN, tag);
} while(0); } while(0);
/* Clean up */ /* Clean up */
@ -189,12 +198,12 @@ int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_l
* ciphertext_len * ciphertext_len
* key must be length KEY_LEN * key must be length KEY_LEN
* iv must be length IV_LEN * iv must be length IV_LEN
* tag must be length GCM_TAG_LEN * tag must be length AEAD_TAG_LEN
* *
* these will be written into: * these will be written into:
* plaintext must have the capacity of at least ciphertext_len * plaintext must have the capacity of at least ciphertext_len
*/ */
int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext_len, int aead_decrypt_openssl(const EVP_CIPHER *type, const unsigned char *ciphertext, const size_t ciphertext_len,
const unsigned char *key, const unsigned char *key,
const unsigned char *iv, const unsigned char *iv,
unsigned char *tag, unsigned char *tag,
@ -210,11 +219,11 @@ int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext
break; break;
/* Initialise the decryption operation. */ /* Initialise the decryption operation. */
if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) if(!EVP_DecryptInit_ex(ctx, type, NULL, NULL, NULL))
break; break;
/* Setting IV length is not necessary because the default of 12 bytes (96 bits) will be used /* 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)) if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, IV_LEN, NULL))
break; break;
*/ */
@ -238,7 +247,7 @@ int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext
*/ */
/* Set expected tag value. Works in OpenSSL 1.0.1d and later */ /* 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)) if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, AEAD_TAG_LEN, tag))
break; break;
/* /*
@ -255,10 +264,56 @@ int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext
return ret; return ret;
} }
int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_len,
const unsigned char *key,
const unsigned char *iv,
unsigned char *ciphertext,
unsigned char *tag
) {
return aead_encrypt_openssl(EVP_aes_256_gcm(), plaintext, plaintext_len,
key, iv,
ciphertext, tag);
}
int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext_len,
const unsigned char *key,
const unsigned char *iv,
unsigned char *tag,
unsigned char *plaintext
)
{
return aead_decrypt_openssl(EVP_aes_256_gcm(), ciphertext, ciphertext_len,
key, iv, tag,
plaintext);
}
/* if both PEGH_OPENSSL and PEGH_LIBSODIUM are defined, we only want the AES funcs from OpenSSL */ /* if both PEGH_OPENSSL and PEGH_LIBSODIUM are defined, we only want the AES funcs from OpenSSL */
#ifndef PEGH_LIBSODIUM #ifndef PEGH_LIBSODIUM
int chacha_encrypt(const unsigned char *plaintext, const size_t plaintext_len,
const unsigned char *key,
const unsigned char *iv,
unsigned char *ciphertext,
unsigned char *tag
) {
return aead_encrypt_openssl(EVP_chacha20_poly1305(), plaintext, plaintext_len,
key, iv,
ciphertext, tag);
}
int chacha_decrypt(const unsigned char *ciphertext, const size_t ciphertext_len,
const unsigned char *key,
const unsigned char *iv,
unsigned char *tag,
unsigned char *plaintext
)
{
return aead_decrypt_openssl(EVP_chacha20_poly1305(), ciphertext, ciphertext_len,
key, iv, tag,
plaintext);
}
/* returns 1 on success, 0 on error */ /* returns 1 on success, 0 on error */
int scrypt_derive_key(char *password, size_t password_len, int scrypt_derive_key(char *password, size_t password_len,
uint32_t scrypt_max_mem, uint32_t N, uint32_t scrypt_max_mem, uint32_t N,
@ -296,13 +351,17 @@ void wipe_memory(void * const ptr, const size_t len) {
#ifdef PEGH_LIBSODIUM #ifdef PEGH_LIBSODIUM
/* /*
* unlike openssl, libsodium uses proper types, so we can go all the way up to the "aes-gcm-256 is still secure" limit of around 32gb * unlike openssl, libsodium uses proper types, so we can go all the way up to the "still secure" limit of around 64gb for aes and 256gb for chacha
* but 32-bit systems have SIZE_MAX smaller than that, so special case that here * but 32-bit systems have SIZE_MAX smaller than that, so special case that here
*/ */
#if (1024UL * 1024 * 1024 * 32) > SIZE_MAX #if 274877906880 > SIZE_MAX
static const size_t CHUNK_SIZE_MAX_LIBSODIUM = SIZE_MAX; static const size_t CHUNK_SIZE_MAX_LIBSODIUM_GCM = SIZE_MAX;
static const size_t CHUNK_SIZE_MAX_CHACHA = SIZE_MAX;
#else #else
static const size_t CHUNK_SIZE_MAX_LIBSODIUM = 1024UL * 1024 * 1024 * 32; /* https://crypto.stackexchange.com/a/22643 */
static const size_t CHUNK_SIZE_MAX_LIBSODIUM_GCM = 68719476704; /* (2^36)-32 or slightly less than 64gb */
/* https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/chacha20-poly1305/ietf_chacha20-poly1305_construction */
static const size_t CHUNK_SIZE_MAX_CHACHA = 274877906880; /* 64*(2^32)-64 or slightly less than 256gb */
#endif #endif
/* /*
@ -316,7 +375,7 @@ static const size_t CHUNK_SIZE_MAX_LIBSODIUM = 1024UL * 1024 * 1024 * 32;
* *
* these will be written into: * these will be written into:
* ciphertext must have the capacity of at least plaintext_len * ciphertext must have the capacity of at least plaintext_len
* tag must have the capacity of at least GCM_TAG_LEN * tag must have the capacity of at least AEAD_TAG_LEN
*/ */
int gcm_encrypt_libsodium(const unsigned char *plaintext, const size_t plaintext_len, int gcm_encrypt_libsodium(const unsigned char *plaintext, const size_t plaintext_len,
const unsigned char *key, const unsigned char *key,
@ -341,7 +400,7 @@ int gcm_encrypt_libsodium(const unsigned char *plaintext, const size_t plaintext
* ciphertext_len * ciphertext_len
* key must be length KEY_LEN * key must be length KEY_LEN
* iv must be length IV_LEN * iv must be length IV_LEN
* tag must be length GCM_TAG_LEN * tag must be length AEAD_TAG_LEN
* *
* these will be written into: * these will be written into:
* plaintext must have the capacity of at least ciphertext_len * plaintext must have the capacity of at least ciphertext_len
@ -361,6 +420,62 @@ int gcm_decrypt_libsodium(const unsigned char *ciphertext, const size_t cipherte
iv, key) != 0 ? 0 : 1; iv, key) != 0 ? 0 : 1;
} }
/*
* 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 AEAD_TAG_LEN
*/
int chacha_encrypt(const unsigned char *plaintext, const size_t plaintext_len,
const unsigned char *key,
const unsigned char *iv,
unsigned char *ciphertext,
unsigned char *tag
)
{
crypto_aead_chacha20poly1305_ietf_encrypt_detached(ciphertext,
tag, NULL,
plaintext, plaintext_len,
NULL, 0,
NULL, iv, key);
return 1;
}
/*
* 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 AEAD_TAG_LEN
*
* these will be written into:
* plaintext must have the capacity of at least ciphertext_len
*/
int chacha_decrypt(const unsigned char *ciphertext, const size_t ciphertext_len,
const unsigned char *key,
const unsigned char *iv,
unsigned char *tag,
unsigned char *plaintext
)
{
return crypto_aead_chacha20poly1305_ietf_decrypt_detached(plaintext,
NULL,
ciphertext, (size_t) ciphertext_len,
tag,
NULL, 0,
iv, key) != 0 ? 0 : 1;
}
/* returns 1 on success, 0 on error */ /* returns 1 on success, 0 on error */
int scrypt_derive_key(char *password, size_t password_len, int scrypt_derive_key(char *password, size_t password_len,
uint32_t scrypt_max_mem, uint32_t N, uint32_t scrypt_max_mem, uint32_t N,
@ -417,14 +532,14 @@ void wipe_memory(void * const ptr, const size_t len) {
#define PEGH_CONST #define PEGH_CONST
#endif #endif
static PEGH_CONST gcm_func gcm_encrypt = gcm_encrypt_libsodium; static PEGH_CONST aead_func gcm_encrypt = gcm_encrypt_libsodium;
static PEGH_CONST gcm_func gcm_decrypt = gcm_decrypt_libsodium; static PEGH_CONST aead_func gcm_decrypt = gcm_decrypt_libsodium;
static PEGH_CONST size_t CHUNK_SIZE_MAX = CHUNK_SIZE_MAX_LIBSODIUM; static PEGH_CONST size_t CHUNK_SIZE_MAX_GCM = CHUNK_SIZE_MAX_LIBSODIUM_GCM;
#elif PEGH_OPENSSL #elif PEGH_OPENSSL
static const gcm_func gcm_encrypt = gcm_encrypt_openssl; static const aead_func gcm_encrypt = gcm_encrypt_openssl;
static const gcm_func gcm_decrypt = gcm_decrypt_openssl; static const aead_func gcm_decrypt = gcm_decrypt_openssl;
static const size_t CHUNK_SIZE_MAX = CHUNK_SIZE_MAX_OPENSSL; static const size_t CHUNK_SIZE_MAX_GCM = CHUNK_SIZE_MAX_OPENSSL_GCM;
#endif #endif
/* returns 1 on success, 0 on failure */ /* returns 1 on success, 0 on failure */
@ -447,7 +562,7 @@ int iv_increment_forbid_zero(unsigned char *n, const size_t nlen, FILE *err)
} }
/* returns 1 on success, 0 on failure */ /* returns 1 on success, 0 on failure */
int gcm_encrypt_stream(const unsigned char *key, unsigned char *iv, size_t buffer_size, int encrypt_stream(const aead_func encrypt, const unsigned char *key, unsigned char *iv, size_t buffer_size,
unsigned char *plaintext, unsigned char *ciphertext, unsigned char *plaintext, unsigned char *ciphertext,
FILE *in, FILE *out, FILE *err FILE *in, FILE *out, FILE *err
) { ) {
@ -455,37 +570,37 @@ int gcm_encrypt_stream(const unsigned char *key, unsigned char *iv, size_t buffe
while ((plaintext_read = fread(plaintext, 1, buffer_size, in)) > 0) { while ((plaintext_read = fread(plaintext, 1, buffer_size, in)) > 0) {
if(1 != gcm_encrypt(plaintext, plaintext_read, key, iv, ciphertext, ciphertext + plaintext_read)) if(1 != encrypt(plaintext, plaintext_read, key, iv, ciphertext, ciphertext + plaintext_read))
return 0; return 0;
if(1 != iv_increment_forbid_zero(iv, IV_LEN, err)) if(1 != iv_increment_forbid_zero(iv, IV_LEN, err))
return 0; return 0;
fwrite(ciphertext, 1, plaintext_read + GCM_TAG_LEN, out); fwrite(ciphertext, 1, plaintext_read + AEAD_TAG_LEN, out);
} }
return 1; return 1;
} }
/* returns 1 on success, 0 on failure */ /* returns 1 on success, 0 on failure */
int gcm_decrypt_stream(const unsigned char *key, unsigned char *iv, size_t buffer_size, int decrypt_stream(const aead_func decrypt, const unsigned char *key, unsigned char *iv, size_t buffer_size,
unsigned char *plaintext, unsigned char *ciphertext, unsigned char *plaintext, unsigned char *ciphertext,
FILE *in, FILE *out, FILE *err FILE *in, FILE *out, FILE *err
) { ) {
size_t ciphertext_read; size_t ciphertext_read;
buffer_size += GCM_TAG_LEN; buffer_size += AEAD_TAG_LEN;
while ((ciphertext_read = fread(ciphertext, 1, buffer_size, in)) > 0) { while ((ciphertext_read = fread(ciphertext, 1, buffer_size, in)) > 0) {
if(ciphertext_read < GCM_TAG_LEN) { if(ciphertext_read < AEAD_TAG_LEN) {
if(NULL != err) if(NULL != err)
fprintf(err, "File too small for decryption, truncated?\n"); fprintf(err, "File too small for decryption, truncated?\n");
return 0; return 0;
} }
ciphertext_read -= GCM_TAG_LEN; ciphertext_read -= AEAD_TAG_LEN;
if(1 != gcm_decrypt(ciphertext, ciphertext_read, key, iv, ciphertext + ciphertext_read, plaintext)) if(1 != decrypt(ciphertext, ciphertext_read, key, iv, ciphertext + ciphertext_read, plaintext))
return 0; return 0;
if(1 != iv_increment_forbid_zero(iv, IV_LEN, err)) if(1 != iv_increment_forbid_zero(iv, IV_LEN, err))
@ -508,9 +623,8 @@ int gcm_decrypt_stream(const unsigned char *key, unsigned char *iv, size_t buffe
* 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_stream(const unsigned char *key, size_t buffer_size, int stream(const stream_func crypt_stream, const aead_func aead, 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 */ /* this is ok because the random salt makes the key random, and we increment this for encryption operation */
@ -518,15 +632,25 @@ int gcm_stream(const unsigned char *key, size_t buffer_size,
/* these are actually mallocd and freed */ /* these are actually mallocd and freed */
unsigned char *plaintext = NULL, *ciphertext = NULL; unsigned char *plaintext = NULL, *ciphertext = NULL;
int exit_code = 0; size_t chunk_size_max;
int exit_code = 0, is_gcm;
if(buffer_size > CHUNK_SIZE_MAX) { /* check chunk size */
is_gcm = aead == gcm_decrypt || aead == gcm_encrypt;
chunk_size_max = is_gcm ? CHUNK_SIZE_MAX_GCM : CHUNK_SIZE_MAX_CHACHA;
if(buffer_size > chunk_size_max) {
if(NULL != err) { if(NULL != err) {
#ifdef PEGH_OPENSSL #if defined(PEGH_OPENSSL) && defined(PEGH_LIBSODIUM)
fprintf(err, "due to openssl API limitation, buffer_size can at most be %lu\n", CHUNK_SIZE_MAX); /* the ugliest branch, openssl limits only apply if it's GCM and we are using openssl's impl */
#endif if(is_gcm && gcm_decrypt == gcm_decrypt_openssl) {
#ifdef PEGH_LIBSODIUM fprintf(err, "due to openssl API limitation, buffer_size can at most be %lu\n", chunk_size_max);
fprintf(err, "due to AES-256-GCM security constraints, buffer_size can at most be %lu\n", CHUNK_SIZE_MAX); } else {
fprintf(err, "due to algorithm security constraints, buffer_size can at most be %lu\n", chunk_size_max);
}
#elif defined(PEGH_OPENSSL)
fprintf(err, "due to openssl API limitation, buffer_size can at most be %lu\n", chunk_size_max);
#elif defined(PEGH_LIBSODIUM)
fprintf(err, "due to algorithm security constraints, buffer_size can at most be %lu\n", chunk_size_max);
#endif #endif
} }
return 0; return 0;
@ -538,7 +662,7 @@ int gcm_stream(const unsigned char *key, size_t buffer_size,
fprintf(err, "plaintext memory allocation failed\n"); fprintf(err, "plaintext memory allocation failed\n");
return 0; return 0;
} }
ciphertext = malloc(buffer_size + GCM_TAG_LEN); ciphertext = malloc(buffer_size + AEAD_TAG_LEN);
if(!ciphertext) { if(!ciphertext) {
if(NULL != err) if(NULL != err)
fprintf(err, "ciphertext memory allocation failed\n"); fprintf(err, "ciphertext memory allocation failed\n");
@ -546,8 +670,7 @@ int gcm_stream(const unsigned char *key, size_t buffer_size,
return 0; return 0;
} }
exit_code = decrypt ? gcm_decrypt_stream(key, iv, buffer_size, plaintext, ciphertext, in, out, err) : exit_code = crypt_stream(aead, key, iv, buffer_size, plaintext, ciphertext, in, out, err);
gcm_encrypt_stream(key, iv, buffer_size, plaintext, ciphertext, in, out, err);
free(plaintext); free(plaintext);
free(ciphertext); free(ciphertext);
@ -557,7 +680,7 @@ int gcm_stream(const unsigned char *key, size_t buffer_size,
/* print openssl errors */ /* print openssl errors */
ERR_print_errors_fp(err); ERR_print_errors_fp(err);
#endif #endif
fprintf(err, "%scryption failed\n", decrypt ? "de" : "en"); fprintf(err, "%scryption failed\n", crypt_stream == decrypt_stream ? "de" : "en");
} }
return exit_code; return exit_code;
@ -580,10 +703,10 @@ void write_uint32_big_endian(uint32_t val, unsigned char *buf) {
} }
/* returns 1 on success, 0 on failure */ /* returns 1 on success, 0 on failure */
int scrypt_derive_key_gcm_stream(char *password, int scrypt_derive_key_stream(const stream_func crypt_stream, const aead_func aead, char *password,
uint32_t scrypt_max_mem, size_t buffer_size, uint32_t scrypt_max_mem, 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, unsigned char *salt, int decrypt) { uint32_t N, uint8_t r, uint8_t p, unsigned char *salt) {
unsigned char key[KEY_LEN] = {0}; unsigned char key[KEY_LEN] = {0};
int ret; int ret;
size_t password_len; size_t password_len;
@ -594,22 +717,56 @@ int scrypt_derive_key_gcm_stream(char *password,
wipe_memory(password, password_len); wipe_memory(password, password_len);
if(ret == 1) if(ret == 1)
ret = gcm_stream(key, buffer_size, decrypt, in, out, err); ret = stream(crypt_stream, aead, key, buffer_size, in, out, err);
wipe_memory(key, KEY_LEN); wipe_memory(key, KEY_LEN);
return ret; return ret;
} }
/* returns 1 on success, 0 on failure */
int check_version(uint8_t version, FILE *err) {
if(version != 0 && version != 1) {
fprintf(stderr, "Error: unsupported file format version %d, we only support version 0 (AES-256-GCM) and 1 (Chacha20-Poly1305)\n", version);
return 0;
}
#ifdef PEGH_LIBSODIUM
else if (version == 0 && crypto_aead_aes256gcm_is_available() == 0) {
/* AES encrypted but libsodium/hardware AES is not available */
#ifdef PEGH_OPENSSL
/* swap to OpenSSL AES which is always supported */
if(NULL != err)
fprintf(err, "Warning: libsodium does not support AES-256-GCM on this CPU, falling back to openssl version instead...\n");
gcm_encrypt = gcm_encrypt_openssl;
gcm_decrypt = gcm_decrypt_openssl;
CHUNK_SIZE_MAX_GCM = CHUNK_SIZE_MAX_OPENSSL_GCM;
#else
/* nothing we can do */
fprintf(stderr, "Error: libsodium does not support AES-256-GCM on this CPU, compile/use openssl version?\n");
return 0;
#endif /* PEGH_OPENSSL */
}
#endif /* PEGH_LIBSODIUM */
/* silence unused-parameter warning */
(void)err;
return 1;
}
/* 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, size_t buffer_size, uint32_t scrypt_max_mem, size_t buffer_size,
FILE *in, FILE *out, FILE *err, FILE *in, FILE *out, FILE *err,
uint8_t version,
uint32_t N, uint8_t r, uint8_t p) uint32_t N, uint8_t r, uint8_t p)
{ {
unsigned char salt[SALT_LEN] = {0}; unsigned char salt[SALT_LEN] = {0};
if(1 != check_version(version, err)) {
fprintf(stderr, "Error: encryption aborting, try version 1 (Chacha20-Poly1305) which is universally supported\n");
return 0;
}
/* first write the version and parameters */ /* first write the version and parameters */
salt[0] = 0; salt[0] = version;
write_uint32_big_endian(N, salt+1); write_uint32_big_endian(N, salt+1);
salt[5] = r; salt[5] = r;
salt[6] = p; salt[6] = p;
@ -628,7 +785,7 @@ int pegh_encrypt(char *password,
} }
fwrite(salt, 1, SALT_LEN, out); fwrite(salt, 1, SALT_LEN, out);
return scrypt_derive_key_gcm_stream(password, scrypt_max_mem, buffer_size, in, out, err, N, r, p, salt, 0); return scrypt_derive_key_stream(encrypt_stream, version == 0 ? gcm_encrypt : chacha_encrypt, password, scrypt_max_mem, buffer_size, in, out, err, N, r, p, salt);
} }
/* returns 1 on success, 0 on failure */ /* returns 1 on success, 0 on failure */
@ -641,7 +798,7 @@ int pegh_decrypt(char *password,
size_t header_read, buffer_size; size_t header_read, buffer_size;
uint32_t N; uint32_t N;
uint8_t r, p; uint8_t version, r, p;
/* first read the version and parameters */ /* first read the version and parameters */
header_read = fread(salt, 1, PRE_SALT_LEN, in); header_read = fread(salt, 1, PRE_SALT_LEN, in);
@ -650,9 +807,9 @@ int pegh_decrypt(char *password,
fprintf(err, "File too small for decryption, invalid header?\n"); fprintf(err, "File too small for decryption, invalid header?\n");
return 0; return 0;
} }
if(salt[0] != 0) { version = salt[0];
if(NULL != err) if(1 != check_version(version, err)) {
fprintf(err, "unsupported file format version %d, we only support version 0\n", salt[0]); fprintf(stderr, "Error: decryption aborting, this file cannot be decrypted with this version/CPU\n");
return 0; return 0;
} }
N = read_uint32_big_endian(salt+1); N = read_uint32_big_endian(salt+1);
@ -673,7 +830,8 @@ int pegh_decrypt(char *password,
return 0; return 0;
} }
return scrypt_derive_key_gcm_stream(password, scrypt_max_mem, buffer_size, in, out, err, N, r, p, salt, 1); return scrypt_derive_key_stream(decrypt_stream, version == 0 ? gcm_decrypt : chacha_decrypt,
password, scrypt_max_mem, buffer_size, in, out, err, N, r, p, salt);
} }
int help(int exit_code) { int help(int exit_code) {
@ -694,11 +852,11 @@ usage: pegh [options...] password\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,\n\ the highest of these only, not both combined,\n\
max: %lu, default: %d\n\ max: %lu (AES-256-GCM) or %lu (Chacha20-Poly1305), 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", CHUNK_SIZE_MAX / 1024 / 1024, BUFFER_SIZE_MB, SCRYPT_MAX_MEM / 1024 / 1024); default: %d\n", CHUNK_SIZE_MAX_GCM / 1024 / 1024, CHUNK_SIZE_MAX_CHACHA / 1024 / 1024, BUFFER_SIZE_MB, SCRYPT_MAX_MEM / 1024 / 1024);
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\
@ -767,7 +925,7 @@ uint32_t next_highest_power_of_2(uint32_t v) {
/* returns 0 on success, 1 on openssl failure, 2 on other failure */ /* returns 0 on success, 1 on openssl failure, 2 on other failure */
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int optind, decrypt = 0, append = 0, exit_code = 2; int optind, decrypt = 0, append = 0, exit_code = 2, version = -1;
char *password = NULL; char *password = NULL;
uint32_t N = SCRYPT_N, scrypt_max_mem = SCRYPT_MAX_MEM, buffer_size = BUFFER_SIZE_MB * 1024 * 1024, scale = 1; uint32_t N = SCRYPT_N, scrypt_max_mem = SCRYPT_MAX_MEM, 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;
@ -775,26 +933,6 @@ int main(int argc, char **argv)
FILE *in = stdin, *out = stdout, *err = stderr; FILE *in = stdin, *out = stdout, *err = stderr;
char *in_filename = NULL, *out_filename = NULL; char *in_filename = NULL, *out_filename = NULL;
#ifdef PEGH_LIBSODIUM
if (sodium_init() == -1) {
fprintf(stderr, "Error: libsodium could not be initialized, compile/use openssl version?\n");
return 2;
}
if (crypto_aead_aes256gcm_is_available() == 0) {
#ifdef PEGH_OPENSSL
/* swap to OpenSSL AES which is always supported */
fprintf(stderr, "Warning: libsodium does not support AES-256-GCM on this CPU, falling back to openssl version instead...\n");
gcm_encrypt = gcm_encrypt_openssl;
gcm_decrypt = gcm_decrypt_openssl;
CHUNK_SIZE_MAX = CHUNK_SIZE_MAX_OPENSSL;
#else
/* nothing we can do */
fprintf(stderr, "Error: libsodium does not support AES-256-GCM on this CPU, compile/use openssl version?\n");
return 2;
#endif /* PEGH_OPENSSL */
}
#endif /* PEGH_LIBSODIUM */
for (optind = 1; optind < argc; ++optind) { for (optind = 1; optind < argc; ++optind) {
if(strlen(argv[optind]) == 2 && argv[optind][0] == '-') { if(strlen(argv[optind]) == 2 && argv[optind][0] == '-') {
@ -830,10 +968,6 @@ int main(int argc, char **argv)
break; break;
case 'c': case 'c':
buffer_size = parse_int_arg(++optind, argc, argv) * 1024 * 1024; buffer_size = parse_int_arg(++optind, argc, argv) * 1024 * 1024;
if(buffer_size > CHUNK_SIZE_MAX) {
fprintf(stderr, "Error: %s chunk size cannot exceed %lu megabytes\n", argv[optind - 1], CHUNK_SIZE_MAX / 1024 / 1024);
return help(2);
}
break; break;
case 'm': case 'm':
scrypt_max_mem = parse_int_arg(++optind, argc, argv) * 1024 * 1024; scrypt_max_mem = parse_int_arg(++optind, argc, argv) * 1024 * 1024;
@ -853,6 +987,9 @@ int main(int argc, char **argv)
case 'q': case 'q':
err = NULL; err = NULL;
break; break;
case 'v':
version = parse_byte_arg(++optind, argc, argv);
break;
case 'V': case 'V':
fprintf(stderr, "pegh %s\nformat versions supported: 0\n", PEGH_VERSION); fprintf(stderr, "pegh %s\nformat versions supported: 0\n", PEGH_VERSION);
return 0; return 0;
@ -893,6 +1030,13 @@ int main(int argc, char **argv)
return 0; return 0;
*/ */
#ifdef PEGH_LIBSODIUM
if (sodium_init() == -1) {
fprintf(stderr, "Error: libsodium could not be initialized, compile/use openssl version?\n");
return 2;
}
#endif /* PEGH_LIBSODIUM */
if(NULL != in_filename) { if(NULL != in_filename) {
in = fopen(in_filename, "rb"); in = fopen(in_filename, "rb");
if(!in) { if(!in) {
@ -912,8 +1056,31 @@ int main(int argc, char **argv)
if(decrypt) if(decrypt)
exit_code = pegh_decrypt(password, scrypt_max_mem, buffer_size, in, out, err); exit_code = pegh_decrypt(password, scrypt_max_mem, buffer_size, in, out, err);
else else {
exit_code = pegh_encrypt(password, scrypt_max_mem, buffer_size, in, out, err, N, r, p); if(-1 == version) {
/* they left this as default, so attempt to pick best (fastest) version */
#ifdef PEGH_LIBSODIUM
/* libsodium is easy, it only supports AES if it is CPU hardware accelerated,
* so if it is AES is fastest, otherwise must use Chacha20
*/
version = crypto_aead_aes256gcm_is_available() == 0 ? 1 : 0;
#elif defined(PEGH_OPENSSL)
/* OpenSSL is NOT easy, ideally we'd also pick AES if hardware accelerated,
* and Chacha20 otherwise, there is *a* way https://stackoverflow.com/a/25284682
* but not a good/supported/standard way, for now just assume AES is faster on
* 64-bit platforms???
*/
#if (1024UL * 1024 * 1024 * 32) > SIZE_MAX
/* small SIZE_MAX, Chacha20 */
version = 1;
#else
/* greated than 32gb SIZE_MAX, AES */
version = 0;
#endif
#endif /* PEGH_OPENSSL */
}
exit_code = pegh_encrypt(password, scrypt_max_mem, buffer_size, in, out, err, (uint8_t) version, N, r, p);
}
if(NULL != in_filename) if(NULL != in_filename)
fclose(in); fclose(in);