From c50371d583e2a2a26d8ea9dfe44621c7f7021e68 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Tue, 27 Oct 2015 23:19:20 +0100 Subject: [PATCH] Add symmetric ciphers to crypto lib --- crypto/symmetric/CFB_Cipher.h | 66 +++++ crypto/symmetric/Cipher.h | 30 +++ crypto/symmetric/GCM_Cipher.h | 84 ++++++ crypto/symmetric/ciphers.h | 47 ++++ test/crypto/symmetric/CipherTest.cpp | 254 ++++++++++++++++++ .../testutils/FakeAuthenticatedCipher.cpp | 5 + .../testutils/FakeAuthenticatedCipher.h | 112 ++++++++ 7 files changed, 598 insertions(+) create mode 100644 crypto/symmetric/CFB_Cipher.h create mode 100644 crypto/symmetric/Cipher.h create mode 100644 crypto/symmetric/GCM_Cipher.h create mode 100644 crypto/symmetric/ciphers.h create mode 100644 test/crypto/symmetric/CipherTest.cpp create mode 100644 test/crypto/symmetric/testutils/FakeAuthenticatedCipher.cpp create mode 100644 test/crypto/symmetric/testutils/FakeAuthenticatedCipher.h diff --git a/crypto/symmetric/CFB_Cipher.h b/crypto/symmetric/CFB_Cipher.h new file mode 100644 index 00000000..f67a8c16 --- /dev/null +++ b/crypto/symmetric/CFB_Cipher.h @@ -0,0 +1,66 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CFBCIPHER_H_ +#define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CFBCIPHER_H_ + +#include "../../data/FixedSizeData.h" +#include "../../data/Data.h" +#include "../../random/Random.h" +#include +#include +#include "Cipher.h" + +namespace cpputils { + +template +class CFB_Cipher { +public: + BOOST_CONCEPT_ASSERT((CipherConcept>)); + + using EncryptionKey = FixedSizeData; + + static EncryptionKey CreateKey(RandomGenerator &randomGenerator) { + return randomGenerator.getFixedSize(); + } + + static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) { + return plaintextBlockSize + IV_SIZE; + } + + static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) { + return ciphertextBlockSize - IV_SIZE; + } + + static Data encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey); + static boost::optional decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey); + +private: + static constexpr unsigned int IV_SIZE = BlockCipher::BLOCKSIZE; +}; + +template +Data CFB_Cipher::encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) { + FixedSizeData iv = Random::PseudoRandom().getFixedSize(); + auto encryption = typename CryptoPP::CFB_Mode::Encryption(encKey.data(), encKey.BINARY_LENGTH, iv.data()); + Data ciphertext(ciphertextSize(plaintextSize)); + std::memcpy(ciphertext.data(), iv.data(), IV_SIZE); + encryption.ProcessData((byte*)ciphertext.data() + IV_SIZE, plaintext, plaintextSize); + return ciphertext; +} + +template +boost::optional CFB_Cipher::decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) { + if (ciphertextSize < IV_SIZE) { + return boost::none; + } + + const byte *ciphertextIV = ciphertext; + const byte *ciphertextData = ciphertext + IV_SIZE; + auto decryption = typename CryptoPP::CFB_Mode::Decryption((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV); + Data plaintext(plaintextSize(ciphertextSize)); + decryption.ProcessData((byte*)plaintext.data(), ciphertextData, plaintext.size()); + return std::move(plaintext); +} + +} + +#endif diff --git a/crypto/symmetric/Cipher.h b/crypto/symmetric/Cipher.h new file mode 100644 index 00000000..d8e705d5 --- /dev/null +++ b/crypto/symmetric/Cipher.h @@ -0,0 +1,30 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHER_H_ +#define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHER_H_ + +#include +#include +#include "../../data/Data.h" +#include "../../random/Random.h" + +namespace cpputils { + +template +struct CipherConcept { +public: + BOOST_CONCEPT_USAGE(CipherConcept) { + same_type(UINT32_C(0), X::ciphertextSize(UINT32_C(5))); + same_type(UINT32_C(0), X::plaintextSize(UINT32_C(5))); + typename X::EncryptionKey key = X::CreateKey(Random::OSRandom()); + same_type(Data(0), X::encrypt((uint8_t*)nullptr, UINT32_C(0), key)); + same_type(boost::optional(Data(0)), X::decrypt((uint8_t*)nullptr, UINT32_C(0), key)); + } + +private: + // Type deduction will fail unless the arguments have the same type. + template void same_type(T const&, T const&); +}; + +} + +#endif diff --git a/crypto/symmetric/GCM_Cipher.h b/crypto/symmetric/GCM_Cipher.h new file mode 100644 index 00000000..2680518f --- /dev/null +++ b/crypto/symmetric/GCM_Cipher.h @@ -0,0 +1,84 @@ +#pragma once +#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 +#include "Cipher.h" + +namespace cpputils { + +template +class GCM_Cipher { +public: + BOOST_CONCEPT_ASSERT((CipherConcept>)); + + using EncryptionKey = FixedSizeData; + + static EncryptionKey CreateKey(RandomGenerator &randomGenerator) { + return randomGenerator.getFixedSize(); + } + + 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 byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey); + static boost::optional decrypt(const 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 +Data GCM_Cipher::encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) { + FixedSizeData iv = Random::PseudoRandom().getFixedSize(); + typename CryptoPP::GCM::Encryption encryption; + encryption.SetKeyWithIV(encKey.data(), encKey.BINARY_LENGTH, iv.data(), IV_SIZE); + Data ciphertext(ciphertextSize(plaintextSize)); + + std::memcpy(ciphertext.data(), iv.data(), IV_SIZE); + CryptoPP::ArraySource(plaintext, plaintextSize, true, + new CryptoPP::AuthenticatedEncryptionFilter(encryption, + new CryptoPP::ArraySink((byte*)ciphertext.data() + IV_SIZE, ciphertext.size() - IV_SIZE), + false, TAG_SIZE + ) + ); + return ciphertext; +} + +template +boost::optional GCM_Cipher::decrypt(const byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) { + if (ciphertextSize < IV_SIZE + TAG_SIZE) { + return boost::none; + } + + const byte *ciphertextIV = ciphertext; + const byte *ciphertextData = ciphertext + IV_SIZE; + typename CryptoPP::GCM::Decryption decryption; + decryption.SetKeyWithIV((byte*)encKey.data(), encKey.BINARY_LENGTH, ciphertextIV, IV_SIZE); + Data plaintext(plaintextSize(ciphertextSize)); + + try { + CryptoPP::ArraySource((byte*)ciphertextData, ciphertextSize - IV_SIZE, true, + new CryptoPP::AuthenticatedDecryptionFilter(decryption, + new CryptoPP::ArraySink((byte*)plaintext.data(), plaintext.size()), + CryptoPP::AuthenticatedDecryptionFilter::DEFAULT_FLAGS, TAG_SIZE + ) + ); + return std::move(plaintext); + } catch (const CryptoPP::HashVerificationFilter::HashVerificationFailed &e) { + return boost::none; + } +} + +} + +#endif diff --git a/crypto/symmetric/ciphers.h b/crypto/symmetric/ciphers.h new file mode 100644 index 00000000..8fb9e5a3 --- /dev/null +++ b/crypto/symmetric/ciphers.h @@ -0,0 +1,47 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHERS_H_ +#define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHERS_H_ + +#include +#include +#include +#include +#include +#include "GCM_Cipher.h" +#include "CFB_Cipher.h" + +namespace cpputils { + +static_assert(32 == CryptoPP::AES::MAX_KEYLENGTH, "If AES offered larger keys, we should offer a variant with it"); +using AES256_GCM = GCM_Cipher; +using AES256_CFB = CFB_Cipher; +using AES128_GCM = GCM_Cipher; +using AES128_CFB = CFB_Cipher; + +static_assert(32 == CryptoPP::Twofish::MAX_KEYLENGTH, "If Twofish offered larger keys, we should offer a variant with it"); +using Twofish256_GCM = GCM_Cipher; +using Twofish256_CFB = CFB_Cipher; +using Twofish128_GCM = GCM_Cipher; +using Twofish128_CFB = CFB_Cipher; + +static_assert(32 == CryptoPP::Serpent::MAX_KEYLENGTH, "If Serpent offered larger keys, we should offer a variant with it"); +using Serpent256_GCM = GCM_Cipher; +using Serpent256_CFB = CFB_Cipher; +using Serpent128_GCM = GCM_Cipher; +using Serpent128_CFB = CFB_Cipher; + +static_assert(32 == CryptoPP::CAST256::MAX_KEYLENGTH, "If Cast offered larger keys, we should offer a variant with it"); +using Cast256_GCM = GCM_Cipher; +using Cast256_CFB = CFB_Cipher; + +static_assert(56 == CryptoPP::MARS::MAX_KEYLENGTH, "If Mars offered larger keys, we should offer a variant with it"); +using Mars448_GCM = GCM_Cipher; +using Mars448_CFB = CFB_Cipher; +using Mars256_GCM = GCM_Cipher; +using Mars256_CFB = CFB_Cipher; +using Mars128_GCM = GCM_Cipher; +using Mars128_CFB = CFB_Cipher; + +} + +#endif diff --git a/test/crypto/symmetric/CipherTest.cpp b/test/crypto/symmetric/CipherTest.cpp new file mode 100644 index 00000000..d51fb4c6 --- /dev/null +++ b/test/crypto/symmetric/CipherTest.cpp @@ -0,0 +1,254 @@ +#include +#include "../../../crypto/symmetric/Cipher.h" +#include "../../../crypto/symmetric/ciphers.h" +#include "testutils/FakeAuthenticatedCipher.h" + +#include "../../../data/DataFixture.h" +#include "../../../data/Data.h" +#include + +using namespace cpputils; + +template +class CipherTest: public ::testing::Test { +public: + BOOST_CONCEPT_ASSERT((CipherConcept)); + typename Cipher::EncryptionKey encKey = createKeyFixture(); + + static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) { + Data data = DataFixture::generate(Cipher::EncryptionKey::BINARY_LENGTH, seed); + return Cipher::EncryptionKey::FromBinary(data.data()); + } + + void CheckEncryptThenDecryptIsIdentity(const Data &plaintext) { + Data ciphertext = Encrypt(plaintext); + Data decrypted = Decrypt(ciphertext); + EXPECT_EQ(plaintext, decrypted); + } + + void CheckEncryptIsIndeterministic(const Data &plaintext) { + Data ciphertext = Encrypt(plaintext); + Data ciphertext2 = Encrypt(plaintext); + EXPECT_NE(ciphertext, ciphertext2); + } + + void CheckEncryptedSize(const Data &plaintext) { + Data ciphertext = Encrypt(plaintext); + EXPECT_EQ(Cipher::ciphertextSize(plaintext.size()), ciphertext.size()); + } + + void ExpectDoesntDecrypt(const Data &ciphertext) { + auto decrypted = Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey); + EXPECT_FALSE(decrypted); + } + + Data Encrypt(const Data &plaintext) { + return Cipher::encrypt((byte*)plaintext.data(), plaintext.size(), this->encKey); + } + + Data Decrypt(const Data &ciphertext) { + return Cipher::decrypt((byte*)ciphertext.data(), ciphertext.size(), this->encKey).value(); + } + + static Data CreateZeroes(unsigned int size) { + return std::move(Data(size).FillWithZeroes()); + } + + static Data CreateData(unsigned int size, unsigned int seed = 0) { + return DataFixture::generate(size, seed); + } +}; + +TYPED_TEST_CASE_P(CipherTest); + +constexpr std::initializer_list SIZES = {0, 1, 100, 1024, 5000, 1048576, 20971520}; + +TYPED_TEST_P(CipherTest, Size) { + for (auto size: SIZES) { + EXPECT_EQ(size, TypeParam::ciphertextSize(TypeParam::plaintextSize(size))); + EXPECT_EQ(size, TypeParam::plaintextSize(TypeParam::ciphertextSize(size))); + } +} + +TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Zeroes) { + for (auto size: SIZES) { + Data plaintext = this->CreateZeroes(size); + this->CheckEncryptThenDecryptIsIdentity(plaintext); + } +} + +TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Data) { + for (auto size: SIZES) { + Data plaintext = this->CreateData(size); + this->CheckEncryptThenDecryptIsIdentity(plaintext); + } +} + +TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Zeroes) { + for (auto size: SIZES) { + Data plaintext = this->CreateZeroes(size); + this->CheckEncryptIsIndeterministic(plaintext); + } +} + +TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Data) { + for (auto size: SIZES) { + Data plaintext = this->CreateData(size); + this->CheckEncryptIsIndeterministic(plaintext); + } +} + +TYPED_TEST_P(CipherTest, EncryptedSize) { + for (auto size: SIZES) { + Data plaintext = this->CreateData(size); + this->CheckEncryptedSize(plaintext); + } +} + +TYPED_TEST_P(CipherTest, TryDecryptDataThatIsTooSmall) { + Data tooSmallCiphertext(TypeParam::ciphertextSize(0) - 1); + this->ExpectDoesntDecrypt(tooSmallCiphertext); +} + +TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_0) { + static_assert(TypeParam::ciphertextSize(0) > 0, "If this fails, the test case doesn't make sense."); + Data tooSmallCiphertext(0); + this->ExpectDoesntDecrypt(tooSmallCiphertext); +} + +TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_1) { + static_assert(TypeParam::ciphertextSize(0) > 1, "If this fails, the test case doesn't make sense."); + Data tooSmallCiphertext(1); + this->ExpectDoesntDecrypt(tooSmallCiphertext); +} + +REGISTER_TYPED_TEST_CASE_P(CipherTest, + Size, + EncryptThenDecrypt_Zeroes, + EncryptThenDecrypt_Data, + EncryptIsIndeterministic_Zeroes, + EncryptIsIndeterministic_Data, + EncryptedSize, + TryDecryptDataThatIsTooSmall, + TryDecryptDataThatIsMuchTooSmall_0, + TryDecryptDataThatIsMuchTooSmall_1 +); + +template +class AuthenticatedCipherTest: public CipherTest { +public: + Data zeroes1 = CipherTest::CreateZeroes(1); + Data plaintext1 = CipherTest::CreateData(1); + Data zeroes2 = CipherTest::CreateZeroes(100 * 1024); + Data plaintext2 = CipherTest::CreateData(100 * 1024); +}; + +TYPED_TEST_CASE_P(AuthenticatedCipherTest); + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes_Size1) { + Data ciphertext = this->Encrypt(this->zeroes1); + *(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data_Size1) { + Data ciphertext = this->Encrypt(this->plaintext1); + *(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes) { + Data ciphertext = this->Encrypt(this->zeroes2); + *(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data) { + Data ciphertext = this->Encrypt(this->plaintext2); + *(byte*)ciphertext.data() = *(byte*)ciphertext.data() + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Zeroes) { + Data ciphertext = this->Encrypt(this->zeroes2); + ((byte*)ciphertext.data())[ciphertext.size() - 1] = ((byte*)ciphertext.data())[ciphertext.size() - 1] + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Data) { + Data ciphertext = this->Encrypt(this->plaintext2); + ((byte*)ciphertext.data())[ciphertext.size() - 1] = ((byte*)ciphertext.data())[ciphertext.size() - 1] + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Zeroes) { + Data ciphertext = this->Encrypt(this->zeroes2); + ((byte*)ciphertext.data())[ciphertext.size()/2] = ((byte*)ciphertext.data())[ciphertext.size()/2] + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Data) { + Data ciphertext = this->Encrypt(this->plaintext2); + ((byte*)ciphertext.data())[ciphertext.size()/2] = ((byte*)ciphertext.data())[ciphertext.size()/2] + 1; + this->ExpectDoesntDecrypt(ciphertext); +} + +TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptZeroesData) { + this->ExpectDoesntDecrypt(this->zeroes2); +} + +TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptRandomData) { + this->ExpectDoesntDecrypt(this->plaintext2); +} + +REGISTER_TYPED_TEST_CASE_P(AuthenticatedCipherTest, + ModifyFirstByte_Zeroes_Size1, + ModifyFirstByte_Zeroes, + ModifyFirstByte_Data_Size1, + ModifyFirstByte_Data, + ModifyLastByte_Zeroes, + ModifyLastByte_Data, + ModifyMiddleByte_Zeroes, + ModifyMiddleByte_Data, + TryDecryptZeroesData, + TryDecryptRandomData +); + + +INSTANTIATE_TYPED_TEST_CASE_P(Fake, CipherTest, FakeAuthenticatedCipher); +INSTANTIATE_TYPED_TEST_CASE_P(Fake, AuthenticatedCipherTest, FakeAuthenticatedCipher); + +INSTANTIATE_TYPED_TEST_CASE_P(AES256_CFB, CipherTest, AES256_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, CipherTest, AES256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(AES128_CFB, CipherTest, AES128_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(AES128_GCM, CipherTest, AES128_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(AES128_GCM, AuthenticatedCipherTest, AES128_GCM); + +INSTANTIATE_TYPED_TEST_CASE_P(Twofish256_CFB, CipherTest, Twofish256_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Twofish256_GCM, CipherTest, Twofish256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Twofish256_GCM, AuthenticatedCipherTest, Twofish256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Twofish128_CFB, CipherTest, Twofish128_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Twofish128_GCM, CipherTest, Twofish128_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Twofish128_GCM, AuthenticatedCipherTest, Twofish128_GCM); + +INSTANTIATE_TYPED_TEST_CASE_P(Serpent256_CFB, CipherTest, Serpent256_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Serpent256_GCM, CipherTest, Serpent256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Serpent256_GCM, AuthenticatedCipherTest, Serpent256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Serpent128_CFB, CipherTest, Serpent128_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Serpent128_GCM, CipherTest, Serpent128_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Serpent128_GCM, AuthenticatedCipherTest, Serpent128_GCM); + +INSTANTIATE_TYPED_TEST_CASE_P(Cast256_CFB, CipherTest, Cast256_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Cast256_GCM, CipherTest, Cast256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Cast256_GCM, AuthenticatedCipherTest, Cast256_GCM); + +INSTANTIATE_TYPED_TEST_CASE_P(Mars448_CFB, CipherTest, Mars448_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Mars448_GCM, CipherTest, Mars448_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Mars448_GCM, AuthenticatedCipherTest, Mars448_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Mars256_CFB, CipherTest, Mars256_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Mars256_GCM, CipherTest, Mars256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Mars256_GCM, AuthenticatedCipherTest, Mars256_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Mars128_CFB, CipherTest, Mars128_CFB); //CFB mode is not authenticated +INSTANTIATE_TYPED_TEST_CASE_P(Mars128_GCM, CipherTest, Mars128_GCM); +INSTANTIATE_TYPED_TEST_CASE_P(Mars128_GCM, AuthenticatedCipherTest, Mars128_GCM); \ No newline at end of file diff --git a/test/crypto/symmetric/testutils/FakeAuthenticatedCipher.cpp b/test/crypto/symmetric/testutils/FakeAuthenticatedCipher.cpp new file mode 100644 index 00000000..e2a6232a --- /dev/null +++ b/test/crypto/symmetric/testutils/FakeAuthenticatedCipher.cpp @@ -0,0 +1,5 @@ +#include "FakeAuthenticatedCipher.h" + +namespace cpputils { + constexpr unsigned int FakeKey::BINARY_LENGTH; +} diff --git a/test/crypto/symmetric/testutils/FakeAuthenticatedCipher.h b/test/crypto/symmetric/testutils/FakeAuthenticatedCipher.h new file mode 100644 index 00000000..ff832c83 --- /dev/null +++ b/test/crypto/symmetric/testutils/FakeAuthenticatedCipher.h @@ -0,0 +1,112 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_TEST_CRYPTO_SYMMETRIC_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_ +#define MESSMER_CPPUTILS_TEST_CRYPTO_SYMMETRIC_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_ + +#include "../../../../crypto/symmetric/Cipher.h" +#include "../../../../data/FixedSizeData.h" +#include "../../../../data/Data.h" +#include "../../../../random/RandomGenerator.h" + +namespace cpputils { + + struct FakeKey { + static FakeKey FromBinary(const void *data) { + return FakeKey{*(uint8_t *) data}; + } + + static constexpr unsigned int BINARY_LENGTH = 1; + + uint8_t value; + }; + + // This is a fake cipher that uses an indeterministic caesar chiffre and a 4-byte parity for a simple authentication mechanism + class FakeAuthenticatedCipher { + public: + BOOST_CONCEPT_ASSERT((CipherConcept)); + + using EncryptionKey = FakeKey; + + static EncryptionKey CreateKey(RandomGenerator &randomGenerator) { + auto data = randomGenerator.getFixedSize<1>(); + return FakeKey{*((uint8_t *) data.data())}; + } + + static EncryptionKey Key1() { + return FakeKey{5}; + } + + static EncryptionKey Key2() { + return FakeKey{63}; + } + + static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) { + return plaintextBlockSize + 5; + } + + static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) { + return ciphertextBlockSize - 5; + } + + static Data encrypt(const byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) { + Data result(ciphertextSize(plaintextSize)); + + //Add a random IV + uint8_t iv = rand(); + std::memcpy(result.data(), &iv, 1); + + //Use caesar chiffre on plaintext + _caesar((byte *) result.data() + 1, plaintext, plaintextSize, encKey.value + iv); + + //Add parity information + int32_t parity = _parity((byte *) result.data(), plaintextSize + 1); + std::memcpy((byte *) result.data() + plaintextSize + 1, &parity, 4); + + return result; + } + + static boost::optional decrypt(const byte *ciphertext, unsigned int ciphertextSize, + const EncryptionKey &encKey) { + //We need at least 5 bytes (iv + parity) + if (ciphertextSize < 5) { + return boost::none; + } + + //Check parity + int32_t expectedParity = _parity(ciphertext, plaintextSize(ciphertextSize) + 1); + int32_t actualParity = *(int32_t * )(ciphertext + plaintextSize(ciphertextSize) + 1); + if (expectedParity != actualParity) { + return boost::none; + } + + //Decrypt caesar chiffre from ciphertext + int32_t iv = *(int32_t *) ciphertext; + Data result(plaintextSize(ciphertextSize)); + _caesar((byte *) result.data(), ciphertext + 1, plaintextSize(ciphertextSize), -(encKey.value + iv)); + return std::move(result); + } + + private: + static int32_t _parity(const byte *data, unsigned int size) { + int32_t parity = 34343435; // some init value + int32_t *intData = (int32_t *) data; + unsigned int intSize = size / sizeof(int32_t); + for (unsigned int i = 0; i < intSize; ++i) { + parity += intData[i]; + } + unsigned int remainingBytes = size - 4 * intSize; + for (unsigned int i = 0; i < remainingBytes; ++i) { + parity += (data[4 * intSize + i] << (24 - 8 * i)); + } + return parity; + } + + static void _caesar(byte *dst, const byte *src, unsigned int size, uint8_t key) { + for (unsigned int i = 0; i < size; ++i) { + dst[i] = src[i] + key; + } + } + }; + +} + +#endif