wolfssl-examples/crypto/ascon/ascon-file-encrypt.c

574 lines
16 KiB
C

/* ascon-file-encrypt.c
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL. (formerly known as CyaSSL)
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#endif
#include <wolfssl/options.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <wolfssl/wolfcrypt/ascon.h>
#include <wolfssl/wolfcrypt/sha256.h>
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/pwdbased.h>
#ifndef HAVE_ASCON
#error "Please build wolfSSL with the --enable-ascon option"
#endif
#define ASCON_AEAD128_RATE 16
#define SALT_SIZE 8
#define AD_SIZE 32
#define FILE_HEADER_SIZE 40
#define BLOCK_SIZE 4096
#define PASSWORD_SIZE 256
#define ERROR (-1)
#define SUCCESS 0
typedef struct wc_AsconCtx {
wc_AsconAEAD128* ascon;
byte* key;
byte* password;
FILE* inFile;
FILE* outFile;
const char* outFileName;
WC_RNG rng;
int rngInit;
byte* plainText;
byte* cipherText;
long inFileLength;
} wc_AsconCtx;
static const byte ad[AD_SIZE] = {
0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B,
0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x01A, 0x1B,
0x1C, 0x1D, 0x1E, 0x1F,
};
void MemFree(wc_AsconCtx* ctx, int error) {
if (ctx != NULL) {
if (ctx->plainText != NULL) {
memset(ctx->plainText, 0, BLOCK_SIZE);
free(ctx->plainText);
}
if (ctx->cipherText != NULL) {
memset(ctx->cipherText, 0, BLOCK_SIZE);
free(ctx->cipherText);
}
if (ctx->outFile != NULL) {
fclose(ctx->outFile);
if (error != 0) {
remove(ctx->outFileName);
}
}
if (ctx->inFile != NULL) {
fclose(ctx->inFile);
}
if (ctx->key != NULL) {
memset(ctx->key, 0, ASCON_AEAD128_KEY_SZ);
free(ctx->key);
}
if (ctx->password != NULL) {
memset(ctx->password, 0, PASSWORD_SIZE);
free(ctx->password);
}
/* Those two functions check validity of pointer before freeing it*/
if (ctx->rngInit && &ctx->rng != NULL) {
wc_FreeRng(&ctx->rng);
}
if (ctx->ascon != NULL) {
wc_AsconAEAD128_Free(ctx->ascon);
}
memset(ctx, 0, sizeof(wc_AsconCtx));
free(ctx);
}
}
/*
* Makes a cryptographically secure key by stretching a user entered password
*/
int GenerateKey(WC_RNG* rng, byte* key, byte* password, byte* salt)
{
if (wc_RNG_GenerateBlock(rng, salt, SALT_SIZE) != SUCCESS)
return ERROR;
/* stretches key */
if (wc_PBKDF2(key, password, strlen((const char*)password), salt, SALT_SIZE, 4096,
ASCON_AEAD128_KEY_SZ, WC_SHA256) != SUCCESS)
return ERROR;
return SUCCESS;
}
/*
* Encrypts a file using Ascon
*/
int AsconEncrypt(wc_AsconCtx* ctx)
{
byte nonce[ASCON_AEAD128_NONCE_SZ] = {0};
byte salt[SALT_SIZE] = {0};
byte tag[ASCON_AEAD128_TAG_SZ] = {0};
int chunk_read = BLOCK_SIZE;
long j;
if (fseek(ctx->inFile, 0, SEEK_END) != SUCCESS) {
printf("fseek failed\n");
return ERROR;
}
ctx->inFileLength = ftell(ctx->inFile);
if (ctx->inFileLength < 0) {
printf("ftell failed\n");
return ERROR;
}
if (fseek(ctx->inFile, 0, SEEK_SET) != SUCCESS) {
printf("fseek failed\n");
return ERROR;
}
if (fseek(ctx->outFile, FILE_HEADER_SIZE, SEEK_SET) != SUCCESS) {
printf("fseek failed\n");
return ERROR;
}
ctx->plainText = malloc(BLOCK_SIZE);
ctx->cipherText = malloc(BLOCK_SIZE);
if (ctx->plainText == NULL || ctx->cipherText == NULL) {
return ERROR;
}
if (wc_InitRng(&ctx->rng) != SUCCESS) {
printf("Failed to initialize random number generator\n");
return ERROR;
}
ctx->rngInit = 1;
/* Generate key from password and salt */
if (GenerateKey(&ctx->rng, ctx->key, ctx->password, salt) != SUCCESS) {
printf("Generate key failed.\n");
return ERROR;
}
/* sets key */
if (wc_AsconAEAD128_SetKey(ctx->ascon, ctx->key) != SUCCESS) {
printf("Set key failed.\n");
return ERROR;
}
/* Generate random nonce for each encryption */
if (wc_RNG_GenerateBlock(&ctx->rng, nonce, ASCON_AEAD128_NONCE_SZ) != SUCCESS) {
printf("Generate nonce failed.\n");
return ERROR;
}
/* sets nonce */
if (wc_AsconAEAD128_SetNonce(ctx->ascon, nonce) != SUCCESS) {
printf("Set nonce failed.\n");
return ERROR;
}
/* sets additional data */
if (wc_AsconAEAD128_SetAD(ctx->ascon, ad, AD_SIZE) != SUCCESS) {
printf("Set AD failed.\n");
return ERROR;
}
/* Loop reading a block at a time */
for (j = 0; j <= ctx->inFileLength; j += BLOCK_SIZE) {
if (chunk_read > ctx->inFileLength - j) {
chunk_read = ctx->inFileLength - j;
}
if (fread(ctx->plainText, 1, chunk_read, ctx->inFile) != chunk_read) {
printf("ERROR: Failed to read the appropriate amount\n");
return ERROR;
}
/* encrypts the message to the output based on input length */
if (wc_AsconAEAD128_EncryptUpdate(ctx->ascon, ctx->cipherText, ctx->plainText, chunk_read) != SUCCESS) {
printf("Encrypt update failed.\n");
return ERROR;
}
/* write ciphertext to output file */
if (fwrite(ctx->cipherText, 1, chunk_read, ctx->outFile) != chunk_read) {
printf("ERROR: Failed to write the appropriate amount\n");
return ERROR;
}
}
/* Finalize encryption and generate tag */
if (wc_AsconAEAD128_EncryptFinal(ctx->ascon, tag) != SUCCESS) {
printf("Encrypt final failed.\n");
return ERROR;
}
/* writes to outFile */
if (fseek(ctx->outFile, 0, SEEK_SET) != SUCCESS) {
printf("fseek failed\n");
return ERROR;
}
if (fwrite(salt, 1, SALT_SIZE, ctx->outFile) != SALT_SIZE) {
printf("ERROR: Failed to write the appropriate amount\n");
return ERROR;
}
if (fwrite(nonce, 1, ASCON_AEAD128_NONCE_SZ, ctx->outFile) != ASCON_AEAD128_NONCE_SZ) {
printf("ERROR: Failed to write the appropriate amount\n");
return ERROR;
}
if (fwrite(tag, 1, ASCON_AEAD128_TAG_SZ, ctx->outFile) != ASCON_AEAD128_TAG_SZ) {
printf("ERROR: Failed to write the appropriate amount\n");
return ERROR;
}
return SUCCESS;
}
/*
* Decrypts a file using Ascon
*/
int AsconDecrypt(wc_AsconCtx* ctx)
{
byte nonce[ASCON_AEAD128_NONCE_SZ] = {0};
byte salt[SALT_SIZE] = {0};
byte tag[ASCON_AEAD128_TAG_SZ] = {0};
if (fseek(ctx->inFile, 0, SEEK_END) != SUCCESS) {
printf("fseek failed\n");
return ERROR;
}
ctx->inFileLength = ftell(ctx->inFile);
if (ctx->inFileLength < 0) {
printf("ftell failed\n");
return ERROR;
}
if (fseek(ctx->inFile, 0, SEEK_SET) != SUCCESS) {
printf("fseek failed\n");
return ERROR;
}
ctx->plainText = malloc(BLOCK_SIZE);
ctx->cipherText = malloc(BLOCK_SIZE);
if (ctx->plainText == NULL || ctx->cipherText == NULL) {
return ERROR;
}
if (wc_InitRng(&ctx->rng) != SUCCESS) {
printf("Failed to initialize random number generator\n");
return ERROR;
}
ctx->rngInit = 1;
if (ctx->inFileLength >= FILE_HEADER_SIZE) {
int chunk_read = BLOCK_SIZE;
int i = 0;
long j;
/* read the file header and extract salt, nonce, and tag */
if (fread(ctx->cipherText, 1, FILE_HEADER_SIZE, ctx->inFile) != FILE_HEADER_SIZE) {
printf("Error while reading input file.\n");
return ERROR;
}
/* finds salt from input message */
for (; i < SALT_SIZE; i++) {
salt[i] = ctx->cipherText[i];
}
/* finds nonce from input message */
for (; i < ASCON_AEAD128_NONCE_SZ + SALT_SIZE; i++) {
nonce[i - SALT_SIZE] = ctx->cipherText[i];
}
/* finds tag from input message */
for (; i < FILE_HEADER_SIZE; i++) {
tag[i - SALT_SIZE - ASCON_AEAD128_NONCE_SZ] = ctx->cipherText[i];
}
/* replicates old key if passwords match */
if (wc_PBKDF2(ctx->key, ctx->password, strlen((const char*)ctx->password), salt, SALT_SIZE, 4096,ASCON_AEAD128_KEY_SZ, WC_SHA256) != SUCCESS) {
printf("Replicating old key failed.\n");
return ERROR;
}
/* sets key */
if (wc_AsconAEAD128_SetKey(ctx->ascon, ctx->key) != SUCCESS) {
printf("Set key failed.\n");
return ERROR;
}
/* sets nonce */
if (wc_AsconAEAD128_SetNonce(ctx->ascon, nonce) != SUCCESS) {
printf("Set nonce failed.\n");
return ERROR;
}
/* sets additional data */
if (wc_AsconAEAD128_SetAD(ctx->ascon, ad, AD_SIZE) != SUCCESS) {
printf("Set AD failed.\n");
return ERROR;
}
/* Start decrypting ciphertext */
ctx->inFileLength -= FILE_HEADER_SIZE;
/* The plaintext written here is unauthenticated until
* DecryptFinal() verifies the tag below. It is written out
* incrementally to keep memory bounded to BLOCK_SIZE; if
* authentication fails, the caller's MemFree() removes the
* output file so the unverified plaintext is never left
* behind on disk. */
for (j = 0; j <= ctx->inFileLength; j += BLOCK_SIZE) {
if (chunk_read > ctx->inFileLength - j) {
chunk_read = ctx->inFileLength - j;
}
if (fread(ctx->cipherText, 1, chunk_read, ctx->inFile) != chunk_read) {
printf("ERROR: Failed to read the appropriate amount\n");
return ERROR;
}
/* decrypts chunk read */
if (wc_AsconAEAD128_DecryptUpdate(ctx->ascon, ctx->plainText, ctx->cipherText, chunk_read) != SUCCESS) {
printf("Decrypt update failed.\n");
return ERROR;
}
if (fwrite(ctx->plainText, 1, chunk_read, ctx->outFile) != (size_t)chunk_read) {
printf("ERROR: Failed to write the appropriate amount\n");
return ERROR;
}
}
/* Finalize decryption and verify tag. */
if (wc_AsconAEAD128_DecryptFinal(ctx->ascon, tag) != SUCCESS) {
printf("Decrypt final failed.\n");
return ERROR;
}
} else {
printf("Invalid length of input file\n");
return ERROR;
}
return SUCCESS;
}
/*
* help message
*/
void help()
{
printf("\n~~~~~~~~~~~~~~~~~~~~|Help|~~~~~~~~~~~~~~~~~~~~~\n\n");
printf("Usage: ./ascon-file-encrypt -d|-e -i <file.in> "
"-o <file.out>\n\n");
printf("Options\n");
printf("-d Decryption\n-e Encryption\n-i Input file\n");
printf("-o Output file\n-h Help\n");
}
/*
* temporarily disables echoing in terminal for secure key input
*/
int NoEcho(char* password)
{
struct termios oflags, nflags;
int ret = SUCCESS;
/* disabling echo */
tcgetattr(fileno(stdin), &oflags);
nflags = oflags;
nflags.c_lflag &= ~ECHO;
nflags.c_lflag |= ECHONL;
if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) {
printf("Error: tcsetattr failed to disable terminal echo\n");
ret = ERROR;
goto restore;
}
printf("Unique Password: ");
if (fgets(password, PASSWORD_SIZE, stdin) == NULL) {
printf("Error: fgets failed to retrieve password input\n");
ret = ERROR;
goto restore;
}
// Remove trailing newline, if present
password[strcspn(password, "\n")] = '\0';
// Check if password is empty
if (password[0] == '\0') {
printf("No password entered\n");
ret = ERROR;
}
/* restore terminal regardless */
restore:
if (tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
printf("Error: tcsetattr failed to enable terminal echo\n");
ret = ERROR;
}
return ret;
}
int main(int argc, char** argv)
{
int ret = ERROR; /* return value */
const char* in;
int option; /* choice of how to run program */
int inCheck = 0;
int outCheck = 0;
char choice = 'n';
wc_AsconCtx* ctx = (wc_AsconCtx*) malloc(sizeof(wc_AsconCtx));
if (ctx == NULL) {
printf("Memory allocation for ctx failed.\n");
return ret;
}
memset(ctx, 0, sizeof(*ctx));
ctx->ascon = NULL;
ctx->key = NULL;
ctx->password = NULL;
ctx->inFile = NULL;
ctx->outFile = NULL;
ctx->plainText = NULL;
ctx->cipherText = NULL;
ctx->outFile = NULL;
while ((option = getopt(argc, argv, "dei:o:")) != -1) {
switch (option) {
case 'd': /* if entered decrypt */
choice = 'd';
break;
case 'e': /* if entered encrypt */
choice = 'e';
break;
case 'i': /* input file */
in = optarg;
inCheck = 1;
ctx->inFile = fopen(in, "rb");
if (ctx->inFile == NULL) {
printf("Error: unable to open input file\n");
goto cleanup;
}
break;
case 'o': /* output file */
ctx->outFileName = optarg;
outCheck = 1;
ctx->outFile = fopen(ctx->outFileName, "wb");
if (ctx->outFile == NULL) {
printf("Error: unable to open output file\n");
goto cleanup;
}
break;
default:
help();
printf("Ending Session\n");
ret = SUCCESS;
goto cleanup;
}
}
if (inCheck == 0 || outCheck == 0) {
printf("Must have both input and output file");
printf(": -i filename -o filename\n");
} else {
if (choice != 'n') {
ctx->key = malloc(ASCON_AEAD128_KEY_SZ);
if (ctx->key == NULL) {
printf("Could not allocate memory for key\n");
goto cleanup;
}
ctx->password = malloc(PASSWORD_SIZE);
if (ctx->password == NULL) {
printf("Could not allocate memory for password\n");
goto cleanup;
}
if (NoEcho((char*)ctx->password) != SUCCESS) {
printf("Entering user password failed\n");
goto cleanup;
}
ctx->ascon = wc_AsconAEAD128_New();
if (ctx->ascon == NULL) {
printf("Error: initiating Ascon object failed\n");
goto cleanup;
}
if (choice == 'e') {
ret = AsconEncrypt(ctx);
if (ret != SUCCESS)
printf("Ascon encrypt failed\n");
else
printf("Success: Ascon encrypt complete\n");
} else {
ret = AsconDecrypt(ctx);
if (ret != SUCCESS)
printf("Ascon decrypt failed\n");
else
printf("Success: Ascon decrypt complete\n");
}
}
else {
printf("Must select either -e or -d for encryption and decryption\n");
}
}
cleanup:
MemFree(ctx, ret);
return ret;
}