From 8678d3cb1b3f29837025634ebdaf258c90c87efe Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Sun, 6 Feb 2022 21:54:43 +0100 Subject: [PATCH] Made the whole thing MUCH more secure, by adding an IV (initialization vector), implemeted RRKM (rolling round key mode) and redone key extrapolation --- ExampleApp/.gitignore | 2 + ExampleApp/main.cpp | 4 +- GhettoCrypt/Cipher.cpp | 4 +- GhettoCrypt/Cipher.h | 2 +- GhettoCrypt/Feistel.cpp | 76 +++++++-- GhettoCrypt/Feistel.h | 6 +- GhettoCrypt/GhettoCryptWrapper.cpp | 13 +- GhettoCrypt/InitializationVector.cpp | 18 +- GhettoCrypt/InitializationVector.h | 21 ++- GhettoCrypt/Util.h | 39 +++-- GhettoCrypt/Version.h | 2 +- SimpleTests/.gitignore | 2 + SimpleTests/EncryptEqualsDecrypt.cpp | 216 ++++++++++++++++++++++-- SimpleTests/GCWrapper.cpp | 81 +++++++++ SimpleTests/SimpleTests.vcxproj | 1 + SimpleTests/SimpleTests.vcxproj.filters | 3 + SimpleTests/testfile.png | Bin 0 -> 9736 bytes readme.md | 2 +- 18 files changed, 427 insertions(+), 65 deletions(-) create mode 100644 ExampleApp/.gitignore create mode 100644 SimpleTests/.gitignore create mode 100644 SimpleTests/GCWrapper.cpp create mode 100644 SimpleTests/testfile.png diff --git a/ExampleApp/.gitignore b/ExampleApp/.gitignore new file mode 100644 index 0000000..366b7f9 --- /dev/null +++ b/ExampleApp/.gitignore @@ -0,0 +1,2 @@ +/main.cpp.crypt +/main.cpp.clear diff --git a/ExampleApp/main.cpp b/ExampleApp/main.cpp index 52851f2..c75ab7d 100644 --- a/ExampleApp/main.cpp +++ b/ExampleApp/main.cpp @@ -12,13 +12,13 @@ void ExampleString() std::cout << "Example on how to encrypt & decrypt a string:" << std::endl; // Get some string - const std::string input = "I am a super secret message!"; + const std::string input = "I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!I am a super secret message!"; std::cout << input << std::endl; // Encrypt const std::string encrypted = GhettoCryptWrapper::EncryptString(input, "password1"); std::cout << encrypted << std::endl; - + // Decrypt const std::string decrypted = GhettoCryptWrapper::DecryptString(encrypted, "password1"); std::cout << decrypted << std::endl; diff --git a/GhettoCrypt/Cipher.cpp b/GhettoCrypt/Cipher.cpp index c17ca0a..ebd93a0 100644 --- a/GhettoCrypt/Cipher.cpp +++ b/GhettoCrypt/Cipher.cpp @@ -65,7 +65,7 @@ GhettoCipher::Flexblock GhettoCipher::Cipher::Encipher(const Flexblock& data, bo std::cout << "Encrypting... (Block " << i << " / " << blocks.size() << " - " << ((float)i*100 / blocks.size()) << "%)" << std::endl; const Block& lastBlock = (i>0) ? blocks[i-1] : initializationVector; - blocks[i] = feistel.Encipher(blocks[i] ^ lastBlock); + blocks[i] = feistel.Encipher(blocks[i] ^ lastBlock); // Xor last cipher block with new clear text block before E() } // Concatenate ciphertext blocks back into a flexblock @@ -101,7 +101,7 @@ GhettoCipher::Flexblock GhettoCipher::Cipher::Decipher(const Flexblock& data, bo Block tmpCopy = blocks[i]; - blocks[i] = feistel.Decipher(blocks[i]) ^ lastBlock; + blocks[i] = feistel.Decipher(blocks[i]) ^ lastBlock; // Decipher cipher block [i] and then xor it with the last cipher block [i-1] we've had lastBlock = std::move(tmpCopy); } diff --git a/GhettoCrypt/Cipher.h b/GhettoCrypt/Cipher.h index cea2985..c66a8d5 100644 --- a/GhettoCrypt/Cipher.h +++ b/GhettoCrypt/Cipher.h @@ -36,6 +36,6 @@ namespace GhettoCipher void ZeroKeyMemory(); // Initial value for cipher block chaining - const Block initializationVector; + Block initializationVector; }; } diff --git a/GhettoCrypt/Feistel.cpp b/GhettoCrypt/Feistel.cpp index 5ce8baa..0f4c81b 100644 --- a/GhettoCrypt/Feistel.cpp +++ b/GhettoCrypt/Feistel.cpp @@ -22,17 +22,17 @@ void GhettoCipher::Feistel::SetKey(const Block& key) return; } -GhettoCipher::Block GhettoCipher::Feistel::Encipher(const Block& data) const +GhettoCipher::Block GhettoCipher::Feistel::Encipher(const Block& data) { return Run(data, false); } -GhettoCipher::Block GhettoCipher::Feistel::Decipher(const Block& data) const +GhettoCipher::Block GhettoCipher::Feistel::Decipher(const Block& data) { return Run(data, true); } -GhettoCipher::Block GhettoCipher::Feistel::Run(const Block& data, bool reverseKeys) const +GhettoCipher::Block GhettoCipher::Feistel::Run(const Block& data, bool reverseKeys) { const auto splitData = FeistelSplit(data); GhettoCipher::Halfblock l = splitData.first; @@ -55,6 +55,10 @@ GhettoCipher::Block GhettoCipher::Feistel::Run(const Block& data, bool reverseKe l = tmp; } + // Block has finished de*ciphering. + // Let's generate a new set of round keys. + GenerateRoundKeys((Block)roundKeys.back()); + return FeistelCombine(r, l); } @@ -75,7 +79,7 @@ GhettoCipher::Halfblock GhettoCipher::Feistel::F(Halfblock m, const Block& key) std::stringstream ss; const std::string m_str = m_expanded.to_string(); - for (std::size_t i = 0; i < m_str.size(); i += 4) + for (std::size_t i = 0; i < BLOCK_SIZE; i += 4) { ss << SBox(m_str.substr(i, 4)); } @@ -113,7 +117,7 @@ GhettoCipher::Block GhettoCipher::Feistel::ExpansionFunction(const Halfblock& bl expansionMap["11"] = "0111"; // We have to double the bits! - for (std::size_t i = 0; i < bits.size(); i += 2) + for (std::size_t i = 0; i < HALFBLOCK_SIZE; i += 2) { const std::string sub = bits.substr(i, 2); ss << expansionMap[sub]; @@ -146,7 +150,7 @@ GhettoCipher::Halfblock GhettoCipher::Feistel::CompressionFunction(const Block& compressionMap["1111"] = "01"; // We have to half the bits! - for (std::size_t i = 0; i < bits.size(); i += 4) + for (std::size_t i = 0; i < BLOCK_SIZE; i += 4) { const std::string sub = bits.substr(i, 4); ss << compressionMap[sub]; @@ -185,19 +189,67 @@ std::string GhettoCipher::Feistel::SBox(const std::string& in) void GhettoCipher::Feistel::GenerateRoundKeys(const Block& seedKey) { - // Generate round keys via output feedback modus (OFM) method - // Clear initial key memory ZeroKeyMemory(); roundKeys = Keyset(); - // Generate new keys from the seed key - roundKeys[0] = seedKey; - roundKeys[1] = (Shiftl(seedKey, 32) ^ roundKeys[0]); + // Derive the initial two round keys + + // Compress- substitute, and expand the seed key to form the initial and the second-initial round key + // This action is non-linear and irreversible, and thus strenghtens security. + Halfblock compressedSeed1 = CompressionFunction(seedKey); + Halfblock compressedSeed2 = CompressionFunction(Shiftl(seedKey, 1)); // Shifting one key by 1 will result in a completely different compression + + // To add further confusion, let's shift seed1 by 1 aswell (after compression, but before substitution) + // but only if the total number of bits set are a multiple of 3 + // if it is a multiple of 4, we'll shift it by 1 into the opposite direction + const std::size_t setBits1 = compressedSeed1.count(); + + if (setBits1 % 4 == 0) + compressedSeed1 = Shiftr(compressedSeed1, 1); + else if (setBits1 % 3 == 0) + compressedSeed1 = Shiftl(compressedSeed1, 1); + + // Now apply substitution + std::stringstream ssKey1; + std::stringstream ssKey2; + const std::string bitsKey1 = compressedSeed1.to_string(); + const std::string bitsKey2 = compressedSeed2.to_string(); + + for (std::size_t i = 0; i < HALFBLOCK_SIZE; i += 4) + { + ssKey1 << SBox(bitsKey1.substr(i, 4)); + ssKey2 << SBox(bitsKey2.substr(i, 4)); + } + + compressedSeed1 = Halfblock(ssKey1.str()); + compressedSeed2 = Halfblock(ssKey2.str()); + + // Now extrapolate them to BLOCK_SIZE (key size) again + // Xor with the original seed key to get rid of the repititions caused by the expansion + roundKeys[0] = ExpansionFunction(compressedSeed1) ^ seedKey; + roundKeys[1] = ExpansionFunction(compressedSeed2) ^ seedKey; + + + // Now derive all other round keys for (std::size_t i = 2; i < roundKeys.size(); i++) { - roundKeys[i] = Shiftl(roundKeys[i - 1], i + 32) ^ roundKeys[i - 2]; + // Initialize new round key with last round key + Block newKey = roundKeys[i - 1]; + + // Shift to left by how many bits are set, modulo 8 + newKey = Shiftl(newKey, newKey.count() % 8); // This action is irreversible + + // Split into two halfblocks, + // apply F() to one halfblock with rk[i-2], + // xor the other one with it + // and put them back together + auto halfkeys = FeistelSplit(newKey); + Halfblock halfkey1 = F(halfkeys.first, roundKeys[i - 2]); + Halfblock halfkey2 = halfkeys.second ^ halfkey1; + + roundKeys[i] = FeistelCombine(halfkey1, halfkey2); } return; diff --git a/GhettoCrypt/Feistel.h b/GhettoCrypt/Feistel.h index 2dbc297..cae2a51 100644 --- a/GhettoCrypt/Feistel.h +++ b/GhettoCrypt/Feistel.h @@ -22,14 +22,14 @@ namespace GhettoCipher void SetKey(const Block& key); //! Will encipher a data block via the set seed-key - Block Encipher(const Block& data) const; + Block Encipher(const Block& data); //! Will decipher a data block via the set seed-key - Block Decipher(const Block& data) const; + Block Decipher(const Block& data); private: //! Will run the feistel rounds, with either regular key order or reversed key order - Block Run(const Block& data, bool reverseKeys) const; + Block Run(const Block& data, bool reverseKeys); //! Arbitrary cipher function static Halfblock F(Halfblock m, const Block& key); diff --git a/GhettoCrypt/GhettoCryptWrapper.cpp b/GhettoCrypt/GhettoCryptWrapper.cpp index 031b88b..b610ae7 100644 --- a/GhettoCrypt/GhettoCryptWrapper.cpp +++ b/GhettoCrypt/GhettoCryptWrapper.cpp @@ -1,11 +1,13 @@ #include "GhettoCryptWrapper.h" #include "Cipher.h" #include "Util.h" +#include std::string GhettoCipher::GhettoCryptWrapper::EncryptString(const std::string& cleartext, const std::string& password) { // Instanciate our cipher and supply a key - Cipher cipher(password); + const Block key = PasswordToKey(password); + Cipher cipher(key); // Recode the ascii-string to bits const Flexblock cleartext_bits = StringToBits(cleartext); @@ -23,7 +25,8 @@ std::string GhettoCipher::GhettoCryptWrapper::EncryptString(const std::string& c std::string GhettoCipher::GhettoCryptWrapper::DecryptString(const std::string& ciphertext, const std::string& password) { // Instanciate our cipher and supply a key - Cipher cipher(password); + const Block key = PasswordToKey(password); + Cipher cipher(key); // Recode the hex-string to bits const Flexblock ciphertext_bits = HexstringToBits(ciphertext); @@ -46,7 +49,8 @@ bool GhettoCipher::GhettoCryptWrapper::EncryptFile(const std::string& filename_i const Flexblock cleartext_bits = ReadFileToBits(filename_in); // Instanciate our cipher and supply a key - Cipher cipher(password); + const Block key = PasswordToKey(password); + Cipher cipher(key); // Encrypt our cleartext bits const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits, printProgressReport); @@ -70,7 +74,8 @@ bool GhettoCipher::GhettoCryptWrapper::DecryptFile(const std::string& filename_i const Flexblock ciphertext_bits = ReadFileToBits(filename_in); // Instanciate our cipher and supply a key - Cipher cipher(password); + const Block key = PasswordToKey(password); + Cipher cipher(key); // Decrypt the ciphertext bits const Flexblock cleartext_bits = cipher.Decipher(ciphertext_bits, printProgressReport); diff --git a/GhettoCrypt/InitializationVector.cpp b/GhettoCrypt/InitializationVector.cpp index c9e4a81..c1b9a8f 100644 --- a/GhettoCrypt/InitializationVector.cpp +++ b/GhettoCrypt/InitializationVector.cpp @@ -2,9 +2,18 @@ #include #include -using namespace GhettoCipher; +// It would be REALLY BAD if another compiler/*version would use +// a mersenne twister with different attrbitutes. It would basically mean +// that E_machine1(M,K) != E_machine2(M,K), which would make them incompatible. +// We do NOT want this to happen, so let's be VERY specific about what mersenne twister setup we want. +// This is std::mt19937, as of msvc stl. +using Prng_MT = std::mersenne_twister_engine< + unsigned int, + 32, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, + 7, 0x9d2c5680, 15,0xefc60000, 18, 1812433253 +>; -InitializationVector::InitializationVector(const Block& seed) +GhettoCipher::InitializationVector::InitializationVector(const Block& seed) { // Since an initialization vector does not have to be a secret, // we should be fine just using a mersenne twister seeded with @@ -12,8 +21,7 @@ InitializationVector::InitializationVector(const Block& seed) // Loosely seed mersenne twister with seed // Here is nothing copied. Both Block::Get, and Hash<>::operator() take refs. - std::mt19937 mt = std::mt19937(std::hash>()(seed.Get())); - + Prng_MT mt = Prng_MT(std::hash>()(seed.Get())); // Now generate BLOCK_SIZE urandom bits std::stringstream ss; for (std::size_t i = 0; i < BLOCK_SIZE; i++) @@ -23,7 +31,7 @@ InitializationVector::InitializationVector(const Block& seed) iv = Block(ss.str()); } -InitializationVector::operator GhettoCipher::Block() const +GhettoCipher::InitializationVector::operator GhettoCipher::Block() const { return iv; } diff --git a/GhettoCrypt/InitializationVector.h b/GhettoCrypt/InitializationVector.h index 959504f..0ae2728 100644 --- a/GhettoCrypt/InitializationVector.h +++ b/GhettoCrypt/InitializationVector.h @@ -2,15 +2,18 @@ #include "Config.h" #include "Block.h" -/** Will create a sudo-random Block based on a seed -*/ -class InitializationVector +namespace GhettoCipher { -public: - InitializationVector(const GhettoCipher::Block& seed); + /** Will create a sudo-random Block based on a seed + */ + class InitializationVector + { + public: + InitializationVector(const GhettoCipher::Block& seed); - operator GhettoCipher::Block() const; + operator GhettoCipher::Block() const; -private: - GhettoCipher::Block iv; -}; + private: + GhettoCipher::Block iv; + }; +} diff --git a/GhettoCrypt/Util.h b/GhettoCrypt/Util.h index a4a8cf4..db60f1b 100644 --- a/GhettoCrypt/Util.h +++ b/GhettoCrypt/Util.h @@ -5,6 +5,7 @@ #include "SecureBitset.h" #include "Block.h" #include "Flexblock.h" +#include "InitializationVector.h" namespace GhettoCipher { @@ -90,8 +91,8 @@ namespace GhettoCipher return Flexblock(ss.str()); } - //! Will convert a fixed-size data block to a string - inline std::string BitblockToString(const Block& bits) + //! Will convert a fixed-size data block to a bytestring + inline std::string BitblockToBytes(const Block& bits) { std::stringstream ss; @@ -102,7 +103,15 @@ namespace GhettoCipher ss << (char)std::bitset<8>(bitstring.substr(i, 8)).to_ulong(); } - std::string text = ss.str(); + return ss.str(); + } + + //! Will convert a fixed-size data block to a string + //! The difference to BitblockToBytes() is, that it strips excess nullbytes + inline std::string BitblockToString(const Block& bits) + { + // Decode to bytes + std::string text = BitblockToBytes(bits); // Dümp excess nullbytes text.resize(strlen(text.data())); @@ -110,8 +119,8 @@ namespace GhettoCipher return text; } - //! Will convert a flexible data block to a string - inline std::string BitsToString(const Flexblock& bits) + //! Will convert a flexible data block to a bytestring + inline std::string BitsToBytes(const Flexblock& bits) { std::stringstream ss; @@ -122,7 +131,15 @@ namespace GhettoCipher ss << (char)std::bitset<8>(bitstring.substr(i, 8)).to_ulong(); } - std::string text = ss.str(); + return ss.str(); + } + + //! Will convert a flexible data block to a string + //! //! The difference to BitsToBytes() is, that it strips excess nullbytes + inline std::string BitsToString(const Flexblock& bits) + { + // Decode to bytes + std::string text = BitsToBytes(bits); // Dümp excess nullbytes text.resize(strlen(text.data())); @@ -212,8 +229,10 @@ namespace GhettoCipher } //! Creates a key of size BLOCK_SIZE from a password of arbitrary length. - //! Using passwords larger (in bits) than BLOCK_SIZE is not generally recommended. - //! Note that if your password is shorter (in bits) than BLOCK_SIZE, the rest of the key will be padded with 0x0. Further round-keys will be extrapolated though. + //! Using passwords larger (in bits) than BLOCK_SIZE is generally not recommended. + //! Note that if your password is shorter (in bits) than BLOCK_SIZE, the rest of the key will be padded with 0 (see next line!). + //! To provide a better initial key, (and to get rid of padding zeroes), the raw result (b) will be xor'd with an initialization vector based on b. + //! : return b ^ iv(b) inline Block PasswordToKey(const std::string& in) { Block b; @@ -224,7 +243,7 @@ namespace GhettoCipher PadStringToLength(in.substr(i, BLOCK_SIZE / 8), BLOCK_SIZE / 8, 0, false) ); - return b; + return b ^ InitializationVector(b); } //! Will read a file into a flexblock @@ -255,7 +274,7 @@ namespace GhettoCipher inline void WriteBitsToFile(const std::string& filepath, const Flexblock& bits) { // Convert bits to bytes - const std::string bytes = BitsToString(bits); + const std::string bytes = BitsToBytes(bits); // Write bits to file std::ofstream ofs(filepath, std::ios::binary); diff --git a/GhettoCrypt/Version.h b/GhettoCrypt/Version.h index 4077bbb..5667d5b 100644 --- a/GhettoCrypt/Version.h +++ b/GhettoCrypt/Version.h @@ -1,2 +1,2 @@ #pragma once -#define GHETTOCRYPT_VERSION 0.13 +#define GHETTOCRYPT_VERSION 0.2 diff --git a/SimpleTests/.gitignore b/SimpleTests/.gitignore new file mode 100644 index 0000000..b71c923 --- /dev/null +++ b/SimpleTests/.gitignore @@ -0,0 +1,2 @@ +/testfile.png.crypt +/testfile.png.clear.png diff --git a/SimpleTests/EncryptEqualsDecrypt.cpp b/SimpleTests/EncryptEqualsDecrypt.cpp index c5e2070..0ea059c 100644 --- a/SimpleTests/EncryptEqualsDecrypt.cpp +++ b/SimpleTests/EncryptEqualsDecrypt.cpp @@ -5,28 +5,31 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace GhettoCipher; +// THESE TESTS ASSUME BLOCK_SIZE == 512 + namespace SimpleTests { TEST_CLASS(EncryptEqualsDecrypt) { public: - // Tests that a decrypted ciphertext equals its plaintrext version - TEST_METHOD(tEncryptEqualsDecrypt) + // Tests that encrypting a message of exactly BLOCK_SIZE yields the exact message back + TEST_METHOD(SingleBlock_NoPadding) { - // Yes, this unit test should ideally exclude string conversions, - // But like this it's easier to see what it's doing - - // Define basic input - const std::string cleartext = "Hello, World!"; - const std::string password = "1234"; - - // Instanciate our cipher and supply a key - const Cipher cipher(password); + const Block key = PasswordToKey("1234"); + const Cipher cipher(key); // Recode the ascii-string to bits - const Flexblock cleartext_bits = StringToBits(cleartext); + const Flexblock cleartext_bits = + "1011110011010110000010110001111000111010111101001010100100011101" + "0101110101010010100000110100001000011000111010001001110101111111" + "1110110101100101110001010101011110001010000010111110011011010111" + "1100110100111000000011100101010100110010001110010011000010111001" + "0000010000010000011001111010011110111001000000000110101000110001" + "0110111110110110100000010100000011010001000011100100111001001011" + "1101100100000100010000001011100010010001101111100100101100010001" + "0000011110010110111010110110111110011110011010001100100111110101"; // Encrypt our cleartext bits const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits); @@ -34,11 +37,194 @@ namespace SimpleTests // Decipher it again const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits); - // Decode it back to ascii - const std::string decryptedText = BitsToString(decryptedBits); + // Assert that the decrypted text equals the plaintext + Assert::AreEqual( + cleartext_bits, + decryptedBits + ); + } + + // Tests that encrypting a message of less than BLOCK_SIZE yields the exact message plus zero-padding back + TEST_METHOD(SingleBlock_Padding) + { + // Instanciate our cipher and supply a key + const Block key = PasswordToKey("1234"); + const Cipher cipher(key); + + // Recode the ascii-string to bits + const Flexblock cleartext_bits = + "1011110011010110000010110001111000111010111101001010100100011101" + "0101110101010010100000110100001000011000111010001001110101111111" + "1110110101100101110001010101011110001010000010111110011011010111" + "1100110100111000000011100101010100110010001110010011000010111001" + "0000010000010000011001111010011110111001000000000110101000110001" + "0110111110110110100000010100000011010001000011100100111001001011" + "1101100100000100"; + + const Flexblock cleartext_bits_EXPECTED_RESULT = + "1011110011010110000010110001111000111010111101001010100100011101" + "0101110101010010100000110100001000011000111010001001110101111111" + "1110110101100101110001010101011110001010000010111110011011010111" + "1100110100111000000011100101010100110010001110010011000010111001" + "0000010000010000011001111010011110111001000000000110101000110001" + "0110111110110110100000010100000011010001000011100100111001001011" + "1101100100000100000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000"; + + // Encrypt our cleartext bits + const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits); + + // Decipher it again + const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits); // Assert that the decrypted text equals the plaintext - Assert::AreEqual(cleartext.length(), decryptedText.length()); + Assert::AreEqual( + cleartext_bits_EXPECTED_RESULT, + decryptedBits + ); + } + + // Tests that a decrypted ciphertext equals its plaintrext version, using a cleartext that requires A LOT of blocks + TEST_METHOD(MultiBlock_NoPadding) + { + // Instanciate our cipher and supply a key + const Block key = PasswordToKey("1234"); + const Cipher cipher(key); + + // Recode the ascii-string to bits + const Flexblock cleartext_bits = + "1011110011010110000010110001111000111010111101001010100100011101" + "0101110101010010100000110100001000011000111010001001110101111111" + "1110110101100101110001010101011110001010000010111110011011010111" + "1100110100111000000011100101010100110010001110010011000010111001" + "0000010000010000011001111010011110111001000000000110101000110001" + "0110111110110110100000010100000011010001000011100100111001001011" + "1101100100000100010000001011100010010001101111100100101100010001" + "0000011110010110111010110110111110011110011010001100100111110101" + "1000010010000000000100101011110001000101101101100000010011111011" + "1011111010110100100111100111110011100001111101111110000110001100" + "0001000111000111101110000111011011101010100010100101100111010100" + "0101111110110010110000111111011001101110101101100100100011000100" + "1000110010101001000100001001101000011111101011111100100000100101" + "1100001100111001011111001101000111011101011101000110010110110110" + "0111001010011010010000010110000110010101101100101110111100100011" + "0010111110011100010100000101100101110101101011110100100000110110" + "1001101110101001001111111000010100011100100000101000111101101111" + "0101111011110001101010111010001000111010101111001101100100100100" + "1110110111001100011010110000101000011001011100101100111101110000" + "1010101111011110000111011011011110000111010110110111111010101010" + "0111100101111001010111101000001010100000111010111100111011111001" + "0110111000000110100011011100101101010101101000010010011111100100" + "0010111000001011101110000110010011101001111010100111110111110101" + "1110111000000000101011000100101010000110110111101010011001111010" + "1101011110001110000011010111001100001100101000000101000101000010" + "0101000011011111010010110010000010101100001110011000110111110111" + "1110010101011110111001100010110101101011100111100011101010001011" + "0101110010100110101100111100010000111101111100000111000110110110" + "1001100111000000011010100000011101011000010010011010001011110000" + "1100100111111001001000011100110000011110001100000000010000001001" + "1110000000110010000010011010100011011011000000000111110000110111" + "0101110011001101010110010100011001110110000110010001100110011111"; + + // Encrypt our cleartext bits + const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits); + + // Decipher it again + const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits); + + // Assert that the decrypted text equals the plaintext + Assert::AreEqual( + cleartext_bits, + decryptedBits + ); + } + + // Tests that a decrypted ciphertext equals its plaintrext version, using a cleartext that requires A LOT of blocks + TEST_METHOD(MultiBlock_Padding) + { + // Instanciate our cipher and supply a key + const Block key = PasswordToKey("1234"); + const Cipher cipher(key); + + // Recode the ascii-string to bits + const Flexblock cleartext_bits = + "1011110011010110000010110001111000111010111101001010100100011101" + "0101110101010010100000110100001000011000111010001001110101111111" + "1110110101100101110001010101011110001010000010111110011011010111" + "1100110100111000000011100101010100110010001110010011000010111001" + "0000010000010000011001111010011110111001000000000110101000110001" + "0110111110110110100000010100000011010001000011100100111001001011" + "1101100100000100010000001011100010010001101111100100101100010001" + "0000011110010110111010110110111110011110011010001100100111110101" + "1000010010000000000100101011110001000101101101100000010011111011" + "1011111010110100100111100111110011100001111101111110000110001100" + "0001000111000111101110000111011011101010100010100101100111010100" + "0101111110110010110000111111011001101110101101100100100011000100" + "1000110010101001000100001001101000011111101011111100100000100101" + "1100001100111001011111001101000111011101011101000110010110110110" + "0111001010011010010000010110000110010101101100101110111100100011" + "0010111110011100010100000101100101110101101011110100100000110110" + "1001101110101001001111111000010100011100100000101000111101101111" + "0101111011110001101010111010001000111010101111001101100100100100" + "1110110111001100011010110000101000011001011100101100111101110000" + "1010101111011110000111011011011110000111010110110111111010101010" + "0111100101111001010111101000001010100000111010111100111011111001" + "0110111000000110100011011100101101010101101000010010011111100100" + "0010111000001011101110000110010011101001111010100111110111110101" + "1110111000000000101011000100101010000110110111101010011001111010" + "1101011110001110000011010111001100001100101000000101000101000010" + "0101000011011111010010110010000010101100001110011000110111110111" + "1110010101011110111001100010110101101011100111100011101010001011" + "0101110010100110101100111100010000111101111100000111000110110110" + "1001100111000000011010100000011101011000010010011010001011110000" + "1100100111111001001000011100110000011110001100000000010000001001" + "11100000001100100000100110101000110110110000000001111100001"; + + const Flexblock cleartext_bits_EXPECTED_RESULT = + "1011110011010110000010110001111000111010111101001010100100011101" + "0101110101010010100000110100001000011000111010001001110101111111" + "1110110101100101110001010101011110001010000010111110011011010111" + "1100110100111000000011100101010100110010001110010011000010111001" + "0000010000010000011001111010011110111001000000000110101000110001" + "0110111110110110100000010100000011010001000011100100111001001011" + "1101100100000100010000001011100010010001101111100100101100010001" + "0000011110010110111010110110111110011110011010001100100111110101" + "1000010010000000000100101011110001000101101101100000010011111011" + "1011111010110100100111100111110011100001111101111110000110001100" + "0001000111000111101110000111011011101010100010100101100111010100" + "0101111110110010110000111111011001101110101101100100100011000100" + "1000110010101001000100001001101000011111101011111100100000100101" + "1100001100111001011111001101000111011101011101000110010110110110" + "0111001010011010010000010110000110010101101100101110111100100011" + "0010111110011100010100000101100101110101101011110100100000110110" + "1001101110101001001111111000010100011100100000101000111101101111" + "0101111011110001101010111010001000111010101111001101100100100100" + "1110110111001100011010110000101000011001011100101100111101110000" + "1010101111011110000111011011011110000111010110110111111010101010" + "0111100101111001010111101000001010100000111010111100111011111001" + "0110111000000110100011011100101101010101101000010010011111100100" + "0010111000001011101110000110010011101001111010100111110111110101" + "1110111000000000101011000100101010000110110111101010011001111010" + "1101011110001110000011010111001100001100101000000101000101000010" + "0101000011011111010010110010000010101100001110011000110111110111" + "1110010101011110111001100010110101101011100111100011101010001011" + "0101110010100110101100111100010000111101111100000111000110110110" + "1001100111000000011010100000011101011000010010011010001011110000" + "1100100111111001001000011100110000011110001100000000010000001001" + "1110000000110010000010011010100011011011000000000111110000100000" + "0000000000000000000000000000000000000000000000000000000000000000"; + + // Encrypt our cleartext bits + const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits); + + // Decipher it again + const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits); + + // Assert that the decrypted text equals the plaintext + Assert::AreEqual( + cleartext_bits_EXPECTED_RESULT, + decryptedBits + ); } }; } diff --git a/SimpleTests/GCWrapper.cpp b/SimpleTests/GCWrapper.cpp new file mode 100644 index 0000000..24da90d --- /dev/null +++ b/SimpleTests/GCWrapper.cpp @@ -0,0 +1,81 @@ +#include "CppUnitTest.h" +#include "../GhettoCrypt/GhettoCryptWrapper.h" +#include "../GhettoCrypt/Flexblock.h" +#include "../GhettoCrypt/Util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace GhettoCipher; + +namespace SimpleTests +{ + TEST_CLASS(GCWrapper) + { + public: + + // Tests that encrypting and decrypting strings using the wrapper works. + // This test will start from scratch after encryption, to ensure EVERYTHING has to be re-calculated. + TEST_METHOD(String) + { + // Setup + const std::string plaintext = "Hello, World!"; + const std::string password = "Der Affe will Zucker"; + + std::string ciphertext; + std::string decrypted; + + // Encryption + { + ciphertext = GhettoCryptWrapper::EncryptString(plaintext, password); + } + + // Decryption + { + decrypted = GhettoCryptWrapper::DecryptString(ciphertext, password); + } + + // Assertion + Assert::AreEqual( + plaintext, + decrypted + ); + } + + // Tests that encrypting and decrypting files using the wrapper works. + // This test will start from scratch after encryption, to ensure EVERYTHING has to be re-calculated. + TEST_METHOD(File) + { + // Setup + #if defined _WIN64 + const std::string testfile_dir = "../../SimpleTests/"; + #elif defined _WIN32 + const std::string testfile_dir = "../SimpleTests/"; + #endif + + const std::string filename_plain = testfile_dir + "testfile.png"; + const std::string filename_encrypted = testfile_dir + "testfile.png.crypt"; + const std::string filename_decrypted = testfile_dir + "testfile.png.clear.png"; + const std::string password = "Der Affe will Zucker"; + + + // Encryption + { + GhettoCryptWrapper::EncryptFile(filename_plain, filename_encrypted, password); + } + + // Decryption + { + GhettoCryptWrapper::DecryptFile(filename_encrypted, filename_decrypted, password); + } + + // Read in both the base, and the decrypted file + const Flexblock plainfile = ReadFileToBits(filename_plain); + const Flexblock decryptfile = ReadFileToBits(filename_decrypted); + + // Assertion (If this fails, maybe check if the image is even readable by an image viewer) + Assert::AreEqual( + PadStringToLength(plainfile, decryptfile.length(), '0', false), + decryptfile + ); + } + }; +} diff --git a/SimpleTests/SimpleTests.vcxproj b/SimpleTests/SimpleTests.vcxproj index 5c1d799..1318a25 100644 --- a/SimpleTests/SimpleTests.vcxproj +++ b/SimpleTests/SimpleTests.vcxproj @@ -157,6 +157,7 @@ + diff --git a/SimpleTests/SimpleTests.vcxproj.filters b/SimpleTests/SimpleTests.vcxproj.filters index 88a0ce6..a66a1ff 100644 --- a/SimpleTests/SimpleTests.vcxproj.filters +++ b/SimpleTests/SimpleTests.vcxproj.filters @@ -18,5 +18,8 @@ Quelldateien + + Quelldateien + \ No newline at end of file diff --git a/SimpleTests/testfile.png b/SimpleTests/testfile.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c97869e8d19d4db554f8c96cc9162ca5d57f04 GIT binary patch literal 9736 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 Uwc3Gmc(sOja9>lYRKYyxfAm