1489 lines
50 KiB
C
1489 lines
50 KiB
C
|
||
/*
|
||
* pegh is a file encryption tool using passwords and authenticated encryption
|
||
* Copyright (C) 2019 Travis Burtrum
|
||
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU Affero General Public License as published by
|
||
* the Free Software Foundation, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU Affero General Public License for more details.
|
||
|
||
* You should have received a copy of the GNU Affero General Public License
|
||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
/* compile with: cc pegh.c -lcrypto -O3 -o pegh */
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <limits.h>
|
||
#include <errno.h>
|
||
|
||
#if !defined(_WIN32) && (_POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE)
|
||
#define USE_TERMIOS 1
|
||
#endif
|
||
|
||
#ifdef USE_TERMIOS
|
||
#include <termios.h>
|
||
#endif
|
||
|
||
#ifdef _WIN32
|
||
|
||
#include <windows.h>
|
||
#include <io.h>
|
||
#include <fcntl.h>
|
||
|
||
#define STDIN 0
|
||
#define STDOUT 1
|
||
|
||
#endif
|
||
|
||
/* default of OpenSSL for now... */
|
||
#if !defined(PEGH_OPENSSL) && !defined(PEGH_LIBSODIUM)
|
||
#define PEGH_OPENSSL 1
|
||
#endif
|
||
|
||
#ifdef PEGH_OPENSSL
|
||
|
||
#include <openssl/conf.h>
|
||
#include <openssl/evp.h>
|
||
#include <openssl/err.h>
|
||
#include <openssl/rand.h>
|
||
|
||
#endif /* PEGH_OPENSSL */
|
||
|
||
#ifdef PEGH_LIBSODIUM
|
||
|
||
#include <sodium.h>
|
||
|
||
#endif /* PEGH_LIBSODIUM */
|
||
|
||
/*
|
||
* tweak default scrypt hardness params here
|
||
*
|
||
* https://tools.ietf.org/html/rfc7914#section-2
|
||
* https://blog.filippo.io/the-scrypt-parameters/
|
||
*/
|
||
const uint32_t SCRYPT_N = 32768;
|
||
const uint8_t SCRYPT_R = 8;
|
||
const uint8_t SCRYPT_P = 1;
|
||
const size_t SCRYPT_MAX_MEM = 1024 * 1024 * 64; /* 64 megabytes */
|
||
|
||
/* memory use will be twice this */
|
||
const uint32_t DEFAULT_CHUNK_SIZE_MB = 32;
|
||
|
||
/*
|
||
* this should be increased regularly, and only checked on encryption
|
||
* to allow old archives to be decrypted with shorter passwords
|
||
*/
|
||
const size_t MINIMUM_PASSWORD_LEN = 12;
|
||
/* technically they can only enter at most 2 less than this */
|
||
const size_t MANUAL_ENTRY_PASSWORD_MAX_LEN = 66;
|
||
|
||
/*
|
||
* pegh file format, numbers are inclusive 0-based byte array indices
|
||
*
|
||
* 0th byte is always version number, everything else depends on version number
|
||
*
|
||
* |------------------------------------------------------------------------------------------------------|
|
||
* | 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). The last |
|
||
* | chunk has Additional Authenticated Data (AAD) sent in, a single byte value 0, which is used to flag |
|
||
* | the last chunk and detect file truncation. |
|
||
* |--------------|---------------------------------------------|-----------------------------------------|
|
||
* | indices | format | value interpretation |
|
||
* |--------------|---------------------------------------------|-----------------------------------------|
|
||
* | 0 | 8 bit unsigned byte | pegh file format version |
|
||
* | 1-4 | 32 bit unsigned integer in big endian order | scrypt N parameter |
|
||
* | 5 | 8 bit unsigned byte | scrypt r parameter |
|
||
* | 6 | 8 bit unsigned byte | scrypt p parameter |
|
||
* | 7-10 | 32 bit unsigned integer in big endian order | aes encrypted chunk size |
|
||
* | 11-42 | 32 randomly generated bytes | scrypt key derivation seed |
|
||
* | 43+end | any number of chunks, chunk_size + 16 long | chunks followed by AES-256-GCM auth tag |
|
||
* |------------------------------------------------------------------------------------------------------|
|
||
*
|
||
* Version 1 has the exact same structure as version 0, except Chacha20-Poly1305 encryption instead of AES-256-GCM,
|
||
* key, IV, tag lengths are all the same.
|
||
*/
|
||
|
||
/* don't touch below here unless you know what you are doing */
|
||
|
||
#define PEGH_VERSION "0.9.3"
|
||
|
||
/* 256 bit key required for AES-256 */
|
||
#define KEY_LEN 32
|
||
|
||
/* 1 for file format version, 4 for N, 1 for r, 1 for p, 4 for block/buffer size */
|
||
const size_t PRE_SALT_LEN = 11;
|
||
/* from libsodium's crypto_pwhash_scryptsalsa208sha256_SALTBYTES */
|
||
#define SALT_LEN 32
|
||
/* AES-GCM/Chacha20-Poly1305 should only ever have an IV_LEN of 12 */
|
||
#define IV_LEN 12
|
||
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 (*aead_func)(const unsigned char *, const size_t,
|
||
const unsigned char *, const unsigned char *,
|
||
const unsigned char*, const size_t,
|
||
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_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
|
||
*
|
||
* 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 aead_encrypt_openssl(const EVP_CIPHER *type, const unsigned char *plaintext, const size_t plaintext_len,
|
||
const unsigned char *key,
|
||
const unsigned char *iv,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
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, 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_AEAD_SET_IVLEN, IV_LEN, NULL))
|
||
break;
|
||
*/
|
||
|
||
/* Initialise key and IV */
|
||
if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
|
||
break;
|
||
|
||
if(aad_len > 0) {
|
||
/*
|
||
* Provide any AAD data. This can be called zero or more times as
|
||
* required
|
||
*/
|
||
if(1 != EVP_EncryptUpdate(ctx, NULL, &ciphertext_written, aad, (int) aad_len))
|
||
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, (int) 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_AEAD_GET_TAG, AEAD_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 AEAD_TAG_LEN
|
||
*
|
||
* these will be written into:
|
||
* plaintext must have the capacity of at least 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,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
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, 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_AEAD_SET_IVLEN, IV_LEN, NULL))
|
||
break;
|
||
*/
|
||
|
||
/* Initialise key and IV */
|
||
if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
|
||
break;
|
||
|
||
if(aad_len > 0) {
|
||
/*
|
||
* Provide any AAD data. This can be called zero or more times as
|
||
* required
|
||
*/
|
||
if(1 != EVP_DecryptUpdate(ctx, NULL, &plaintext_written, aad, (int) aad_len))
|
||
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, (int) 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_AEAD_SET_TAG, AEAD_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;
|
||
}
|
||
|
||
int gcm_encrypt_openssl(const unsigned char *plaintext, const size_t plaintext_len,
|
||
const unsigned char *key,
|
||
const unsigned char *iv,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *ciphertext,
|
||
unsigned char *tag
|
||
) {
|
||
return aead_encrypt_openssl(EVP_aes_256_gcm(), plaintext, plaintext_len,
|
||
key, iv,
|
||
aad, aad_len,
|
||
ciphertext, tag);
|
||
}
|
||
|
||
int gcm_decrypt_openssl(const unsigned char *ciphertext, const size_t ciphertext_len,
|
||
const unsigned char *key,
|
||
const unsigned char *iv,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *tag,
|
||
unsigned char *plaintext
|
||
)
|
||
{
|
||
return aead_decrypt_openssl(EVP_aes_256_gcm(), ciphertext, ciphertext_len,
|
||
key, iv,
|
||
aad, aad_len,
|
||
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,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *ciphertext,
|
||
unsigned char *tag
|
||
) {
|
||
return aead_encrypt_openssl(EVP_chacha20_poly1305(), plaintext, plaintext_len,
|
||
key, iv,
|
||
aad, aad_len,
|
||
ciphertext, tag);
|
||
}
|
||
|
||
int chacha_decrypt(const unsigned char *ciphertext, const size_t ciphertext_len,
|
||
const unsigned char *key,
|
||
const unsigned char *iv,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *tag,
|
||
unsigned char *plaintext
|
||
)
|
||
{
|
||
return aead_decrypt_openssl(EVP_chacha20_poly1305(), ciphertext, ciphertext_len,
|
||
key, iv,
|
||
aad, aad_len,
|
||
tag,
|
||
plaintext);
|
||
}
|
||
|
||
/* returns 1 on success, 0 on error */
|
||
int scrypt_derive_key(char *password, const size_t password_len,
|
||
uint32_t scrypt_max_mem, uint32_t N,
|
||
uint8_t r, uint8_t p, unsigned char *salt, unsigned char *key, FILE *err) {
|
||
/* derive key using salt, password, and scrypt parameters */
|
||
if (EVP_PBE_scrypt(
|
||
password, password_len,
|
||
salt, SALT_LEN,
|
||
(uint64_t) N, (uint64_t) r, (uint64_t) p,
|
||
(uint64_t) scrypt_max_mem,
|
||
key, KEY_LEN
|
||
) <= 0) {
|
||
if(NULL != err) {
|
||
fprintf(err, "scrypt key derivation error\n");
|
||
ERR_print_errors_fp(err);
|
||
}
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* returns 1 on success, 0 on error */
|
||
int random_salt(unsigned char *salt) {
|
||
return RAND_bytes(salt, SALT_LEN) <= 0 ? 0 : 1;
|
||
}
|
||
|
||
void wipe_memory(void * const ptr, const size_t len) {
|
||
OPENSSL_cleanse(ptr, len);
|
||
}
|
||
|
||
#endif /* PEGH_LIBSODIUM */
|
||
|
||
#endif /* PEGH_OPENSSL */
|
||
|
||
#ifdef PEGH_LIBSODIUM
|
||
|
||
/*
|
||
* 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 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
|
||
/* 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
|
||
|
||
/*
|
||
* 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 gcm_encrypt_libsodium(const unsigned char *plaintext, const size_t plaintext_len,
|
||
const unsigned char *key,
|
||
const unsigned char *iv,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *ciphertext,
|
||
unsigned char *tag
|
||
)
|
||
{
|
||
crypto_aead_aes256gcm_encrypt_detached(ciphertext,
|
||
tag, NULL,
|
||
plaintext, plaintext_len,
|
||
aad, aad_len,
|
||
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 gcm_decrypt_libsodium(const unsigned char *ciphertext, const size_t ciphertext_len,
|
||
const unsigned char *key,
|
||
const unsigned char *iv,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *tag,
|
||
unsigned char *plaintext
|
||
)
|
||
{
|
||
return crypto_aead_aes256gcm_decrypt_detached(plaintext,
|
||
NULL,
|
||
ciphertext, (size_t) ciphertext_len,
|
||
tag,
|
||
aad, aad_len,
|
||
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,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *ciphertext,
|
||
unsigned char *tag
|
||
)
|
||
{
|
||
crypto_aead_chacha20poly1305_ietf_encrypt_detached(ciphertext,
|
||
tag, NULL,
|
||
plaintext, plaintext_len,
|
||
aad, aad_len,
|
||
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,
|
||
const unsigned char* aad, const size_t aad_len,
|
||
unsigned char *tag,
|
||
unsigned char *plaintext
|
||
)
|
||
{
|
||
return crypto_aead_chacha20poly1305_ietf_decrypt_detached(plaintext,
|
||
NULL,
|
||
ciphertext, (size_t) ciphertext_len,
|
||
tag,
|
||
aad, aad_len,
|
||
iv, key) != 0 ? 0 : 1;
|
||
}
|
||
|
||
/* returns 1 on success, 0 on error */
|
||
int scrypt_derive_key(char *password, const size_t password_len,
|
||
uint32_t scrypt_max_mem, uint32_t N,
|
||
uint8_t r, uint8_t p, unsigned char *salt, unsigned char *key, FILE *err) {
|
||
size_t needed_memory;
|
||
/* derive key using salt, password, and scrypt parameters */
|
||
|
||
/* this is how crypto_pwhash_scryptsalsa208sha256_ll calculates the memory needed, so do it here first and check */
|
||
needed_memory = (size_t) 128 * r * p;
|
||
needed_memory += (size_t) 128 * r * (size_t) N;
|
||
needed_memory += (size_t) 256 * r + 64;
|
||
|
||
if (needed_memory > scrypt_max_mem) {
|
||
if(NULL != err) {
|
||
/* +1 is to round up here and avoid math.h and ceil()... */
|
||
fprintf(err, "scrypt key derivation error, needed memory %lu mb, allowed memory %d mb, increase -m\n", (needed_memory / 1024 / 1024) + 1, scrypt_max_mem / 1024 / 1024);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
if (crypto_pwhash_scryptsalsa208sha256_ll(
|
||
(const uint8_t *) password, password_len,
|
||
salt, SALT_LEN,
|
||
(uint64_t) N, (uint32_t) r, (uint32_t) p,
|
||
key, KEY_LEN
|
||
) < 0) {
|
||
if(NULL != err) {
|
||
fprintf(err, "scrypt key derivation error\n");
|
||
}
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/* returns 1 on success, 0 on error */
|
||
int random_salt(unsigned char *salt) {
|
||
randombytes_buf(salt, SALT_LEN);
|
||
return 1;
|
||
}
|
||
|
||
void wipe_memory(void * const ptr, const size_t len) {
|
||
sodium_memzero(ptr, len);
|
||
}
|
||
|
||
#endif /* PEGH_LIBSODIUM */
|
||
|
||
/* always prefer libsodium AES if possible because it's faster */
|
||
#ifdef PEGH_LIBSODIUM
|
||
|
||
/* if PEGH_OPENSSL is defined, these can be redefined at runtime depending on CPU, otherwise const */
|
||
#ifndef PEGH_OPENSSL
|
||
#define PEGH_CONST const
|
||
#else
|
||
#define PEGH_CONST
|
||
#endif
|
||
|
||
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 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 */
|
||
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 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
|
||
) {
|
||
unsigned char aad[1] = {0};
|
||
unsigned char* plaintext_read_zone = plaintext + 1;
|
||
size_t plaintext_read, extra_byte_idx = buffer_size - 1;
|
||
|
||
/* read single byte */
|
||
plaintext_read = fread(plaintext, 1, 1, in);
|
||
if(plaintext_read != 1) {
|
||
if(NULL != err)
|
||
fprintf(err, "Cowardly refusing to encrypt empty file...\n");
|
||
return 0;
|
||
}
|
||
|
||
while ((plaintext_read = fread(plaintext_read_zone, 1, buffer_size, in)) == buffer_size) {
|
||
|
||
/* there are more chunks after this */
|
||
if(1 != encrypt(plaintext, plaintext_read, key, iv, NULL, 0, ciphertext, ciphertext + plaintext_read))
|
||
return 0;
|
||
|
||
/* save extra byte at end */
|
||
plaintext[0] = plaintext_read_zone[extra_byte_idx];
|
||
|
||
if(1 != iv_increment_forbid_zero(iv, IV_LEN, err))
|
||
return 0;
|
||
|
||
fwrite(ciphertext, 1, plaintext_read + AEAD_TAG_LEN, out);
|
||
}
|
||
/* this is the last chunk, use AD */
|
||
plaintext_read += 1;
|
||
|
||
if(1 != encrypt(plaintext, plaintext_read, key, iv, aad, 1, ciphertext, ciphertext + plaintext_read))
|
||
return 0;
|
||
|
||
fwrite(ciphertext, 1, plaintext_read + AEAD_TAG_LEN, out);
|
||
|
||
return 1;
|
||
}
|
||
|
||
/* returns 1 on success, 0 on failure */
|
||
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
|
||
) {
|
||
unsigned char aad[1] = {0};
|
||
unsigned char* ciphertext_read_zone = ciphertext + 1;
|
||
size_t ciphertext_read, extra_byte_idx = buffer_size + AEAD_TAG_LEN - 1;
|
||
|
||
buffer_size += AEAD_TAG_LEN;
|
||
|
||
ciphertext_read = fread(ciphertext, 1, 1, in);
|
||
if(ciphertext_read != 1) {
|
||
if(NULL != err)
|
||
fprintf(err, "File too small for decryption, truncated?\n");
|
||
return 0;
|
||
}
|
||
|
||
while ((ciphertext_read = fread(ciphertext_read_zone, 1, buffer_size, in)) == buffer_size) {
|
||
|
||
if(ciphertext_read < AEAD_TAG_LEN) {
|
||
if(NULL != err)
|
||
fprintf(err, "File too small for decryption, truncated?\n");
|
||
return 0;
|
||
}
|
||
|
||
ciphertext_read -= AEAD_TAG_LEN;
|
||
|
||
if(1 != decrypt(ciphertext, ciphertext_read, key, iv, NULL, 0, ciphertext + ciphertext_read, plaintext))
|
||
return 0;
|
||
|
||
/* save extra byte at end */
|
||
ciphertext[0] = ciphertext_read_zone[extra_byte_idx];
|
||
|
||
if(1 != iv_increment_forbid_zero(iv, IV_LEN, err))
|
||
return 0;
|
||
|
||
fwrite(plaintext, 1, ciphertext_read, out);
|
||
}
|
||
/* this is the last chunk, use AD */
|
||
ciphertext_read += 1;
|
||
|
||
if(ciphertext_read < AEAD_TAG_LEN) {
|
||
if(NULL != err)
|
||
fprintf(err, "File too small for decryption, truncated?\n");
|
||
return 0;
|
||
}
|
||
|
||
ciphertext_read -= AEAD_TAG_LEN;
|
||
|
||
if(1 != decrypt(ciphertext, ciphertext_read, key, iv, aad, 1, ciphertext + ciphertext_read, plaintext))
|
||
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
|
||
*
|
||
* 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
|
||
* in/out must be set
|
||
* err can be NULL in which case no messages are printed
|
||
*/
|
||
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 */
|
||
unsigned char iv[IV_LEN] = {0};
|
||
/* these are actually mallocd and freed */
|
||
unsigned char *plaintext = NULL, *ciphertext = NULL;
|
||
|
||
size_t chunk_size_max;
|
||
int exit_code = 0, is_gcm;
|
||
|
||
/* 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 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;
|
||
}
|
||
|
||
plaintext = malloc(buffer_size + 1);
|
||
if(!plaintext) {
|
||
if(NULL != err)
|
||
fprintf(err, "plaintext memory allocation failed\n");
|
||
return 0;
|
||
}
|
||
ciphertext = malloc(buffer_size + AEAD_TAG_LEN + 1);
|
||
if(!ciphertext) {
|
||
if(NULL != err)
|
||
fprintf(err, "ciphertext memory allocation failed\n");
|
||
free(plaintext);
|
||
return 0;
|
||
}
|
||
|
||
exit_code = crypt_stream(aead, key, iv, buffer_size, plaintext, ciphertext, in, out, err);
|
||
|
||
free(plaintext);
|
||
free(ciphertext);
|
||
|
||
if(NULL != err && exit_code != 1) {
|
||
#ifdef PEGH_OPENSSL
|
||
/* print openssl errors */
|
||
ERR_print_errors_fp(err);
|
||
#endif
|
||
fprintf(err, "%scryption failed\n", crypt_stream == decrypt_stream ? "de" : "en");
|
||
}
|
||
|
||
return exit_code;
|
||
}
|
||
|
||
/* buf must be at least 4 bytes */
|
||
uint32_t read_uint32_big_endian(const unsigned char *buf) {
|
||
return (uint32_t) ((buf[0] & 0xFF) << 24)
|
||
| (uint32_t) ((buf[1] & 0xFF) << 16)
|
||
| (uint32_t) ((buf[2] & 0xFF) << 8)
|
||
| (uint32_t) (buf[3] & 0xFF);
|
||
}
|
||
|
||
/* buf must be at least 4 bytes */
|
||
void write_uint32_big_endian(uint32_t val, unsigned char *buf) {
|
||
buf[0] = (unsigned char) ((val >> 24) & 0xFF);
|
||
buf[1] = (val >> 16) & 0xFF;
|
||
buf[2] = (val >> 8) & 0xFF;
|
||
buf[3] = val & 0xFF;
|
||
}
|
||
|
||
/* returns 1 on success, 0 on failure */
|
||
int scrypt_derive_key_stream(const stream_func crypt_stream, const aead_func aead,
|
||
char *password, const size_t password_len,
|
||
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) {
|
||
unsigned char key[KEY_LEN] = {0};
|
||
int ret;
|
||
|
||
ret = scrypt_derive_key(password, password_len, scrypt_max_mem, N, r, p, salt, key, err);
|
||
wipe_memory(password, password_len);
|
||
|
||
if(ret == 1)
|
||
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
|
||
/* check for AES encrypted but libsodium/hardware AES is not available */
|
||
#ifdef PEGH_OPENSSL
|
||
/* if we've already checked/set this, don't again */
|
||
else if (gcm_encrypt != gcm_encrypt_openssl && version == 0 && crypto_aead_aes256gcm_is_available() == 0) {
|
||
/* 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
|
||
else if (version == 0 && crypto_aead_aes256gcm_is_available() == 0) {
|
||
/* nothing we can do */
|
||
fprintf(stderr, "Error: libsodium does not support AES-256-GCM on this CPU, compile/use openssl version?\n");
|
||
return 19;
|
||
#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, const size_t password_len,
|
||
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] = version;
|
||
write_uint32_big_endian(N, salt+1);
|
||
salt[5] = r;
|
||
salt[6] = p;
|
||
write_uint32_big_endian((uint32_t) buffer_size, salt+7);
|
||
fwrite(salt, 1, PRE_SALT_LEN, out);
|
||
|
||
/* generate random salt, then write it out */
|
||
if (random_salt(salt) != 1) {
|
||
if(NULL != err) {
|
||
fprintf(err, "random salt generation error\n");
|
||
#ifdef PEGH_OPENSSL
|
||
ERR_print_errors_fp(err);
|
||
#endif
|
||
}
|
||
return 0;
|
||
}
|
||
fwrite(salt, 1, SALT_LEN, out);
|
||
|
||
return scrypt_derive_key_stream(encrypt_stream, version == 0 ? gcm_encrypt : chacha_encrypt,
|
||
password, password_len,
|
||
scrypt_max_mem, buffer_size, in, out, err, N, r, p, salt);
|
||
}
|
||
|
||
/* returns 1 on success, 0 on failure */
|
||
int pegh_decrypt(char *password, const size_t password_len,
|
||
uint32_t scrypt_max_mem, size_t max_buffer_size,
|
||
FILE *in, FILE *out, FILE *err)
|
||
{
|
||
unsigned char salt[SALT_LEN] = {0};
|
||
|
||
int version_exit_code;
|
||
|
||
size_t header_read, buffer_size;
|
||
|
||
uint32_t N;
|
||
uint8_t version, r, p;
|
||
|
||
/* first read the version and parameters */
|
||
header_read = fread(salt, 1, PRE_SALT_LEN, in);
|
||
if(header_read != PRE_SALT_LEN) {
|
||
if(NULL != err)
|
||
fprintf(err, "File too small for decryption, invalid header?\n");
|
||
return 0;
|
||
}
|
||
version = salt[0];
|
||
if(1 != (version_exit_code = check_version(version, err))) {
|
||
fprintf(stderr, "Error: decryption aborting, this file cannot be decrypted with this version/CPU\n");
|
||
return version_exit_code;
|
||
}
|
||
N = read_uint32_big_endian(salt+1);
|
||
r = salt[5];
|
||
p = salt[6];
|
||
buffer_size = read_uint32_big_endian(salt+7);
|
||
if(buffer_size > max_buffer_size) {
|
||
if(NULL != err)
|
||
fprintf(err, "memory required to decrypt file is %lu MB but only %lu MB allowed, increase -c\n", buffer_size / 1024 / 1024, max_buffer_size / 1024 / 1024);
|
||
return 0;
|
||
}
|
||
|
||
/* next read salt */
|
||
header_read = fread(salt, 1, SALT_LEN, in);
|
||
if(header_read != SALT_LEN) {
|
||
if(NULL != err)
|
||
fprintf(err, "File too small for decryption, invalid header?\n");
|
||
return 0;
|
||
}
|
||
|
||
return scrypt_derive_key_stream(decrypt_stream, version == 0 ? gcm_decrypt : chacha_decrypt,
|
||
password, password_len,
|
||
scrypt_max_mem, buffer_size, in, out, err, N, r, p, salt);
|
||
}
|
||
|
||
int help(int exit_code) {
|
||
/* this ridiculous split is because C89 only supports strings of 509 characters */
|
||
fprintf(stderr, "\
|
||
usage: pegh [options...] password\n\
|
||
password: minimum required length is %lu\n\
|
||
-e encrypt input to output, default mode\n\
|
||
-d decrypt input to output\n\
|
||
-i <filename> file to use for input, - means stdin, default stdin\n\
|
||
-o <filename> file to use for output, - means stdout, default stdout\n\
|
||
-a append to -o instead of truncate\n\
|
||
-v pegh file format version to output,\n", MINIMUM_PASSWORD_LEN);
|
||
fprintf(stderr, "\
|
||
either 0 (AES-256-GCM) or 1 (Chacha20-Poly1305),\n\
|
||
default: 0 if AES is hardware accelerated, 1 otherwise\n\
|
||
-c <mb> chunk size for encryption, while decrypting/encrypting twice\n\
|
||
this ram will be used, the same amount will be needed for\n\
|
||
decryption as encryption. This value is saved in the file\n\
|
||
format, so decryption will fail if this isn't set high enough,\n");
|
||
fprintf(stderr, "\
|
||
these are only allocated after scrypt is finished so max usage\n\
|
||
will be the highest of these only, not both combined,\n\
|
||
max: %lu (AES-256-GCM) or %lu (Chacha20-Poly1305),\n\
|
||
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_GCM / 1024 / 1024, CHUNK_SIZE_MAX_CHACHA / 1024 / 1024, DEFAULT_CHUNK_SIZE_MB, SCRYPT_MAX_MEM / 1024 / 1024);
|
||
fprintf(stderr, "\
|
||
-f <filename> read password from file instead of argument, - means stdin\n\
|
||
-g prompt for password, confirm on encryption, max characters: %lu\n",
|
||
MANUAL_ENTRY_PASSWORD_MAX_LEN - 2
|
||
);
|
||
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\
|
||
-r <num> scrypt parameter r, only applies for encryption, default %d\n\
|
||
-p <num> scrypt parameter p, only applies for encryption, default %d\n\
|
||
-s <num> multiplication factor to apply to both -N and -m for easy\n\
|
||
work scaling, rounded up to the next highest power of 2,\n", SCRYPT_N, SCRYPT_R, SCRYPT_P);
|
||
fprintf(stderr, "\
|
||
BEWARE: -s 32 requires 2G ram, -s 64 requires 4G and so on,\n\
|
||
default: 1\n\
|
||
-h print this usage text\n\
|
||
-q do not print error output to stderr\n\
|
||
-V show version number and format version support then quit\n\
|
||
\nFor additional info on scrypt params refer to:\n\
|
||
https://blog.filippo.io/the-scrypt-parameters/\n\
|
||
https://tools.ietf.org/html/rfc7914#section-2\n\n");
|
||
|
||
return exit_code;
|
||
}
|
||
|
||
void help_exit(int exit_code) {
|
||
help(exit_code);
|
||
exit(exit_code);
|
||
}
|
||
|
||
uint32_t parse_int_arg(int optind, int argc, char **argv) {
|
||
uint64_t tmp = 0;
|
||
|
||
if(optind >= argc) {
|
||
fprintf(stderr, "Error: %s requires an argument\n", argv[optind - 1]);
|
||
help_exit(2);
|
||
return 0;
|
||
}
|
||
errno = 0;
|
||
tmp = strtoul(argv[optind], NULL, 10);
|
||
if(errno != 0 || tmp < 1 || tmp > UINT_MAX) {
|
||
fprintf(stderr, "Error: %s %s failed to parse as a number\n", argv[optind - 1], argv[optind]);
|
||
help_exit(2);
|
||
return 0;
|
||
}
|
||
return (uint32_t) tmp;
|
||
}
|
||
|
||
uint8_t parse_byte_arg(int optind, int argc, char **argv) {
|
||
uint32_t tmp;
|
||
|
||
tmp = parse_int_arg(optind, argc, argv);
|
||
if(tmp > 255) {
|
||
fprintf(stderr, "Error: %s %s failed to parse as a number 1-255\n", argv[optind - 1], argv[optind]);
|
||
help_exit(2);
|
||
return 0;
|
||
}
|
||
return (uint8_t) tmp;
|
||
}
|
||
|
||
char* parse_filename_arg(int optind, int argc, char **argv) {
|
||
if(optind >= argc) {
|
||
fprintf(stderr, "Error: %s requires an argument\n", argv[optind - 1]);
|
||
help_exit(2);
|
||
return 0;
|
||
}
|
||
/* - means stdin or stdout, we return NULL */
|
||
return strlen(argv[optind]) == 1 && argv[optind][0] == '-' ? NULL : argv[optind];
|
||
}
|
||
|
||
uint32_t next_highest_power_of_2(uint32_t v) {
|
||
--v;
|
||
v |= v >> 1;
|
||
v |= v >> 2;
|
||
v |= v >> 4;
|
||
v |= v >> 8;
|
||
v |= v >> 16;
|
||
return ++v;
|
||
}
|
||
|
||
/* in_filename NULL means stdin */
|
||
char* read_file_fully(const char *in_filename, size_t *file_len) {
|
||
char* contents;
|
||
size_t read, actual_size = 0, total_size = 1024;
|
||
FILE *in = stdin;
|
||
|
||
if(NULL != in_filename) {
|
||
in = fopen(in_filename, "rb");
|
||
if(!in) {
|
||
fprintf (stderr, "Error: file '%s' cannot be opened for reading\n", in_filename);
|
||
return NULL;
|
||
}
|
||
}
|
||
#ifdef _WIN32
|
||
else {
|
||
/* windows in/out is text and mangles certain bytes by default... */
|
||
setmode(STDIN, O_BINARY);
|
||
}
|
||
#endif
|
||
|
||
contents = malloc(total_size);
|
||
if(!contents) {
|
||
fprintf (stderr, "Error: memory cannot be allocated to read file '%s'\n", in_filename);
|
||
if(NULL != in_filename) {
|
||
fclose(in);
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
while ((read = fread(contents + actual_size, 1, 512, in)) > 0) {
|
||
actual_size += read;
|
||
if ((actual_size + 512) > total_size) {
|
||
total_size = total_size * 2;
|
||
contents = realloc(contents, total_size);
|
||
if(!contents) {
|
||
fprintf (stderr, "Error: memory cannot be allocated to read file '%s'\n", in_filename);
|
||
if(NULL != in_filename) {
|
||
fclose(in);
|
||
}
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(NULL != in_filename) {
|
||
fclose(in);
|
||
}
|
||
|
||
/* now let's size it down to the minimum size */
|
||
contents = realloc(contents, actual_size);
|
||
if (!contents) {
|
||
fprintf (stderr, "Error: memory cannot be allocated to read file '%s'\n", in_filename);
|
||
return NULL;
|
||
}
|
||
|
||
*file_len = actual_size;
|
||
|
||
return contents;
|
||
}
|
||
|
||
/* returns -1 on error, or 0+ for length of string read into buff */
|
||
int read_passwd(char *prompt, char *buff, int buff_len, FILE *in, FILE *out)
|
||
{
|
||
int pass_len;
|
||
|
||
/*
|
||
* Turn echoing off and fail if we can’t.
|
||
* we support POSIX and Windows here
|
||
*/
|
||
#ifdef USE_TERMIOS
|
||
struct termios orig_input_flags, new_input_flags;
|
||
|
||
if (tcgetattr (fileno (in), &orig_input_flags) != 0)
|
||
return -1;
|
||
new_input_flags = orig_input_flags;
|
||
new_input_flags.c_lflag &= (tcflag_t) ~ECHO;
|
||
if (tcsetattr (fileno (in), TCSAFLUSH, &new_input_flags) != 0)
|
||
return -1;
|
||
#endif
|
||
#ifdef _WIN32
|
||
HANDLE hStdin;
|
||
DWORD orig_input_flags;
|
||
|
||
hStdin = GetStdHandle(STD_INPUT_HANDLE);
|
||
if (hStdin == INVALID_HANDLE_VALUE)
|
||
return -1;
|
||
|
||
/* Save the current input mode, to be restored on exit. */
|
||
if (! GetConsoleMode(hStdin, &orig_input_flags) )
|
||
return -1;
|
||
|
||
if (! SetConsoleMode(hStdin, orig_input_flags & (DWORD) ~ENABLE_ECHO_INPUT) )
|
||
return -1;
|
||
#endif
|
||
|
||
/* Display the prompt */
|
||
fprintf(out, "%s", prompt);
|
||
|
||
/* Read the password. cast is safe because buff_len is int */
|
||
pass_len = fgets (buff, buff_len, in) == NULL ? 0 : (int) strlen(buff);
|
||
|
||
/* Remove the line feed */
|
||
if (pass_len >= 1 && buff[pass_len - 1] == '\n') {
|
||
buff[--pass_len] = 0;
|
||
}
|
||
/* Remove the carriage return */
|
||
if (pass_len >= 1 && buff[pass_len - 1] == '\r') {
|
||
buff[--pass_len] = 0;
|
||
}
|
||
|
||
/* Restore terminal. we could check failure here but if it failed, what could we do? */
|
||
#ifdef USE_TERMIOS
|
||
tcsetattr (fileno (in), TCSAFLUSH, &orig_input_flags);
|
||
fprintf(out, "\n"); /* windows does this automatically */
|
||
#endif
|
||
#ifdef _WIN32
|
||
SetConsoleMode(hStdin, orig_input_flags);
|
||
#endif
|
||
|
||
return pass_len;
|
||
}
|
||
|
||
/* returns -1 on error, or 0+ for length of string read into buff */
|
||
int read_passwd_console(char *prompt, char *buff, int buff_len) {
|
||
int pass_len;
|
||
FILE *in;
|
||
#ifdef _WIN32
|
||
in = fopen("CONIN$", "r+");
|
||
/* this *should* work here but does not, just using stderr for now
|
||
out = fopen("CONOUT$", "w");
|
||
*/
|
||
if(in) {
|
||
pass_len = read_passwd(prompt, buff, buff_len, in, stderr);
|
||
fclose(in);
|
||
return pass_len;
|
||
}
|
||
#else
|
||
in = fopen("/dev/tty", "r+");
|
||
if(in) {
|
||
pass_len = read_passwd(prompt, buff, buff_len, in, in);
|
||
fclose(in);
|
||
return pass_len;
|
||
}
|
||
#endif
|
||
/* oh well, we tried */
|
||
return read_passwd(prompt, buff, buff_len, stdin, stderr);
|
||
}
|
||
|
||
/* returns -1 on error, or 0+ for length of string read into buff */
|
||
int read_passwd_console_confirm(char *buff, int buff_len, int min_passwd_length) {
|
||
int pass_len, confirm_len;
|
||
char *confirm_passwd;
|
||
|
||
confirm_passwd = malloc((size_t) buff_len);
|
||
if(!confirm_passwd)
|
||
return -1;
|
||
|
||
while(1) {
|
||
pass_len = read_passwd_console("Enter encryption password: ", buff, buff_len);
|
||
if(-1 == pass_len)
|
||
break; /* fail */
|
||
if(pass_len < min_passwd_length) {
|
||
fprintf(stderr, "Error: Minimum password length is %d but was only %d, try again...\n", min_passwd_length, pass_len);
|
||
continue;
|
||
}
|
||
if(pass_len == (buff_len - 1)) {
|
||
fprintf(stderr, "Error: Maximum password length for manual entry (%d) exceeded, try again...\n", buff_len - 2);
|
||
continue;
|
||
}
|
||
|
||
confirm_len = read_passwd_console("Confirm password: ", confirm_passwd, buff_len);
|
||
if(-1 == confirm_len)
|
||
break; /* fail */
|
||
if(pass_len == confirm_len && strcmp(buff, confirm_passwd) == 0)
|
||
break; /* finally success! */
|
||
|
||
fprintf(stderr, "Error: Entered passwords do not match, try again...\n");
|
||
}
|
||
|
||
/* wipe confirm password */
|
||
wipe_memory(confirm_passwd, (size_t) buff_len);
|
||
if(pass_len == -1 || pass_len == 0) {
|
||
/* wipe entire password buffer */
|
||
wipe_memory(buff, (size_t) buff_len);
|
||
} else {
|
||
/* wipe the unused portion of the read password too, in case some secrets exist there */
|
||
wipe_memory(buff + (pass_len + 1), (size_t) (buff_len - pass_len - 1));
|
||
}
|
||
|
||
free(confirm_passwd);
|
||
return pass_len;
|
||
}
|
||
|
||
/* returns 0 on success, 1 on cryptography failure, 19 on "libsodium only and CPU does not support AES" error, 2 on other failure */
|
||
int main(int argc, char **argv)
|
||
{
|
||
int optind, decrypt = 0, append = 0, passwd_prompt = 0, exit_code = 2, version = -1;
|
||
char *password = NULL;
|
||
uint32_t N = SCRYPT_N, scrypt_max_mem = SCRYPT_MAX_MEM, buffer_size = DEFAULT_CHUNK_SIZE_MB * 1024 * 1024, scale = 1;
|
||
uint8_t r = SCRYPT_R, p = SCRYPT_P;
|
||
size_t password_len;
|
||
|
||
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 1;
|
||
}
|
||
#endif /* PEGH_LIBSODIUM */
|
||
|
||
for (optind = 1; optind < argc; ++optind) {
|
||
if(strlen(argv[optind]) == 2 && argv[optind][0] == '-') {
|
||
|
||
/* -- means stop parsing options */
|
||
if(argv[optind][1] == '-') {
|
||
++optind;
|
||
break;
|
||
}
|
||
|
||
switch (argv[optind][1]) {
|
||
case 'e':
|
||
decrypt = 0;
|
||
break;
|
||
case 'd':
|
||
decrypt = 1;
|
||
break;
|
||
case 'a':
|
||
append = 1;
|
||
break;
|
||
case 'i':
|
||
in_filename = parse_filename_arg(++optind, argc, argv);
|
||
break;
|
||
case 'o':
|
||
out_filename = parse_filename_arg(++optind, argc, argv);
|
||
break;
|
||
case 'f':
|
||
password = read_file_fully(parse_filename_arg(++optind, argc, argv), &password_len);
|
||
break;
|
||
case 'g':
|
||
passwd_prompt = 1;
|
||
break;
|
||
case 'c':
|
||
buffer_size = parse_int_arg(++optind, argc, argv) * 1024 * 1024;
|
||
break;
|
||
case 'm':
|
||
scrypt_max_mem = parse_int_arg(++optind, argc, argv) * 1024 * 1024;
|
||
break;
|
||
case 'N':
|
||
N = next_highest_power_of_2(parse_int_arg(++optind, argc, argv));
|
||
break;
|
||
case 's':
|
||
scale = next_highest_power_of_2(parse_int_arg(++optind, argc, argv));
|
||
break;
|
||
case 'r':
|
||
r = parse_byte_arg(++optind, argc, argv);
|
||
break;
|
||
case 'p':
|
||
p = parse_byte_arg(++optind, argc, argv);
|
||
break;
|
||
case 'q':
|
||
err = NULL;
|
||
break;
|
||
case 'v':
|
||
if(++optind >= argc) {
|
||
fprintf(stderr, "Error: %s requires an argument\n", argv[optind - 1]);
|
||
return help(2);
|
||
}
|
||
if(strlen(argv[optind]) != 1 || (argv[optind][0] != '0' && argv[optind][0] != '1')) {
|
||
fprintf(stderr, "Error: unsupported file format version %s, we only support version 0 (AES-256-GCM) and 1 (Chacha20-Poly1305)\n", argv[optind]);
|
||
return help(2);
|
||
}
|
||
if(argv[optind][0] == '0') {
|
||
version = 0;
|
||
#ifdef PEGH_LIBSODIUM
|
||
if(1 != check_version(0, NULL)) {
|
||
return help(19);
|
||
}
|
||
#endif
|
||
} else {
|
||
version = 1;
|
||
}
|
||
break;
|
||
case 'V':
|
||
fprintf(stderr, "pegh %s\nformat versions supported: 0 (AES-256-GCM) and 1 (Chacha20-Poly1305)\n", PEGH_VERSION);
|
||
return 0;
|
||
case 'h':
|
||
return help(0);
|
||
default:
|
||
fprintf(stderr, "Error: invalid option %s\n", argv[optind]);
|
||
return help(exit_code);
|
||
}
|
||
} else if (password == NULL) {
|
||
password = argv[optind];
|
||
password_len = strlen(password);
|
||
} else {
|
||
fprintf (stderr, "Error: more than one password provided\n");
|
||
return help(exit_code);
|
||
}
|
||
}
|
||
|
||
if(password == NULL) {
|
||
if(passwd_prompt) {
|
||
password = malloc(MANUAL_ENTRY_PASSWORD_MAX_LEN);
|
||
if(!password) {
|
||
fprintf (stderr, "Error: cannot allocate memory to prompt for password\n");
|
||
return exit_code;
|
||
}
|
||
if(decrypt) {
|
||
/* for decryption, we just read the password once, maybe print a warning, try to decrypt */
|
||
optind = read_passwd_console("Enter decryption password: ", password, MANUAL_ENTRY_PASSWORD_MAX_LEN);
|
||
if(optind == (MANUAL_ENTRY_PASSWORD_MAX_LEN - 1)) {
|
||
/* possibly truncated */
|
||
fprintf (stderr, "Warning: read maximum characters (%d) for password, possibly truncated, attempting decryption anyway...!\n", optind);
|
||
}
|
||
} else {
|
||
optind = read_passwd_console_confirm(password, MANUAL_ENTRY_PASSWORD_MAX_LEN, MINIMUM_PASSWORD_LEN);
|
||
}
|
||
if(optind == -1) {
|
||
fprintf (stderr, "Error: cannot prompt user for password!\n");
|
||
return exit_code;
|
||
}
|
||
password_len = (size_t) optind;
|
||
} else {
|
||
if(argc == optind) {
|
||
fprintf (stderr, "Error: no password provided\n");
|
||
return help(exit_code);
|
||
}
|
||
|
||
if((argc - optind) != 1) {
|
||
fprintf (stderr, "Error: more than one password provided\n");
|
||
return help(exit_code);
|
||
}
|
||
password = argv[optind];
|
||
password_len = strlen(password);
|
||
}
|
||
}
|
||
|
||
if(0 == decrypt && password_len < MINIMUM_PASSWORD_LEN) {
|
||
fprintf (stderr, "Error: Minimum password length is %lu but was only %lu\n", MINIMUM_PASSWORD_LEN, password_len);
|
||
return help(exit_code);
|
||
}
|
||
|
||
/* apply scale */
|
||
N *= scale;
|
||
scrypt_max_mem *= scale;
|
||
|
||
/*
|
||
fprintf (stderr, "decrypt = %d, key = %s, scrypt_max_mem = %d, N = %d, r = %d, p = %d, scale = %d\n",
|
||
decrypt, password, scrypt_max_mem, N, r, p, scale);
|
||
return 0;
|
||
*/
|
||
|
||
if(NULL != in_filename) {
|
||
in = fopen(in_filename, "rb");
|
||
if(!in) {
|
||
fprintf (stderr, "Error: file '%s' cannot be opened for reading\n", in_filename);
|
||
return exit_code;
|
||
}
|
||
}
|
||
#ifdef _WIN32
|
||
else {
|
||
/* windows in/out is text and mangles certain bytes by default... */
|
||
setmode(STDIN, O_BINARY);
|
||
}
|
||
#endif
|
||
|
||
if(NULL != out_filename) {
|
||
out = fopen(out_filename, append ? "ab" : "wb");
|
||
if(!out) {
|
||
fprintf (stderr, "Error: file '%s' cannot be opened for writing\n", out_filename);
|
||
if(NULL != in_filename)
|
||
fclose(in);
|
||
return exit_code;
|
||
}
|
||
}
|
||
#ifdef _WIN32
|
||
else {
|
||
/* windows in/out is text and mangles certain bytes by default... */
|
||
setmode(STDOUT, O_BINARY);
|
||
}
|
||
#endif
|
||
|
||
|
||
if(decrypt)
|
||
exit_code = pegh_decrypt(password, password_len, scrypt_max_mem, buffer_size, in, out, err);
|
||
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
|
||
/* greater than 32gb SIZE_MAX, AES */
|
||
version = 0;
|
||
#endif
|
||
#endif /* PEGH_OPENSSL */
|
||
}
|
||
exit_code = pegh_encrypt(password, password_len, scrypt_max_mem, buffer_size, in, out, err, (uint8_t) version, N, r, p);
|
||
}
|
||
|
||
if(NULL != in_filename) {
|
||
fclose(in);
|
||
}
|
||
if(NULL != out_filename) {
|
||
fclose(out);
|
||
}
|
||
|
||
/* to the OS, 0 means success, the above functions 1 means success */
|
||
return exit_code == 1 ? 0 : (exit_code == 0 ? 1 : exit_code);
|
||
}
|