Add support for encrypting/decrypting token secrets

Introduce a secrets library which implements the necessary crypto using
libsodium. This change provides the basic building blocks for resolving
issue #6.
master
Johan Ouwerkerk 2020-02-10 20:04:29 +01:00
parent 3a048f221f
commit a9ed1507b2
10 changed files with 628 additions and 0 deletions

View File

@ -12,6 +12,7 @@ project(keysmith)
set(KF5_MIN_VERSION "5.62.0")
set(QT_MIN_VERSION "5.12.0")
set(SODIUM_MIN_VERSION "1.0.16")
################# Disallow in-source build #################
@ -41,6 +42,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
################# Find dependencies #################
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Gui Svg QuickControls2)
find_package(sodium ${SODIUM_MIN_VERSION} REQUIRED)
find_package(KF5Kirigami2 ${KF5_MIN_VERSION} REQUIRED)
find_package(KF5I18n ${KF5_MIN_VERSION} REQUIRED)

View File

@ -8,6 +8,7 @@ include_directories(BEFORE ../src)
add_subdirectory(base32)
add_subdirectory(hmac)
add_subdirectory(oath)
add_subdirectory(secrets)
add_subdirectory(account)
add_subdirectory(model)
add_subdirectory(validators)

View File

@ -0,0 +1,16 @@
#
# SPDX-License-Identifier: BSD-2-Clause
# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
#
set(Test_DEP_LIBS Qt5::Core Qt5::Test secrets_lib)
set(secrets_lib_test_SRCS
encrypt-decrypt-rt.cpp
key-derivation.cpp
)
ecm_add_tests(
${secrets_lib_test_SRCS}
LINK_LIBRARIES ${Test_DEP_LIBS}
NAME_PREFIX secrets-
)

View File

@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "secrets/secrets.h"
#include <QTest>
#include <QtDebug>
#include <string.h>
class EncryptionDecryptionRoundTripTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
void testEncryptionDecryptionRoundTrip(void);
void testDecryptionOfCorruptInputs(void);
};
void EncryptionDecryptionRoundTripTest::testEncryptionDecryptionRoundTrip(void)
{
QScopedPointer<secrets::SecureMemory> passwd(secrets::SecureMemory::allocate(13ULL));
QVERIFY2(passwd, "password memory should be allocated");
memcpy(passwd->data(), "Hello, world!", passwd->size());
std::optional<secrets::KeyDerivationParameters> defaults = secrets::KeyDerivationParameters::create();
QVERIFY2(defaults, "defaults should yield a valid key parameters object");
QScopedPointer<secrets::SecureMasterKey> masterKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults));
QVERIFY2(masterKey, "key derivation should succeed");
QScopedPointer<secrets::SecureMemory> payload(secrets::SecureMemory::allocate(42ULL));
QVERIFY2(payload, "allocating the secure memory input buffer should succeed");
memset(payload->data(), 'B', 42ULL);
std::optional<secrets::EncryptedSecret> encrypted = masterKey->encrypt(payload.data());
QVERIFY2(encrypted, "encryption of the payload should succeed");
QScopedPointer<secrets::SecureMemory> decrypted(masterKey->decrypt(*encrypted));
QVERIFY2(decrypted, "decryption should succeed");
QCOMPARE(decrypted->size(), 42ULL);
QByteArray copyOfDecrypted;
copyOfDecrypted.append(reinterpret_cast<const char *>(decrypted->constData()), 42ULL);
QByteArray expected(42, 'B');
QCOMPARE(copyOfDecrypted, expected);
}
void EncryptionDecryptionRoundTripTest::testDecryptionOfCorruptInputs(void)
{
QScopedPointer<secrets::SecureMemory> passwd(secrets::SecureMemory::allocate(13ULL));
QVERIFY2(passwd, "password memory should be allocated");
memcpy(passwd->data(), "Hello, world!", passwd->size());
std::optional<secrets::KeyDerivationParameters> defaults = secrets::KeyDerivationParameters::create();
QVERIFY2(defaults, "defaults should yield a valid key parameters object");
QScopedPointer<secrets::SecureMasterKey> masterKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults));
QVERIFY2(masterKey, "key derivation should succeed");
QScopedPointer<secrets::SecureMemory> payload(secrets::SecureMemory::allocate(42ULL));
QVERIFY2(payload, "allocating the secure memory input buffer should succeed");
memset(payload->data(), 'B', 42ULL);
std::optional<secrets::EncryptedSecret> encrypted = masterKey->encrypt(payload.data());
QVERIFY2(encrypted, "encryption of the payload should succeed");
QByteArray brokenTag(encrypted->cryptText());
brokenTag[0] = brokenTag[0] ^ ((char) 0xFF);
std::optional<secrets::EncryptedSecret> fakedTag = secrets::EncryptedSecret::from(brokenTag, encrypted->nonce());
QVERIFY2(fakedTag, "should be able to construct the 'fake' encrypted input (tag)");
QScopedPointer<secrets::SecureMemory> decryptedUsingFakeTag(masterKey->decrypt(*fakedTag));
QVERIFY2(!decryptedUsingFakeTag, "decryption should fail when the authentication tag has been tampered with");
QByteArray brokenPayload(encrypted->cryptText());
brokenPayload[brokenPayload.size() - 1] = brokenPayload[brokenPayload.size() - 1] ^ ((char) 0xFF);
std::optional<secrets::EncryptedSecret> fakedPayload = secrets::EncryptedSecret::from(brokenPayload, encrypted->nonce());
QVERIFY2(fakedPayload, "should be able to construct the 'fake' encrypted input (payload)");
QScopedPointer<secrets::SecureMemory> decryptedUsingFakePayload(masterKey->decrypt(*fakedPayload));
QVERIFY2(!decryptedUsingFakePayload, "decryption should fail when the payload has been tampered with");
QByteArray brokenNonce(encrypted->nonce());
brokenNonce[0] = brokenNonce[0] ^ ((char) 0xFF);
std::optional<secrets::EncryptedSecret> fakedNonce = secrets::EncryptedSecret::from(encrypted->cryptText(), brokenNonce);
QVERIFY2(fakedNonce, "should be able to construct the 'fake' encrypted input (nonce)");
QScopedPointer<secrets::SecureMemory> decryptedUsingFakeNonce(masterKey->decrypt(*fakedNonce));
QVERIFY2(!decryptedUsingFakeNonce, "decryption should fail when the nonce has been tampered with");
}
QTEST_APPLESS_MAIN(EncryptionDecryptionRoundTripTest)
#include "encrypt-decrypt-rt.moc"

View File

@ -0,0 +1,60 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "secrets/secrets.h"
#include <QTest>
#include <QtDebug>
#include <string.h>
class KeyDerivationTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
void testRecovery(void);
};
void KeyDerivationTest::testRecovery(void)
{
QScopedPointer<secrets::SecureMemory> passwd(secrets::SecureMemory::allocate(13ULL));
QVERIFY2(passwd, "password memory should be allocated");
memcpy(passwd->data(), "Hello, world!", passwd->size());
std::optional<secrets::KeyDerivationParameters> defaults = secrets::KeyDerivationParameters::create();
QVERIFY2(defaults, "defaults should yield a valid key parameters object");
const secrets::SecureRandom fakeRandom([](void *buf, size_t amount) -> bool
{
memset(buf, 'A', amount);
return true;
});
QScopedPointer<secrets::SecureMasterKey> origMasterKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults, fakeRandom));
QVERIFY2(origMasterKey, "key derivation should succeed");
QByteArray expectedSalt(crypto_pwhash_SALTBYTES, 'A');
QCOMPARE(origMasterKey->salt(), expectedSalt);
QScopedPointer<secrets::SecureMasterKey> copyKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults, expectedSalt, fakeRandom));
QVERIFY2(copyKey, "recovering/re-deriving a copy of the master key should succeed");
QScopedPointer<secrets::SecureMemory> payload(secrets::SecureMemory::allocate(42ULL));
QVERIFY2(payload, "allocating the secure memory input buffer should succeed");
memset(payload->data(), 'B', 42ULL);
std::optional<secrets::EncryptedSecret> fromOrigKey = origMasterKey->encrypt(payload.data());
QVERIFY2(fromOrigKey, "encryption of the payload should succeed with the original master key");
std::optional<secrets::EncryptedSecret> fromCopyKey = copyKey->encrypt(payload.data());
QVERIFY2(fromCopyKey, "encryption of the payload should also succeed with the recovered copy of the master key");
QCOMPARE(fromOrigKey->cryptText(), fromCopyKey->cryptText());
QCOMPARE(fromOrigKey->nonce(), fromCopyKey->nonce());
}
QTEST_APPLESS_MAIN(KeyDerivationTest)
#include "key-derivation.moc"

View File

@ -9,6 +9,7 @@
add_subdirectory(base32)
add_subdirectory(hmac)
add_subdirectory(oath)
add_subdirectory(secrets)
add_subdirectory(account)
add_subdirectory(model)
add_subdirectory(validators)

View File

@ -0,0 +1,9 @@
#
# SPDX-License-Identifier: BSD-2-Clause
# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
#
set(secret_SRCS secrets.cpp)
add_library(secrets_lib STATIC ${secret_SRCS})
target_link_libraries(secrets_lib Qt5::Core sodium base32_lib)

325
src/secrets/secrets.cpp Normal file
View File

@ -0,0 +1,325 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#include "secrets.h"
#include "../base32/base32.h"
#include "../logging_p.h"
#include <QCryptographicHash>
#include <limits>
KEYSMITH_LOGGER(logger, ".secrets")
namespace secrets
{
std::optional<EncryptedSecret> EncryptedSecret::from(const QByteArray& cryptText, const QByteArray& nonce)
{
if (cryptText.size() < 0 || ((uint) cryptText.size()) <= crypto_secretbox_MACBYTES) {
qCDebug(logger) << "Invalid ciphertext: too short, expected at least:" << crypto_secretbox_MACBYTES << "bytes but got:" << cryptText.size();
return std::nullopt;
}
if (nonce.size() != crypto_secretbox_NONCEBYTES) {
qCDebug(logger) << "Invalid nonce: expected exactly:" << crypto_secretbox_NONCEBYTES << "but got:" << nonce.size();
return std::nullopt;
}
return std::optional<EncryptedSecret>({cryptText, nonce});
}
unsigned long long EncryptedSecret::messageLength(void) const
{
return m_taggedCryptText.size() - crypto_secretbox_MACBYTES;
}
const QByteArray& EncryptedSecret::cryptText(void) const
{
return m_taggedCryptText;
}
const QByteArray& EncryptedSecret::nonce(void) const
{
return m_nonce;
}
EncryptedSecret::EncryptedSecret(const QByteArray &taggedCryptText, const QByteArray &nonce) : m_taggedCryptText(taggedCryptText), m_nonce(nonce)
{
}
std::optional<KeyDerivationParameters> KeyDerivationParameters::create(unsigned long long keyLength, int algorithm, size_t memoryCost, unsigned long long cpuCost)
{
switch (algorithm)
{
case crypto_pwhash_ALG_ARGON2I13:
case crypto_pwhash_ALG_ARGON2ID13:
break;
default:
qCDebug(logger) << "Unable to construct key derivation parameters: unkown algorithm:" << algorithm;
return std::nullopt;
}
if (keyLength < crypto_pwhash_BYTES_MIN || keyLength > crypto_pwhash_BYTES_MAX ||
cpuCost < crypto_pwhash_OPSLIMIT_MIN || cpuCost > crypto_pwhash_OPSLIMIT_MAX ||
memoryCost < crypto_pwhash_MEMLIMIT_MIN || memoryCost > crypto_pwhash_MEMLIMIT_MAX) {
return std::nullopt;
}
return std::optional<KeyDerivationParameters>({algorithm, keyLength, memoryCost, cpuCost});
}
int KeyDerivationParameters::algorithm(void) const
{
return m_algorithm;
}
unsigned long long KeyDerivationParameters::keyLength(void) const
{
return m_keyLength;
}
size_t KeyDerivationParameters::memoryCost(void) const
{
return m_memoryCost;
}
unsigned long long KeyDerivationParameters::cpuCost(void) const
{
return m_cpuCost;
}
KeyDerivationParameters::KeyDerivationParameters(int algorithm, unsigned long long keyLength, size_t memoryCost, unsigned long long cpuCost) : m_algorithm(algorithm), m_keyLength(keyLength), m_memoryCost(memoryCost), m_cpuCost(cpuCost)
{
}
SecureMemory * SecureMemory::allocate(size_t size)
{
if (sodium_init() < 0) {
qCDebug(logger) << "Unable to allocate secure memory region: libsodium failed to initialise";
return nullptr;
}
if (size == 0ULL) {
qCDebug(logger) << "Rejecting attempt to allocate empty secure memory region";
return nullptr;
}
void * memory = sodium_malloc(size);
if (memory) {
return new SecureMemory(memory, size);
}
qCDebug(logger) << "Failed to allocate secure memory region";
return nullptr;
}
size_t SecureMemory::size(void) const
{
return m_size;
}
const unsigned char * SecureMemory::constData(void) const
{
return m_secureMemory;
}
unsigned char * SecureMemory::data(void)
{
return m_secureMemory;
}
SecureMemory::~SecureMemory()
{
sodium_free(m_secureMemory);
}
SecureMemory::SecureMemory(void *memory, size_t size) : m_secureMemory(reinterpret_cast<unsigned char*>(memory)), m_size(size)
{
}
bool SecureMasterKey::validate(const SecureMemory *secret)
{
static const int max = std::numeric_limits<int>::max() - crypto_secretbox_MACBYTES;
return max > 0 && secret->size() > 0ULL && secret->size() <= max;
}
bool SecureMasterKey::validate(const KeyDerivationParameters &params)
{
return params.keyLength() == crypto_secretbox_KEYBYTES;
}
bool SecureMasterKey::validate(const QByteArray &salt)
{
return salt.size() == crypto_pwhash_SALTBYTES;
}
SecureMasterKey * SecureMasterKey::derive(const SecureMemory *password, const KeyDerivationParameters &params, const SecureRandom &random)
{
QByteArray salt;
if (!password) {
qCDebug(logger) << "Unable to derive a key: no password given";
return nullptr;
}
if (sodium_init() < 0) {
qCDebug(logger) << "Unable to derive a key: libsodium failed to initialise";
return nullptr;
}
salt.reserve(crypto_pwhash_SALTBYTES);
salt.resize(crypto_pwhash_SALTBYTES);
if (!random(salt.data(), crypto_pwhash_SALTBYTES)) {
qCDebug(logger) << "Unable to derive a key: failed to generate the random nonce";
return nullptr;
}
return derive(password, params, salt, random);
}
SecureMasterKey * SecureMasterKey::derive(const SecureMemory *password, const KeyDerivationParameters &params, const QByteArray &salt, const SecureRandom &random)
{
if (!password) {
qCDebug(logger) << "Unable to derive a key: no password given";
return nullptr;
}
if (sodium_init() < 0) {
qCDebug(logger) << "Unable to derive a key: libsodium failed to initialise";
return nullptr;
}
if (!validate(params)) {
qCDebug(logger) << "Unable to derive a key: invalid key derivation parameters";
return nullptr;
}
if (!validate(salt)) {
qCDebug(logger) << "Unable to derive a key: invalid salt";
return nullptr;
}
void * memory = sodium_malloc((size_t) params.keyLength());
if (!memory) {
qCDebug(logger) << "Unable to derive a key: failed to allocate secure memory region";
return nullptr;
}
unsigned char * derived = reinterpret_cast<unsigned char*>(memory);
const unsigned char *saltData = reinterpret_cast<const unsigned char*>(salt.constData());
const char *passwordData = reinterpret_cast<const char*>(password->constData());
if (crypto_pwhash(derived, params.keyLength(), passwordData, password->size(), saltData, params.cpuCost(), params.memoryCost(), params.algorithm()) == 0) {
return new SecureMasterKey(derived, params, salt, random);
}
qCDebug(logger) << "Failed to derive a key: password hashing failed";
sodium_free(memory);
return nullptr;
}
const KeyDerivationParameters SecureMasterKey::params(void) const
{
return m_params;
}
const QByteArray SecureMasterKey::salt(void) const
{
return m_salt;
}
std::optional<EncryptedSecret> SecureMasterKey::encrypt(const SecureMemory *secret) const
{
QByteArray cryptText;
QByteArray nonce;
if (!validate(secret)) {
qCDebug(logger) << "Unable to encrypt secret: invalid input";
return std::nullopt;
}
cryptText.reserve(((int) secret->size()) + crypto_secretbox_MACBYTES);
cryptText.resize(((int) secret->size()) + crypto_secretbox_MACBYTES);
nonce.reserve(crypto_secretbox_NONCEBYTES);
nonce.resize(crypto_secretbox_NONCEBYTES);
if (!m_secureRandom(nonce.data(), crypto_secretbox_NONCEBYTES)) {
qCDebug(logger) << "Unable to encrypt secret: failed to generate the random nonce";
return std::nullopt;
}
unsigned char *encrypted = reinterpret_cast<unsigned char*>(cryptText.data());
const unsigned char *nonceData = reinterpret_cast<const unsigned char*>(nonce.constData());
if (crypto_secretbox_easy(encrypted, secret->constData(), (unsigned long long) secret->size(), nonceData, m_secureKeyMemory) == 0) {
return EncryptedSecret::from(cryptText, nonce);
}
qCDebug(logger) << "Failed to encrypt secret";
return std::nullopt;
}
SecureMemory * SecureMasterKey::decrypt(const EncryptedSecret &secret) const
{
SecureMemory *result = SecureMemory::allocate(secret.messageLength());
if (!result) {
qCDebug(logger) << "Unable to decrypt secret: failed to allocate secure memory region";
return nullptr;
}
unsigned char *decrypted = reinterpret_cast<unsigned char*>(result->data());
const unsigned char *cryptText = reinterpret_cast<const unsigned char*>(secret.cryptText().constData());
const unsigned char *nonce = reinterpret_cast<const unsigned char*>(secret.nonce().constData());
if (crypto_secretbox_open_easy(decrypted, cryptText, (unsigned long long) secret.cryptText().size(), nonce, m_secureKeyMemory) == 0) {
return result;
}
qCDebug(logger) << "Failed to decrypt secret";
delete result;
return nullptr;
}
SecureMasterKey::~SecureMasterKey()
{
sodium_free(m_secureKeyMemory);
}
SecureMasterKey::SecureMasterKey(unsigned char *keyMemory, const KeyDerivationParameters &params, const QByteArray &salt, const SecureRandom &random) :
m_secureKeyMemory(keyMemory), m_params(params), m_salt(salt), m_secureRandom(random)
{
}
bool defaultSecureRandom(void *data, size_t size)
{
if (sodium_init() < 0) {
qCDebug(logger) << "Unable to fill buffer with random bytes: libsodium failed to initialise";
return false;
}
randombytes_buf(data, size);
return true;
}
SecureMemory * decodeBase32(const QString &encoded, int from, int until)
{
std::optional<size_t> size = base32::validate(encoded, from, until);
if (!size) {
return nullptr;
}
SecureMemory *result = SecureMemory::allocate(*size);
if (!result) {
qCDebug(logger) << "Unable to decoded base32 secrets: failed to allocate secure memory";
return nullptr;
}
std::optional<size_t> decoded = base32::decode(encoded, reinterpret_cast<char*>(result->data()), result->size(), from, until);
if (decoded && *decoded == *size) {
return result;
}
Q_ASSERT_X(decoded, Q_FUNC_INFO, "invalid base32 should have been caught by prior validation");
Q_ASSERT_X(*decoded == *size, Q_FUNC_INFO, "the entire base32 input should have been decoded");
delete result;
return nullptr;
}
}

100
src/secrets/secrets.h Normal file
View File

@ -0,0 +1,100 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#ifndef SECRETS_H
#define SECRETS_H
#include "sodium_cpp.h"
#include "../hmac/hmac.h"
#include <QByteArray>
#include <QCryptographicHash>
#include <QString>
#include <functional>
#include <optional>
namespace secrets
{
class EncryptedSecret
{
public:
static std::optional<EncryptedSecret> from(const QByteArray& cryptText, const QByteArray& nonce);
const QByteArray& cryptText(void) const;
const QByteArray& nonce(void) const;
unsigned long long messageLength(void) const;
private:
EncryptedSecret(const QByteArray &taggedCryptText, const QByteArray &nonce);
private:
const QByteArray m_taggedCryptText;
const QByteArray m_nonce;
};
class KeyDerivationParameters
{
public:
static std::optional<KeyDerivationParameters> create(unsigned long long keyLength = crypto_secretbox_KEYBYTES,
int algorithm = crypto_pwhash_ALG_DEFAULT,
size_t memoryCost = crypto_pwhash_MEMLIMIT_MODERATE,
unsigned long long cpuCost = crypto_pwhash_OPSLIMIT_MODERATE);
int algorithm(void) const;
unsigned long long keyLength(void) const;
size_t memoryCost(void) const;
unsigned long long cpuCost(void) const;
private:
KeyDerivationParameters(int algorithm, unsigned long long keyLength, size_t memoryCost, unsigned long long cpuCost);
private:
const int m_algorithm;
const unsigned long long m_keyLength;
const size_t m_memoryCost;
const unsigned long long m_cpuCost;
};
class SecureMemory
{
public:
static SecureMemory * allocate(size_t size);
virtual ~SecureMemory();
size_t size(void) const;
const unsigned char * constData(void) const;
unsigned char * data(void);
private:
SecureMemory(void * memory, size_t size);
Q_DISABLE_COPY(SecureMemory)
private:
unsigned char * const m_secureMemory;
const size_t m_size;
};
using SecureRandom = std::function<bool(void *, size_t)>;
bool defaultSecureRandom(void *data, size_t size);
class SecureMasterKey
{
public:
static bool validate(const SecureMemory *secret);
static bool validate(const KeyDerivationParameters &params);
static bool validate(const QByteArray &salt);
static SecureMasterKey * derive(const SecureMemory *password, const KeyDerivationParameters &params, const SecureRandom &random = defaultSecureRandom);
static SecureMasterKey * derive(const SecureMemory *password, const KeyDerivationParameters &params, const QByteArray &salt, const SecureRandom &random = defaultSecureRandom);
public:
const KeyDerivationParameters params(void) const;
const QByteArray salt(void) const;
virtual ~SecureMasterKey();
std::optional<EncryptedSecret> encrypt(const SecureMemory *secret) const;
SecureMemory * decrypt(const EncryptedSecret &secret) const;
private:
SecureMasterKey(unsigned char *keyMemory, const KeyDerivationParameters &params, const QByteArray &salt, const SecureRandom &random);
Q_DISABLE_COPY(SecureMasterKey)
private:
unsigned char * const m_secureKeyMemory;
const KeyDerivationParameters m_params;
const QByteArray m_salt;
const SecureRandom m_secureRandom;
};
SecureMemory * decodeBase32(const QString &encoded, int from = 0, int until = -1);
}
#endif

12
src/secrets/sodium_cpp.h Normal file
View File

@ -0,0 +1,12 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
*/
#ifndef SECRET_SODIUM_H
#define SECRET_SODIUM_H
extern "C" {
#include <sodium.h>
}
#endif