Add XChaCha20-Poly1305 cipher and make it the default

This commit is contained in:
Sebastian Messmer 2021-01-14 20:02:06 -08:00
parent 042c7130eb
commit a245ac1ffb
9 changed files with 150 additions and 111 deletions

View File

@ -1,5 +1,14 @@
Version 0.11.0 (unreleased)
---------------
Security:
* Added the XChaCha20-Poly1305 encryption cipher. For new filesystems, this will be the default, but you're still able to create a filesystem with the previous default of AES-256-GCM
by saying "no" to the "use default settings?" question when creating the file system. Also, old filesystems will not be automatically converted and will keep using AES-256-GCM.
XChaCha20-Poly1305 is significantly slower than AES-256-GCM on modern CPUs, but it is more secure for large filesystems (>64GB).
For AES-256-GCM, it is recommended to encrypt at most 2^32 blocks, which at the CryFS default block size of 16KB would be 64GB. The more the filesystem grows above that, the
more likely it gets that a nonce gets reused and the two corresponding blocks become decryptable by an adversary. Other blocks would not be affected, but an adversary being
able to access those two blocks (i.e. 64KB of the stored data) is bad enough. See Section 8.3 in https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
XChaCha20-Poly1305 does not suffer from this constraint and stays secure even if the filesystem gets very large.
Build changes:
* Switch to Conan package manager
* Allow an easy way to modify how the dependencies are found. This is mostly helpful for package maintainers.
@ -13,8 +22,8 @@ Improvements:
New features:
* Add support for atime mount options (noatime, strictatime, relatime, atime, nodiratime).
* The new default is now *noatime* (in 0.10.x is was relatime).
Noatime reduces the amount of write necessary and with that reduces the probability for synchronization conflicts
or corrupted file systems if a power outage happens while writing.
Noatime reduces the amount of writes necessary and with that reduces the probability of synchronization conflicts,
and the probability of corrupted file systems if a power outage happens while writing.
* Add an --immediate flag to cryfs-unmount that tries to unmount immediately and doesn't wait for processes to release their locks on the file system.
* Add a --create-missing-basedir and --create-missing-mountpoint flag to create the base directory and mount directory respectively, if they don't exist, skipping the confirmation prompt.

View File

@ -0,0 +1,90 @@
#pragma once
#ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_AEADCIPHER_H_
#define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_AEADCIPHER_H_
#include "../../data/FixedSizeData.h"
#include "../../data/Data.h"
#include "../../random/Random.h"
#include "Cipher.h"
#include "EncryptionKey.h"
namespace cpputils {
template<class CryptoPPCipher, unsigned int _KEY_SIZE, unsigned int _IV_SIZE, unsigned int _TAG_SIZE>
class AEADCipher {
public:
using EncryptionKey = cpputils::EncryptionKey;
static constexpr unsigned int KEYSIZE = _KEY_SIZE;
static constexpr unsigned int STRING_KEYSIZE = 2 * KEYSIZE;
static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) {
return plaintextBlockSize + IV_SIZE + TAG_SIZE;
}
static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) {
return ciphertextBlockSize - IV_SIZE - TAG_SIZE;
}
static Data encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey);
static boost::optional<Data> decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey);
private:
static constexpr unsigned int IV_SIZE = _IV_SIZE;
static constexpr unsigned int TAG_SIZE = _TAG_SIZE;
};
template<class CryptoPPCipher, unsigned int _KEY_SIZE, unsigned int _IV_SIZE, unsigned int _TAG_SIZE>
constexpr unsigned int AEADCipher<CryptoPPCipher, _KEY_SIZE, _IV_SIZE, _TAG_SIZE>::KEYSIZE;
template<class CryptoPPCipher, unsigned int _KEY_SIZE, unsigned int _IV_SIZE, unsigned int _TAG_SIZE>
constexpr unsigned int AEADCipher<CryptoPPCipher, _KEY_SIZE, _IV_SIZE, _TAG_SIZE>::STRING_KEYSIZE;
template<class CryptoPPCipher, unsigned int _KEY_SIZE, unsigned int _IV_SIZE, unsigned int _TAG_SIZE>
Data AEADCipher<CryptoPPCipher, _KEY_SIZE, _IV_SIZE, _TAG_SIZE>::encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) {
ASSERT(encKey.binaryLength() == AEADCipher::KEYSIZE, "Wrong key size");
FixedSizeData<IV_SIZE> iv = Random::PseudoRandom().getFixedSize<IV_SIZE>();
typename CryptoPPCipher::Encryption encryption;
encryption.SetKeyWithIV(static_cast<const CryptoPP::byte*>(encKey.data()), encKey.binaryLength(), iv.data(), IV_SIZE);
Data ciphertext(ciphertextSize(plaintextSize));
iv.ToBinary(ciphertext.data());
CryptoPP::ArraySource(plaintext, plaintextSize, true,
new CryptoPP::AuthenticatedEncryptionFilter(encryption,
new CryptoPP::ArraySink(static_cast<CryptoPP::byte*>(ciphertext.data()) + IV_SIZE, ciphertext.size() - IV_SIZE),
false, TAG_SIZE
)
);
return ciphertext;
}
template<class CryptoPPCipher, unsigned int _KEY_SIZE, unsigned int _IV_SIZE, unsigned int _TAG_SIZE>
boost::optional<Data> AEADCipher<CryptoPPCipher, _KEY_SIZE, _IV_SIZE, _TAG_SIZE>::decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
ASSERT(encKey.binaryLength() == AEADCipher::KEYSIZE, "Wrong key size");
if (ciphertextSize < IV_SIZE + TAG_SIZE) {
return boost::none;
}
const CryptoPP::byte *ciphertextIV = ciphertext;
const CryptoPP::byte *ciphertextData = ciphertext + IV_SIZE;
typename CryptoPPCipher::Decryption decryption;
decryption.SetKeyWithIV(static_cast<const CryptoPP::byte*>(encKey.data()), encKey.binaryLength(), ciphertextIV, IV_SIZE);
Data plaintext(plaintextSize(ciphertextSize));
try {
CryptoPP::ArraySource(static_cast<const CryptoPP::byte*>(ciphertextData), ciphertextSize - IV_SIZE, true,
new CryptoPP::AuthenticatedDecryptionFilter(decryption,
new CryptoPP::ArraySink(static_cast<CryptoPP::byte*>(plaintext.data()), plaintext.size()),
CryptoPP::AuthenticatedDecryptionFilter::DEFAULT_FLAGS, TAG_SIZE
)
);
return plaintext;
} catch (const CryptoPP::HashVerificationFilter::HashVerificationFailed &e) {
return boost::none;
}
}
}
#endif

View File

@ -2,89 +2,13 @@
#ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_GCMCIPHER_H_
#define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_GCMCIPHER_H_
#include "../../data/FixedSizeData.h"
#include "../../data/Data.h"
#include "../../random/Random.h"
#include "AEAD_Cipher.h"
#include <vendor_cryptopp/gcm.h>
#include "Cipher.h"
#include "EncryptionKey.h"
namespace cpputils {
template<typename BlockCipher, unsigned int KeySize>
class GCM_Cipher {
public:
using EncryptionKey = cpputils::EncryptionKey;
static constexpr unsigned int KEYSIZE = KeySize;
static constexpr unsigned int STRING_KEYSIZE = 2 * KEYSIZE;
static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) {
return plaintextBlockSize + IV_SIZE + TAG_SIZE;
}
static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) {
return ciphertextBlockSize - IV_SIZE - TAG_SIZE;
}
static Data encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey);
static boost::optional<Data> decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey);
private:
static constexpr unsigned int IV_SIZE = BlockCipher::BLOCKSIZE;
static constexpr unsigned int TAG_SIZE = 16;
};
template<class BlockCipher, unsigned int KeySize>
constexpr unsigned int GCM_Cipher<BlockCipher, KeySize>::KEYSIZE;
template<class BlockCipher, unsigned int KeySize>
constexpr unsigned int GCM_Cipher<BlockCipher, KeySize>::STRING_KEYSIZE;
template<typename BlockCipher, unsigned int KeySize>
Data GCM_Cipher<BlockCipher, KeySize>::encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) {
ASSERT(encKey.binaryLength() == KeySize, "Wrong key size");
FixedSizeData<IV_SIZE> iv = Random::PseudoRandom().getFixedSize<IV_SIZE>();
typename CryptoPP::GCM<BlockCipher, CryptoPP::GCM_64K_Tables>::Encryption encryption;
encryption.SetKeyWithIV(static_cast<const CryptoPP::byte*>(encKey.data()), encKey.binaryLength(), iv.data(), IV_SIZE);
Data ciphertext(ciphertextSize(plaintextSize));
iv.ToBinary(ciphertext.data());
CryptoPP::ArraySource(plaintext, plaintextSize, true,
new CryptoPP::AuthenticatedEncryptionFilter(encryption,
new CryptoPP::ArraySink(static_cast<CryptoPP::byte*>(ciphertext.data()) + IV_SIZE, ciphertext.size() - IV_SIZE),
false, TAG_SIZE
)
);
return ciphertext;
}
template<typename BlockCipher, unsigned int KeySize>
boost::optional<Data> GCM_Cipher<BlockCipher, KeySize>::decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) {
ASSERT(encKey.binaryLength() == KeySize, "Wrong key size");
if (ciphertextSize < IV_SIZE + TAG_SIZE) {
return boost::none;
}
const CryptoPP::byte *ciphertextIV = ciphertext;
const CryptoPP::byte *ciphertextData = ciphertext + IV_SIZE;
typename CryptoPP::GCM<BlockCipher, CryptoPP::GCM_64K_Tables>::Decryption decryption;
decryption.SetKeyWithIV(static_cast<const CryptoPP::byte*>(encKey.data()), encKey.binaryLength(), ciphertextIV, IV_SIZE);
Data plaintext(plaintextSize(ciphertextSize));
try {
CryptoPP::ArraySource(static_cast<const CryptoPP::byte*>(ciphertextData), ciphertextSize - IV_SIZE, true,
new CryptoPP::AuthenticatedDecryptionFilter(decryption,
new CryptoPP::ArraySink(static_cast<CryptoPP::byte*>(plaintext.data()), plaintext.size()),
CryptoPP::AuthenticatedDecryptionFilter::DEFAULT_FLAGS, TAG_SIZE
)
);
return plaintext;
} catch (const CryptoPP::HashVerificationFilter::HashVerificationFailed &e) {
return boost::none;
}
}
using GCM_Cipher = AEADCipher<CryptoPP::GCM<BlockCipher, CryptoPP::GCM_64K_Tables>, KeySize, BlockCipher::BLOCKSIZE, 16>;
}

View File

@ -5,6 +5,8 @@
namespace cpputils {
DEFINE_CIPHER(XChaCha20Poly1305);
DEFINE_CIPHER(AES256_GCM);
DEFINE_CIPHER(AES256_CFB);
DEFINE_CIPHER(AES128_GCM);

View File

@ -7,48 +7,57 @@
#include <vendor_cryptopp/serpent.h>
#include <vendor_cryptopp/cast.h>
#include <vendor_cryptopp/mars.h>
#include <vendor_cryptopp/chachapoly.h>
#include "GCM_Cipher.h"
#include "CFB_Cipher.h"
#define DECLARE_CIPHER(InstanceName, StringName, Mode, Base, Keysize) \
class InstanceName final: public Mode<Base, Keysize> { \
public: \
BOOST_CONCEPT_ASSERT((CipherConcept<InstanceName>)); \
static constexpr const char *NAME = StringName; \
} \
namespace cpputils {
// REMOVE_PARENTHESES_FROM_TYPENAME is needed because the DECLARE_CIPHER macro will get a typename enclosed in parentheses.
#define SINGLE_ARG(...) __VA_ARGS__
#define DECLARE_CIPHER(InstanceName, StringName, Impl) \
class InstanceName final: public Impl { \
public: \
BOOST_CONCEPT_ASSERT((CipherConcept<InstanceName>)); \
static constexpr const char *NAME = StringName; \
} \
DECLARE_CIPHER(XChaCha20Poly1305, "xchacha20-poly1305", SINGLE_ARG(AEADCipher<CryptoPP::XChaCha20Poly1305, 32, 24, 16>));
static_assert(32 == CryptoPP::AES::MAX_KEYLENGTH, "If AES offered larger keys, we should offer a variant with it");
DECLARE_CIPHER(AES256_GCM, "aes-256-gcm", GCM_Cipher, CryptoPP::AES, 32);
DECLARE_CIPHER(AES256_CFB, "aes-256-cfb", CFB_Cipher, CryptoPP::AES, 32);
DECLARE_CIPHER(AES128_GCM, "aes-128-gcm", GCM_Cipher, CryptoPP::AES, 16);
DECLARE_CIPHER(AES128_CFB, "aes-128-cfb", CFB_Cipher, CryptoPP::AES, 16);
DECLARE_CIPHER(AES256_GCM, "aes-256-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::AES, 32>));
DECLARE_CIPHER(AES256_CFB, "aes-256-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::AES, 32>));
DECLARE_CIPHER(AES128_GCM, "aes-128-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::AES, 16>));
DECLARE_CIPHER(AES128_CFB, "aes-128-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::AES, 16>));
static_assert(32 == CryptoPP::Twofish::MAX_KEYLENGTH, "If Twofish offered larger keys, we should offer a variant with it");
DECLARE_CIPHER(Twofish256_GCM, "twofish-256-gcm", GCM_Cipher, CryptoPP::Twofish, 32);
DECLARE_CIPHER(Twofish256_CFB, "twofish-256-cfb", CFB_Cipher, CryptoPP::Twofish, 32);
DECLARE_CIPHER(Twofish128_GCM, "twofish-128-gcm", GCM_Cipher, CryptoPP::Twofish, 16);
DECLARE_CIPHER(Twofish128_CFB, "twofish-128-cfb", CFB_Cipher, CryptoPP::Twofish, 16);
DECLARE_CIPHER(Twofish256_GCM, "twofish-256-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::Twofish, 32>));
DECLARE_CIPHER(Twofish256_CFB, "twofish-256-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::Twofish, 32>));
DECLARE_CIPHER(Twofish128_GCM, "twofish-128-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::Twofish, 16>));
DECLARE_CIPHER(Twofish128_CFB, "twofish-128-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::Twofish, 16>));
static_assert(32 == CryptoPP::Serpent::MAX_KEYLENGTH, "If Serpent offered larger keys, we should offer a variant with it");
DECLARE_CIPHER(Serpent256_GCM, "serpent-256-gcm", GCM_Cipher, CryptoPP::Serpent, 32);
DECLARE_CIPHER(Serpent256_CFB, "serpent-256-cfb", CFB_Cipher, CryptoPP::Serpent, 32);
DECLARE_CIPHER(Serpent128_GCM, "serpent-128-gcm", GCM_Cipher, CryptoPP::Serpent, 16);
DECLARE_CIPHER(Serpent128_CFB, "serpent-128-cfb", CFB_Cipher, CryptoPP::Serpent, 16);
DECLARE_CIPHER(Serpent256_GCM, "serpent-256-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::Serpent, 32>));
DECLARE_CIPHER(Serpent256_CFB, "serpent-256-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::Serpent, 32>));
DECLARE_CIPHER(Serpent128_GCM, "serpent-128-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::Serpent, 16>));
DECLARE_CIPHER(Serpent128_CFB, "serpent-128-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::Serpent, 16>));
static_assert(32 == CryptoPP::CAST256::MAX_KEYLENGTH, "If Cast offered larger keys, we should offer a variant with it");
DECLARE_CIPHER(Cast256_GCM, "cast-256-gcm", GCM_Cipher, CryptoPP::CAST256, 32);
DECLARE_CIPHER(Cast256_CFB, "cast-256-cfb", CFB_Cipher, CryptoPP::CAST256, 32);
DECLARE_CIPHER(Cast256_GCM, "cast-256-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::CAST256, 32>));
DECLARE_CIPHER(Cast256_CFB, "cast-256-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::CAST256, 32>));
static_assert(56 == CryptoPP::MARS::MAX_KEYLENGTH, "If Mars offered larger keys, we should offer a variant with it");
DECLARE_CIPHER(Mars448_GCM, "mars-448-gcm", GCM_Cipher, CryptoPP::MARS, 56);
DECLARE_CIPHER(Mars448_CFB, "mars-448-cfb", CFB_Cipher, CryptoPP::MARS, 56);
DECLARE_CIPHER(Mars256_GCM, "mars-256-gcm", GCM_Cipher, CryptoPP::MARS, 32);
DECLARE_CIPHER(Mars256_CFB, "mars-256-cfb", CFB_Cipher, CryptoPP::MARS, 32);
DECLARE_CIPHER(Mars128_GCM, "mars-128-gcm", GCM_Cipher, CryptoPP::MARS, 16);
DECLARE_CIPHER(Mars128_CFB, "mars-128-cfb", CFB_Cipher, CryptoPP::MARS, 16);
DECLARE_CIPHER(Mars448_GCM, "mars-448-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::MARS, 56>));
DECLARE_CIPHER(Mars448_CFB, "mars-448-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::MARS, 56>));
DECLARE_CIPHER(Mars256_GCM, "mars-256-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::MARS, 32>));
DECLARE_CIPHER(Mars256_CFB, "mars-256-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::MARS, 32>));
DECLARE_CIPHER(Mars128_GCM, "mars-128-gcm", SINGLE_ARG(GCM_Cipher<CryptoPP::MARS, 16>));
DECLARE_CIPHER(Mars128_CFB, "mars-128-cfb", SINGLE_ARG(CFB_Cipher<CryptoPP::MARS, 16>));
}
#undef DECLARE_CIPHER
#undef SINGLE_ARG
#endif

View File

@ -59,6 +59,7 @@ const string CryCiphers::INTEGRITY_WARNING = "This cipher does not ensure integr
//We have to use shared_ptr instead of unique_ref, because c++ initializer_list needs copyable values
const vector<shared_ptr<CryCipher>> CryCiphers::SUPPORTED_CIPHERS = {
make_shared<CryCipherInstance<XChaCha20Poly1305>>(),
make_shared<CryCipherInstance<AES256_GCM>>(),
make_shared<CryCipherInstance<AES256_CFB>>(INTEGRITY_WARNING),
make_shared<CryCipherInstance<AES128_GCM>>(),

View File

@ -16,7 +16,7 @@ namespace cryfs {
uint32_t askBlocksizeBytes();
bool askMissingBlockIsIntegrityViolation();
static constexpr const char *DEFAULT_CIPHER = "aes-256-gcm";
static constexpr const char *DEFAULT_CIPHER = "xchacha20-poly1305";
static constexpr uint32_t DEFAULT_BLOCKSIZE_BYTES = 16 * 1024; // 16KB
static constexpr uint32_t DEFAULT_MISSINGBLOCKISINTEGRITYVIOLATION = false;

View File

@ -227,6 +227,9 @@ REGISTER_TYPED_TEST_SUITE_P(AuthenticatedCipherTest,
INSTANTIATE_TYPED_TEST_SUITE_P(Fake, CipherTest, FakeAuthenticatedCipher);
INSTANTIATE_TYPED_TEST_SUITE_P(Fake, AuthenticatedCipherTest, FakeAuthenticatedCipher);
INSTANTIATE_TYPED_TEST_SUITE_P(XChaCha20Poly1305, CipherTest, XChaCha20Poly1305);
INSTANTIATE_TYPED_TEST_SUITE_P(XChaCha20Poly1305, AuthenticatedCipherTest, XChaCha20Poly1305);
INSTANTIATE_TYPED_TEST_SUITE_P(AES256_CFB, CipherTest, AES256_CFB); //CFB mode is not authenticated
INSTANTIATE_TYPED_TEST_SUITE_P(AES256_GCM, CipherTest, AES256_GCM);
INSTANTIATE_TYPED_TEST_SUITE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM);
@ -265,6 +268,8 @@ INSTANTIATE_TYPED_TEST_SUITE_P(Mars128_GCM, AuthenticatedCipherTest, Mars128_GCM
// Test cipher names
TEST(CipherNameTest, TestCipherNames) {
EXPECT_EQ("xchacha20-poly1305", string(XChaCha20Poly1305::NAME));
EXPECT_EQ("aes-256-gcm", string(AES256_GCM::NAME));
EXPECT_EQ("aes-256-cfb", string(AES256_CFB::NAME));
EXPECT_EQ("aes-128-gcm", string(AES128_GCM::NAME));

View File

@ -262,7 +262,6 @@ TEST_F(CryConfigLoaderTest, EncryptionKey_Load_whenKeyChanged_thenFails) {
TEST_F(CryConfigLoaderTest, EncryptionKey_Create) {
auto created = Create();
//aes-256-gcm is the default cipher chosen by mockConsole()
cpputils::AES256_GCM::EncryptionKey::FromString(created->config()->EncryptionKey()); // This crashes if key is invalid
}
@ -274,8 +273,8 @@ TEST_F(CryConfigLoaderTest, Cipher_Load) {
TEST_F(CryConfigLoaderTest, Cipher_Create) {
auto created = Create();
//aes-256-gcm is the default cipher chosen by mockConsole()
EXPECT_EQ("aes-256-gcm", created->config()->Cipher());
//xchacha20-poly1305 is the default cipher chosen by mockConsole()
EXPECT_EQ("xchacha20-poly1305", created->config()->Cipher());
}
TEST_F(CryConfigLoaderTest, Version_Load) {