6 n__crypto_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

src/n_crypto.c

Vigenere encode decode function. More...

Structures

Name
struct CYPHER_PARAM
structure of a n_vigenere_cypher_thread param

Functions

Name
N_STR * n_vigenere_encode_in_place(N_STR * string, N_STR * key)
encode string in place using vigenere cypher and key
N_STR * n_vigenere_decode_in_place(N_STR * string, N_STR * key)
decode input using vigenere cypher and key
void * n_vigenere_cypher_thread(void * ptr)
encode or decode threaded func using params from ptr
N_STR * n_vigenere_cypher(N_STR * input, N_STR * key, bool encipher, bool in_place)
encode or decode input using vigenere cypher
N_STR * n_get_current_dir_hd_serial()
get the serial of the current running dir hosting disk
N_STR * n_get_cpu_id()
get the CPU id

Attributes

Name
char * __internal_root_key
static internal root key, just to obfuscate text so we do not care if is is public

Defines

Name
_GNU_SOURCE_WAS_NOT_DEFINED
_GNU_SOURCE

Detailed Description

Vigenere encode decode function.

Author: Castagnier Mickael

Version: 1.0

Date: 10/11/2022

Functions Documentation

function n_vigenere_encode_in_place

N_STR * n_vigenere_encode_in_place(
    N_STR * string,
    N_STR * key
)

encode string in place using vigenere cypher and key

Parameters:

  • string source to encode
  • key key to use for the encoding/decoding

Return: A pointer to string or an error

function n_vigenere_decode_in_place

N_STR * n_vigenere_decode_in_place(
    N_STR * string,
    N_STR * key
)

decode input using vigenere cypher and key

Parameters:

  • string source to decode
  • key key to use for the encoding/decoding

Return: a N_STR *decoded

function n_vigenere_cypher_thread

void * n_vigenere_cypher_thread(
    void * ptr
)

encode or decode threaded func using params from ptr

Parameters:

Return: NULL

function n_vigenere_cypher

N_STR * n_vigenere_cypher(
    N_STR * input,
    N_STR * key,
    bool encipher,
    bool in_place
)

encode or decode input using vigenere cypher

Parameters:

  • input source to encode/decode
  • key key to use for the encoding/decoding
  • encipher bool, set to 1 => encode, 0 => decode
  • in_place bool, set to 1 => use input as output string , 0 return a new encoded copy

Return: a N_STR *encoded or *decoded string

function n_get_current_dir_hd_serial

N_STR * n_get_current_dir_hd_serial()

get the serial of the current running dir hosting disk

Return: a N_STR *serial which contain actual running path disk id

function n_get_cpu_id

N_STR * n_get_cpu_id()

get the CPU id

Return: a N_STR *cpuid

Attributes Documentation

variable __internal_root_key

static char * __internal_root_key = "ENOFGLUCUNZJADRDMCZZSACRBKXAGSLTTFCICPALKHMWWGLVUIFYSLQJIHHXSHOZGRABFNBHGVTOMEBTVIPXZJHHEKIYWNVTWQKERROCTXFGMMLYUJSJFWLFCHQQMMUM";

static internal root key, just to obfuscate text so we do not care if is is public

Macros Documentation

define _GNU_SOURCE_WAS_NOT_DEFINED

#define _GNU_SOURCE_WAS_NOT_DEFINED 

define _GNU_SOURCE

#define _GNU_SOURCE 

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#define _GNU_SOURCE_WAS_NOT_DEFINED
#endif

#include "nilorea/n_common.h"
#include "nilorea/n_base64.h"
#include "nilorea/n_crypto.h"
#include "nilorea/n_thread_pool.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef __solaris__
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#endif
#if defined(__linux__) || defined(__windows__)
#include <cpuid.h>
#endif
#ifndef __windows__
#include <sys/sysinfo.h>
#endif
#ifdef _GNU_SOURCE_WAS_NOT_DEFINED
#undef _GNU_SOURCE
#endif

#ifdef __windows__
#include "nilorea/n_windows.h"
#endif

static char* __internal_root_key = "ENOFGLUCUNZJADRDMCZZSACRBKXAGSLTTFCICPALKHMWWGLVUIFYSLQJIHHXSHOZGRABFNBHGVTOMEBTVIPXZJHHEKIYWNVTWQKERROCTXFGMMLYUJSJFWLFCHQQMMUM";

N_STR* n_vigenere_get_rootkey(size_t rootkey_size) {
    if ((rootkey_size % 2) != 0) {
        n_log(LOG_ERR, "key size %zu is not a multiple of 2", rootkey_size);
        return NULL;
    }
    time_t t = time(NULL);
    if (t == -1) {
        n_log(LOG_ERR, "Error: Unable to get the current time");
        return NULL;
    }
    N_STR* output = new_nstr(rootkey_size + 1);
    /* NOTE: rand() seeded with time() is not cryptographically secure.
     * This key is suitable only for obfuscation, not for strong protection. */
    srand((unsigned int)t);
    for (size_t it = 0; it < rootkey_size; it++) {
        output->data[it] = (char)('A' + rand() % 26);
    }
    output->written = rootkey_size;
    return output;
}

N_STR* n_get_current_dir_hd_serial() {
    N_STR* output = NULL;

#ifdef __windows__
    DWORD dwVolSerial;
    BOOL bIsRetrieved;
    bIsRetrieved = GetVolumeInformation(NULL, NULL, 0, &dwVolSerial, NULL, NULL, NULL, 0);

    if (bIsRetrieved) {
        nstrprintf(output, "%X", (unsigned int)dwVolSerial);
    } else {
        n_log(LOG_ERR, "Could not retrieve current directory disk serial id");
        return NULL;
    }
    return output;
#elif __linux__
    char* curdir = getcwd(NULL, 0);

    N_STR* cmd = NULL;
    nstrprintf(cmd, "df -T \"%s\" | awk '/^\\/dev/ {print $1}'", curdir);
    Free(curdir);

    N_STR* cur_disk = NULL;
    int ret = -1;
    n_popen(_nstr(cmd), 1024, (void**)&cur_disk, &ret);
    free_nstr(&cmd);

    nstrprintf(cmd, "udevadm info --query=all --name=\"%s\" | egrep '(ID_SERIAL=|ID_FS_UUID=)' | awk -F'=' '{print $2}'", _nstr(cur_disk));
    n_popen(_nstr(cmd), 1024, (void**)&output, &ret);
    free_nstr(&cmd);

    N_STR* hd_serial = NULL;
    nstrprintf(hd_serial, "%s=>%s", _nstr(output), _nstr(cur_disk));

    free_nstr(&output);
    free_nstr(&cur_disk);

    for (unsigned int it = 0; it < hd_serial->written; it++) {
        if (isspace(hd_serial->data[it])) {
            hd_serial->data[it] = '-';
        }
    }
    return hd_serial;

#else /* SOLARIS */

    char* curdir = getcwd(NULL, 0);
    N_STR* hostid = NULL;
    int ret = -1;
    n_popen("hostid", 1024, (void**)&hostid, &ret);
    /* build output before freeing curdir */
    nstrprintf(output, "%s:%s", _nstr(hostid), _str(curdir));
    free_nstr(&hostid);
    Free(curdir);
    return output;

#endif
}

N_STR* n_get_cpu_id() {
    N_STR* cpu_id = NULL;

    /* x86 / x86_64 */
#if defined(__i386__) || defined(__x86_64__)

    unsigned int eax, ebx, ecx, edx;

    if (__get_cpuid_max(0, NULL) >= 1 &&
        __get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
        unsigned int stepping = eax & 0xF;
        unsigned int model = (eax >> 4) & 0xF;
        unsigned int family = (eax >> 8) & 0xF;
        unsigned int processorType = (eax >> 12) & 0x3;

        nstrprintf(cpu_id, "%u%u%u%u-%u%u%u%u-%u%u%u%u-%u%u",
                   (stepping >> 3) & 1,
                   (stepping >> 2) & 1,
                   (stepping >> 1) & 1,
                   stepping & 1,

                   (model >> 3) & 1,
                   (model >> 2) & 1,
                   (model >> 1) & 1,
                   model & 1,

                   (family >> 3) & 1,
                   (family >> 2) & 1,
                   (family >> 1) & 1,
                   family & 1,

                   (processorType >> 1) & 1,
                   processorType & 1);

        return cpu_id;
    }
#endif

    /* Solaris fallback */
#ifdef __solaris__
    static const char devname[] = "/dev/cpu/self/cpuid";

    struct {
        uint32_t r_eax, r_ebx, r_ecx, r_edx;
    } regs;

    int d;

    if ((d = open(devname, O_RDONLY)) != -1) {
        if (pread(d, &regs, sizeof(regs), 0) == sizeof(regs)) {
            nstrprintf(cpu_id, "%08x", regs.r_eax);
            close(d);
            return cpu_id;
        }
        close(d);
    }
#endif

    /* Linux / Android fallback */
#if defined(__linux__) || defined(__ANDROID__)
    FILE* f = fopen("/proc/cpuinfo", "r");
    if (f) {
        char line[256];

        while (fgets(line, sizeof(line), f)) {
            if (strncmp(line, "model name", 10) == 0 ||
                strncmp(line, "Hardware", 8) == 0) {
                char* colon = strchr(line, ':');
                if (colon) {
                    colon += 2; /* skip ": " */
                    colon[strcspn(colon, "\n")] = 0;
                    nstrprintf(cpu_id, "%s", colon);
                    fclose(f);
                    return cpu_id;
                }
            }
        }

        fclose(f);
    }
#endif

    /* Ultimate fallback */
    n_log(LOG_ERR, "unable to determine CPU id");
    nstrprintf(cpu_id, "unknown");
    return cpu_id;
}

typedef struct CYPHER_PARAM {
    N_STR* input;
    N_STR* dest;
    N_STR* key;
    bool encipher;
    size_t start_pos;
    size_t end_pos;
} CYPHER_PARAM;

void* n_vigenere_cypher_thread(void* ptr) {
    __n_assert(ptr, return NULL);
    CYPHER_PARAM* params = (CYPHER_PARAM*)ptr;
    __n_assert(params, return NULL);

    const N_STR* input = params->input;
    N_STR* dest = params->dest;
    const N_STR* key = params->key;

    __n_assert(input, return NULL);
    __n_assert(input->data, return NULL);
    __n_assert(key, return NULL);
    __n_assert(key->data, return NULL);

    if (key->written == 0) return NULL;

    bool encipher = params->encipher;

    size_t start_pos = params->start_pos;
    size_t end_pos = params->end_pos;

    bool cIsUpper;
    size_t keyIndexMod;
    if (encipher) {
        for (size_t i = start_pos; i < end_pos; i++) {
            if ((cIsUpper = n_isupper(input->data[i])) || (n_islower(input->data[i]))) {
                char offset = cIsUpper ? 'A' : 'a';
                keyIndexMod = i % key->written;
                int k = (cIsUpper ? n_toupper(key->data[keyIndexMod]) : n_tolower(key->data[keyIndexMod])) - offset;
                dest->data[i] = (char)((n_mathmod(((input->data[i] + k) - offset), 26)) + offset);
            }
        }
    } else {
        for (size_t i = start_pos; i < end_pos; i++) {
            if ((cIsUpper = n_isupper(input->data[i])) || (n_islower(input->data[i]))) {
                char offset = cIsUpper ? 'A' : 'a';
                keyIndexMod = i % key->written;
                int k = (cIsUpper ? n_toupper(key->data[keyIndexMod]) : n_tolower(key->data[keyIndexMod])) - offset;
                dest->data[i] = (char)((n_mathmod(((input->data[i] - k) - offset), 26)) + offset);
            }
        }
    }
    Free(params);
    return NULL;
}

N_STR* n_vigenere_cypher(N_STR* input, N_STR* key, bool encipher, bool in_place) {
    __n_assert(input, return NULL);
    __n_assert(input->data, return NULL);
    __n_assert(key, return NULL);
    __n_assert(key->data, return NULL);

    if (input->written == 0) {
        n_log(LOG_ERR, "empty input, written=0");
        return NULL;
    }

    // cppcheck-suppress nullPointerRedundantCheck ; key validated by __n_assert above
    if ((key->written % 2) != 0) {
        n_log(LOG_ERR, "odd number of elements in root key %s, %zu", _nstr(key), key->written);
        return NULL;
    }

    for (size_t i = 0; i < key->written; ++i) {
        if (!isalpha(key->data[i])) {
            n_log(LOG_ERR, "key contain bad char '%c'", key->data[i]);
            return NULL;  // Error
        }
    }

    N_STR* dest = NULL;
    if (in_place) {
        dest = input;
    } else {
        dest = nstrdup(input);
    }

#ifdef __windows__
    SYSTEM_INFO sysinfo;
    GetSystemInfo(&sysinfo);
    size_t nb_procs = (size_t)sysinfo.dwNumberOfProcessors;
#else
    size_t nb_procs = (size_t)get_nprocs();
#endif

    if (nb_procs < 2)
        nb_procs = 2;

    THREAD_POOL* thread_pool = new_thread_pool(nb_procs, 0);
    if (!thread_pool) {
        n_log(LOG_ERR, "Could not create thread pool for vigenere cypher");
        if (!in_place) free_nstr(&dest);
        return NULL;
    }

    size_t bytesteps = input->written / nb_procs;
    if (bytesteps == 0) bytesteps = input->written;

    size_t end_pos = 0;
    for (size_t it = 0; it < input->written; it += bytesteps) {
        end_pos = it + bytesteps;

        if (end_pos >= input->written)
            end_pos = input->written;

        CYPHER_PARAM* params = NULL;
        Malloc(params, CYPHER_PARAM, 1);
        params->input = input;
        params->dest = dest;
        params->key = key;
        params->encipher = encipher;
        params->start_pos = it;
        params->end_pos = end_pos;

        if (add_threaded_process(thread_pool, &n_vigenere_cypher_thread, (void*)(intptr_t)params, DIRECT_PROC) == FALSE) {
            n_log(LOG_ERR, "Error adding n_vigenere_cypher_thread to thread pool");
            Free(params);
        }
        refresh_thread_pool(thread_pool);
        usleep(1);
    }
    if (end_pos < input->written) {
        CYPHER_PARAM* params = NULL;
        Malloc(params, CYPHER_PARAM, 1);
        params->input = input;
        params->dest = dest;
        params->key = key;
        params->encipher = encipher;
        params->start_pos = end_pos;
        params->end_pos = input->written;

        if (add_threaded_process(thread_pool, &n_vigenere_cypher_thread, (void*)(intptr_t)params, DIRECT_PROC) == FALSE) {
            n_log(LOG_ERR, "Error adding n_vigenere_cypher_thread to thread pool");
            Free(params);
        }
        refresh_thread_pool(thread_pool);
        usleep(1);
    }
    wait_for_threaded_pool(thread_pool);
    destroy_threaded_pool(&thread_pool, 100000);

    /*
       uint32_t nonAlphaCharCount = 0;
       for( uint32_t i = 0 ; i < input -> written ; i++ )
       {
       char ch = input -> data[ i ];
       if( isalpha( input -> data[ i ] ) )
       {
       bool cIsUpper = isupper( input -> data[ i ] );
       char offset = cIsUpper ? 'A' : 'a';
       int keyIndex = ( i - nonAlphaCharCount ) % key -> written ;
       int k = ( cIsUpper ? toupper( key -> data[ keyIndex % key -> written ] ) : tolower( key -> data[ keyIndex % key -> written ] ) ) - offset ;
       k = encipher ? k : -k;
       ch = (char)( ( n_mathmod( ( ( input -> data[ i ] + k ) - offset ) , 26 ) ) + offset );
       }
       else
       {
       ++nonAlphaCharCount;
       }
       dest -> data[ i ] = ch;
       }
       */

    return dest;
}

N_STR* n_vigenere_encode(N_STR* string, N_STR* key) {
    return n_vigenere_cypher(string, key, 1, 0);
}

N_STR* n_vigenere_decode(N_STR* string, N_STR* key) {
    return n_vigenere_cypher(string, key, 0, 0);
}

N_STR* n_vigenere_encode_in_place(N_STR* string, N_STR* key) {
    return n_vigenere_cypher(string, key, 1, 1);
}

N_STR* n_vigenere_decode_in_place(N_STR* string, N_STR* key) {
    return n_vigenere_cypher(string, key, 0, 1);
}

N_STR* n_vigenere_quick_encode(N_STR* decoded_data) {
    N_STR* __internal_root_key_nstr = char_to_nstr(__internal_root_key);

    N_STR* base64_data = n_base64_encode(decoded_data);
    N_STR* encoded_data = n_vigenere_encode_in_place(base64_data, __internal_root_key_nstr);
    free_nstr(&__internal_root_key_nstr);

    return encoded_data;
}

N_STR* n_vigenere_quick_decode(N_STR* encoded_data) {
    N_STR* __internal_root_key_nstr = char_to_nstr(__internal_root_key);

    N_STR* decoded_data = n_vigenere_decode(encoded_data, __internal_root_key_nstr);
    N_STR* base64_data = n_base64_decode(decoded_data);

    free_nstr(&__internal_root_key_nstr);
    free_nstr(&decoded_data);

    return base64_data;
}

N_STR* n_vigenere_get_question(size_t question_size) {
    size_t it = 0;

    if ((question_size % 2) != 0) {
        n_log(LOG_ERR, "odd number of elements for question size, %zu", question_size);
        return NULL;
    }

    N_STR* output = NULL;
    output = n_get_current_dir_hd_serial();

    N_STR* cpu_id = n_get_cpu_id();
    nstrprintf_cat(output, "@%s", _nstr(cpu_id));
    free_nstr(&cpu_id);

    if (output) {
        N_STR* tmpoutput = n_base64_encode(output);
        free_nstr(&output);
        output = new_nstr(question_size + 1);
        for (it = 0; it < question_size; it++) {
            int val = tmpoutput->data[it % tmpoutput->written];
            while (val < 'A') val += 26;
            while (val > 'Z') val -= 26;
            output->data[it] = (char)val;
        }
        output->written = question_size;
        free_nstr(&tmpoutput);
    } else {
        // generate random
        output = new_nstr(question_size + 1);
        for (it = 0; it < question_size; it++) {
            int val = rand() % 256;
            while (val < 'A') val += 26;
            while (val > 'Z') val -= 26;
            output->data[it] = (char)val;
        }
    }

    N_STR* encoded_data = n_vigenere_quick_encode(output);
    free_nstr(&output);

    if (!encoded_data) {
        return NULL;
    }

    it = 0;
    while (it < encoded_data->written) {
        if (encoded_data->data[it] == '=') {
            encoded_data->data[it] = '\0';
        }
        it++;
    }
    return encoded_data;
}

N_STR* n_vigenere_get_answer(N_STR* root_key, N_STR* question) {
    __n_assert(root_key, return NULL);
    __n_assert(question, return NULL);

    N_STR* decoded_question = n_vigenere_quick_decode(question);

    if (!decoded_question) {
        return NULL;
    }

    if (root_key->written < decoded_question->written) {
        n_log(LOG_ERR, "root key of size %zu is lower than question key of size %zu", root_key->written, decoded_question->written);
        free_nstr(&decoded_question);
        return NULL;
    }
    if ((root_key->written % decoded_question->written) != 0) {
        n_log(LOG_ERR, "question key of size %zu is not a multiple of root key of size %zu", decoded_question->written, root_key->written);
        free_nstr(&decoded_question);
        return NULL;
    }

    N_STR* answer = new_nstr(root_key->length + 1);
    for (uint32_t it = 0; it < root_key->written; it++) {
        int32_t val = (root_key->data[it % root_key->written] - 'A') + (decoded_question->data[it % decoded_question->written] - 'A');
        val = val % 26;
        val = val + 'A';
        answer->data[it] = (char)val;
    }
    free_nstr(&decoded_question);
    answer->written = root_key->written;
    return answer;
}

int n_vigenere_encode_file(N_STR* in, N_STR* out, N_STR* key) {
    __n_assert(in, return FALSE);
    __n_assert(out, return FALSE);
    __n_assert(key, return FALSE);

    if ((key->written % 2) != 0) {
        n_log(LOG_ERR, "odd number of elements in key %s, %zu", _nstr(key), key->written);
        return FALSE;
    }

    N_STR* file = file_to_nstr(_nstr(in));
    if (!file) {
        n_log(LOG_ERR, "error loading %s", _nstr(in));
        return FALSE;
    }

    N_STR* base64_data = n_base64_encode(file);
    free_nstr(&file);

    if (!base64_data) {
        n_log(LOG_ERR, "Could not base64 decode data from file %s", _nstr(in));
        return FALSE;
    }

    N_STR* encoded_data = n_vigenere_encode_in_place(base64_data, key);
    if (!encoded_data) {
        n_log(LOG_ERR, "Could not decode data from file %s", _nstr(in));
        free_nstr(&base64_data);
        return FALSE;
    }

    int ret = nstr_to_file(encoded_data, _nstr(out));
    free_nstr(&encoded_data);

    return ret;
}

int n_vigenere_decode_file(N_STR* in, N_STR* out, N_STR* key) {
    __n_assert(in, return FALSE);
    __n_assert(out, return FALSE);
    __n_assert(key, return FALSE);

    if ((key->written % 2) != 0) {
        n_log(LOG_ERR, "odd number of elements in key %s , %zu", _nstr(key), key->written);
        return FALSE;
    }

    N_STR* file = file_to_nstr(_nstr(in));
    if (!file) {
        n_log(LOG_ERR, "error loading %s", _nstr(in));
        return FALSE;
    }

    N_STR* decoded_data = n_vigenere_decode_in_place(file, key);
    if (!decoded_data) {
        n_log(LOG_ERR, "Could not decode data from file %s", _nstr(in));
        free_nstr(&file);
        return FALSE;
    }

    N_STR* base64_data = n_base64_decode(decoded_data);
    free_nstr(&decoded_data);
    if (!base64_data) {
        n_log(LOG_ERR, "Could not base64 decode data from file %s", _nstr(in));
        return FALSE;
    }

    int ret = nstr_to_file(base64_data, _nstr(out));
    free_nstr(&base64_data);

    return ret;
}

N_STR* n_vigenere_encode_qa(N_STR* input_data, N_STR* question, N_STR* answer) {
    __n_assert(input_data, return NULL);
    __n_assert(question, return NULL);
    __n_assert(answer, return NULL);

    N_STR* b64_encoded = n_base64_encode(input_data);
    if (!b64_encoded) {
        n_log(LOG_ERR, "could not base64 decode: input_data %p", input_data);
        return NULL;
    }

    N_STR* encoded_with_answer_data = n_vigenere_encode_in_place(b64_encoded, answer);
    if (!encoded_with_answer_data) {
        n_log(LOG_ERR, "could not encode: input_data %p", input_data);
        free_nstr(&b64_encoded);
        return NULL;
    }

    N_STR* decoded_question = n_vigenere_quick_decode(question);
    if (!decoded_question) {
        n_log(LOG_ERR, "could not decode question");
        /* encoded_with_answer_data is b64_encoded (in-place), already modified */
        free_nstr(&encoded_with_answer_data);
        return NULL;
    }
    N_STR* encoded_data = n_vigenere_decode_in_place(encoded_with_answer_data, decoded_question);
    free_nstr(&decoded_question);
    if (!encoded_data) {
        n_log(LOG_ERR, "could not decode: input_data %p", input_data);
        return NULL;
    }

    return encoded_data;
}

N_STR* n_vigenere_decode_qa(N_STR* input_data, N_STR* question, N_STR* answer) {
    __n_assert(input_data, return NULL);
    __n_assert(input_data->data, return NULL);
    __n_assert(question, return NULL);
    __n_assert(question->data, return NULL);
    __n_assert(answer, return NULL);
    __n_assert(answer->data, return NULL);

    N_STR* decoded_question = n_vigenere_quick_decode(question);

    N_STR* encoded_data = n_vigenere_encode(input_data, decoded_question);
    free_nstr(&decoded_question);
    if (!encoded_data) {
        n_log(LOG_ERR, "could not encode input_data %p", input_data);
        return NULL;
    }
    n_log(LOG_DEBUG, "encoded_data: %zu/%zu", encoded_data->written, encoded_data->length);

    N_STR* encoded_with_answer_data = n_vigenere_decode_in_place(encoded_data, answer);
    if (!encoded_with_answer_data) {
        n_log(LOG_ERR, "could not decode: input_data %p", input_data);
        return NULL;
    }
    n_log(LOG_DEBUG, "encoded_with_answer_data: %zu/%zu", encoded_with_answer_data->written, encoded_with_answer_data->length);

    N_STR* b64_decoded = n_base64_decode(encoded_with_answer_data);
    free_nstr(&encoded_with_answer_data);
    if (!b64_decoded) {
        n_log(LOG_ERR, "could not base64 decode: input_data %p", input_data);
        return NULL;
    }
    n_log(LOG_DEBUG, "b64_decoded: %zu/%zu", b64_decoded->written, b64_decoded->length);
    return b64_decoded;
}

int n_vigenere_decode_file_qa(N_STR* in, N_STR* out, N_STR* question, N_STR* answer) {
    __n_assert(in, return FALSE);
    __n_assert(out, return FALSE);
    __n_assert(question, return FALSE);
    __n_assert(answer, return FALSE);

    N_STR* input_data = file_to_nstr(_nstr(in));
    if (!input_data) {
        n_log(LOG_ERR, "could not load input file %s", _nstr(in));
        return FALSE;
    }

    N_STR* decoded = n_vigenere_decode_qa(input_data, question, answer);
    free_nstr(&input_data);

    int ret = nstr_to_file(decoded, _nstr(out));
    free_nstr(&decoded);

    return ret;
}

int n_vigenere_encode_file_qa(N_STR* in, N_STR* out, N_STR* question, N_STR* answer) {
    __n_assert(in, return FALSE);
    __n_assert(out, return FALSE);
    __n_assert(question, return FALSE);
    __n_assert(answer, return FALSE);

    N_STR* input_data = file_to_nstr(_nstr(in));
    if (!input_data) {
        n_log(LOG_ERR, "could not load input file %s", _nstr(in));
        return FALSE;
    }

    N_STR* encoded = n_vigenere_encode_qa(input_data, question, answer);
    free_nstr(&input_data);

    int ret = nstr_to_file(encoded, _nstr(out));
    free_nstr(&encoded);

    return ret;
}