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")"
# dependencies to build+test pegh
apk add build-base clang bash libsodium-dev libsodium-static libressl-dev
# 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
apk add build-base clang bash libsodium-dev libsodium-static 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
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
# 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
if ./pegh.static.libsodium -h 2>&1 >/dev/null | grep '^Error: libsodium'
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.libsodium "./pegh-$ARCH-libsodium"
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
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;
/* from libsodium's crypto_pwhash_scryptsalsa208sha256_SALTBYTES */
#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
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 */
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 *,
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
/* 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
@ -118,9 +127,9 @@ static const size_t CHUNK_SIZE_MAX_OPENSSL = INT_MAX;
*
* 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
* 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 *iv,
unsigned char *ciphertext,
@ -136,11 +145,11 @@ int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_l
break;
/* 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;
/* 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;
*/
@ -171,7 +180,7 @@ int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_l
break;
/* 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);
/* Clean up */
@ -189,12 +198,12 @@ int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_l
* ciphertext_len
* key must be length KEY_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:
* 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 *iv,
unsigned char *tag,
@ -210,11 +219,11 @@ int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext
break;
/* 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;
/* 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;
*/
@ -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 */
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;
/*
@ -255,10 +264,56 @@ int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext
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 */
#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 */
int scrypt_derive_key(char *password, size_t password_len,
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
/*
* 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
*/
#if (1024UL * 1024 * 1024 * 32) > SIZE_MAX
static const size_t CHUNK_SIZE_MAX_LIBSODIUM = SIZE_MAX;
#if 274877906880 > SIZE_MAX
static const size_t CHUNK_SIZE_MAX_LIBSODIUM_GCM = SIZE_MAX;
static const size_t CHUNK_SIZE_MAX_CHACHA = SIZE_MAX;
#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
/*
@ -316,7 +375,7 @@ static const size_t CHUNK_SIZE_MAX_LIBSODIUM = 1024UL * 1024 * 1024 * 32;
*
* 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
* tag must have the capacity of at least AEAD_TAG_LEN
*/
int gcm_encrypt_libsodium(const unsigned char *plaintext, const size_t plaintext_len,
const unsigned char *key,
@ -341,7 +400,7 @@ int gcm_encrypt_libsodium(const unsigned char *plaintext, const size_t plaintext
* ciphertext_len
* key must be length KEY_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:
* 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;
}
/*
* 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 */
int scrypt_derive_key(char *password, size_t password_len,
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
#endif
static PEGH_CONST gcm_func gcm_encrypt = gcm_encrypt_libsodium;
static PEGH_CONST gcm_func gcm_decrypt = gcm_decrypt_libsodium;
static PEGH_CONST size_t CHUNK_SIZE_MAX = CHUNK_SIZE_MAX_LIBSODIUM;
static PEGH_CONST aead_func gcm_encrypt = gcm_encrypt_libsodium;
static PEGH_CONST aead_func gcm_decrypt = gcm_decrypt_libsodium;
static PEGH_CONST size_t CHUNK_SIZE_MAX_GCM = CHUNK_SIZE_MAX_LIBSODIUM_GCM;
#elif PEGH_OPENSSL
static const gcm_func gcm_encrypt = gcm_encrypt_openssl;
static const gcm_func gcm_decrypt = gcm_decrypt_openssl;
static const size_t CHUNK_SIZE_MAX = CHUNK_SIZE_MAX_OPENSSL;
static const aead_func gcm_encrypt = gcm_encrypt_openssl;
static const aead_func gcm_decrypt = gcm_decrypt_openssl;
static const size_t CHUNK_SIZE_MAX_GCM = CHUNK_SIZE_MAX_OPENSSL_GCM;
#endif
/* 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 */
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,
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) {
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;
if(1 != iv_increment_forbid_zero(iv, IV_LEN, err))
return 0;
fwrite(ciphertext, 1, plaintext_read + GCM_TAG_LEN, out);
fwrite(ciphertext, 1, plaintext_read + AEAD_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,
int decrypt_stream(const aead_func decrypt, 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;
buffer_size += AEAD_TAG_LEN;
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)
fprintf(err, "File too small for decryption, truncated?\n");
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;
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
* err can be NULL in which case no messages are printed
*/
int gcm_stream(const unsigned char *key, size_t buffer_size,
int decrypt,
FILE *in, FILE *out, FILE *err
int stream(const stream_func crypt_stream, const aead_func aead, unsigned char *key, size_t buffer_size,
FILE *in, FILE *out, FILE *err
)
{
/* 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 */
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) {
#ifdef PEGH_OPENSSL
fprintf(err, "due to openssl API limitation, buffer_size can at most be %lu\n", CHUNK_SIZE_MAX);
#endif
#ifdef PEGH_LIBSODIUM
fprintf(err, "due to AES-256-GCM security constraints, buffer_size can at most be %lu\n", CHUNK_SIZE_MAX);
#if defined(PEGH_OPENSSL) && defined(PEGH_LIBSODIUM)
/* the ugliest branch, openssl limits only apply if it's GCM and we are using openssl's impl */
if(is_gcm && gcm_decrypt == gcm_decrypt_openssl) {
fprintf(err, "due to openssl API limitation, 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
}
return 0;
@ -538,7 +662,7 @@ int gcm_stream(const unsigned char *key, size_t buffer_size,
fprintf(err, "plaintext memory allocation failed\n");
return 0;
}
ciphertext = malloc(buffer_size + GCM_TAG_LEN);
ciphertext = malloc(buffer_size + AEAD_TAG_LEN);
if(!ciphertext) {
if(NULL != err)
fprintf(err, "ciphertext memory allocation failed\n");
@ -546,8 +670,7 @@ int gcm_stream(const unsigned char *key, size_t buffer_size,
return 0;
}
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);
exit_code = crypt_stream(aead, key, iv, buffer_size, plaintext, ciphertext, in, out, err);
free(plaintext);
free(ciphertext);
@ -557,7 +680,7 @@ int gcm_stream(const unsigned char *key, size_t buffer_size,
/* print openssl errors */
ERR_print_errors_fp(err);
#endif
fprintf(err, "%scryption failed\n", decrypt ? "de" : "en");
fprintf(err, "%scryption failed\n", crypt_stream == decrypt_stream ? "de" : "en");
}
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 */
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,
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};
int ret;
size_t password_len;
@ -594,22 +717,56 @@ int scrypt_derive_key_gcm_stream(char *password,
wipe_memory(password, password_len);
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);
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 */
int pegh_encrypt(char *password,
uint32_t scrypt_max_mem, size_t buffer_size,
FILE *in, FILE *out, FILE *err,
uint8_t version,
uint32_t N, uint8_t r, uint8_t p)
{
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 */
salt[0] = 0;
salt[0] = version;
write_uint32_big_endian(N, salt+1);
salt[5] = r;
salt[6] = p;
@ -628,7 +785,7 @@ int pegh_encrypt(char *password,
}
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 */
@ -641,7 +798,7 @@ int pegh_decrypt(char *password,
size_t header_read, buffer_size;
uint32_t N;
uint8_t r, p;
uint8_t version, r, p;
/* first read the version and parameters */
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");
return 0;
}
if(salt[0] != 0) {
if(NULL != err)
fprintf(err, "unsupported file format version %d, we only support version 0\n", salt[0]);
version = salt[0];
if(1 != check_version(version, err)) {
fprintf(stderr, "Error: decryption aborting, this file cannot be decrypted with this version/CPU\n");
return 0;
}
N = read_uint32_big_endian(salt+1);
@ -673,7 +830,8 @@ int pegh_decrypt(char *password,
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) {
@ -694,11 +852,11 @@ usage: pegh [options...] password\n\
fprintf(stderr, "\
only allocated after scrypt is finished so max usage will be\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\
with scrypt, applies for encryption AND decryption, must\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, "\
-N <num> scrypt parameter N, only applies for encryption, default %d\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 */
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;
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;
@ -775,26 +933,6 @@ int main(int argc, char **argv)
FILE *in = stdin, *out = stdout, *err = stderr;
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) {
if(strlen(argv[optind]) == 2 && argv[optind][0] == '-') {
@ -830,10 +968,6 @@ int main(int argc, char **argv)
break;
case 'c':
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;
case 'm':
scrypt_max_mem = parse_int_arg(++optind, argc, argv) * 1024 * 1024;
@ -853,6 +987,9 @@ int main(int argc, char **argv)
case 'q':
err = NULL;
break;
case 'v':
version = parse_byte_arg(++optind, argc, argv);
break;
case 'V':
fprintf(stderr, "pegh %s\nformat versions supported: 0\n", PEGH_VERSION);
return 0;
@ -893,6 +1030,13 @@ int main(int argc, char **argv)
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) {
in = fopen(in_filename, "rb");
if(!in) {
@ -912,8 +1056,31 @@ int main(int argc, char **argv)
if(decrypt)
exit_code = pegh_decrypt(password, scrypt_max_mem, buffer_size, in, out, err);
else
exit_code = pegh_encrypt(password, scrypt_max_mem, buffer_size, in, out, err, N, r, p);
else {
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)
fclose(in);