From 0b800f988a5e45df0bbfc911c9f853e2e7b29778 Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Sun, 6 Feb 2022 22:59:24 +0100 Subject: [PATCH] compiled single-header --- INCLUDE/GhettoCrypt.cpp | 123 ++++++++++++++++++++++------ INCLUDE/GhettoCrypt.h | 176 ++++++++++++++++++++++++++-------------- 2 files changed, 212 insertions(+), 87 deletions(-) diff --git a/INCLUDE/GhettoCrypt.cpp b/INCLUDE/GhettoCrypt.cpp index ff1815a..97a00b3 100644 --- a/INCLUDE/GhettoCrypt.cpp +++ b/INCLUDE/GhettoCrypt.cpp @@ -35,16 +35,18 @@ GhettoCipher::Cipher::Cipher(const Block& key) : - key { key } + key { key }, + initializationVector(InitializationVector(key)) { return; } GhettoCipher::Cipher::Cipher(const std::string& password) + : + key { PasswordToKey(password) }, + initializationVector(InitializationVector(key)) { - key = PasswordToKey(password); - return; } @@ -91,8 +93,8 @@ GhettoCipher::Flexblock GhettoCipher::Cipher::Encipher(const Flexblock& data, bo if ((i % ((blocks.size() > 1000)? 100 : 10) == 0) && (printProgress)) std::cout << "Encrypting... (Block " << i << " / " << blocks.size() << " - " << ((float)i*100 / blocks.size()) << "%)" << std::endl; - const Block& lastBlock = (i>0) ? blocks[i-1] : emptyBlock; - blocks[i] = feistel.Encipher(blocks[i] ^ lastBlock); + const Block& lastBlock = (i>0) ? blocks[i-1] : initializationVector; + 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 @@ -118,7 +120,7 @@ GhettoCipher::Flexblock GhettoCipher::Cipher::Decipher(const Flexblock& data, bo Feistel feistel(key); // We can't do this in-loop for decryption, because we are decrypting the blocks in-place. - Block lastBlock = emptyBlock; + Block lastBlock = initializationVector; for (std::size_t i = 0; i < blocks.size(); i++) { @@ -128,7 +130,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); } @@ -159,8 +161,6 @@ void GhettoCipher::Cipher::ZeroKeyMemory() #elif defined __GNUG__ #pragma GCC pop_options #endif - -const GhettoCipher::Block GhettoCipher::Cipher::emptyBlock; /*** ./../GhettoCrypt/Feistel.cpp ***/ @@ -186,17 +186,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; @@ -219,6 +219,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); } @@ -239,7 +243,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)); } @@ -277,7 +281,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]; @@ -310,7 +314,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]; @@ -349,19 +353,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; @@ -394,7 +446,8 @@ void GhettoCipher::Feistel::ZeroKeyMemory() 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); @@ -412,7 +465,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); @@ -435,7 +489,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); @@ -459,7 +514,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); @@ -475,3 +531,20 @@ bool GhettoCipher::GhettoCryptWrapper::DecryptFile(const std::string& filename_i } } + +/*** ./../GhettoCrypt/InitializationVector.cpp ***/ + +#include + +GhettoCipher::InitializationVector::InitializationVector(const Block& seed) +{ + // We'll generate our initialization vector by encrypting our seed with itself as a key + // iv = E(M=seed, K=seed) + iv = Feistel(seed).Encipher(seed); +} + +GhettoCipher::InitializationVector::operator GhettoCipher::Block() const +{ + return iv; +} + diff --git a/INCLUDE/GhettoCrypt.h b/INCLUDE/GhettoCrypt.h index be046d4..e245bd3 100644 --- a/INCLUDE/GhettoCrypt.h +++ b/INCLUDE/GhettoCrypt.h @@ -28,10 +28,41 @@ #pragma once -/*** ./../GhettoCrypt/Version.h ***/ +/*** ./../GhettoCrypt/GhettoCryptWrapper.h ***/ #pragma once -#define GHETTOCRYPT_VERSION 0.13 +#include + +namespace GhettoCipher +{ + /** This class is a wrapper to make working with the GhettoCipher super easy with a python-like syntax + */ + class GhettoCryptWrapper + { + public: + //! Will encrypt a string and return it hexadecimally encoded. + static std::string EncryptString(const std::string& cleartext, const std::string& password); + + //! Will decrypt a hexadecimally encoded string. + static std::string DecryptString(const std::string& ciphertext, const std::string& password); + + //! Will encrypt a file. + //! Returns false if anything goes wrong (like, file-access). + //! @filename_in The file to be read. + //! @filename_out The file the encrypted version should be saved in. + static bool EncryptFile(const std::string& filename_in, const std::string& filename_out, const std::string& password, bool printProgressReport = false); + + //! Will decrypt a file. + //! Returns false if anything goes wrong (like, file-access). + //! @filename_in The file to be read. + //! @filename_out The file the decrypted version should be saved in. + static bool DecryptFile(const std::string& filename_in, const std::string& filename_out, const std::string& password, bool printProgressReport = false); + + private: + // No instanciation! >:( + GhettoCryptWrapper(); + }; +} /*** ./../GhettoCrypt/Flexblock.h ***/ @@ -47,14 +78,22 @@ namespace GhettoCipher /*** ./../GhettoCrypt/Config.h ***/ #pragma once -#include +#include namespace GhettoCipher { + // MUST BE A POWER OF 2 > 4 constexpr std::size_t BLOCK_SIZE = 512; + + // MUST BE > 2 constexpr std::size_t N_ROUNDS = 64; } +/*** ./../GhettoCrypt/Version.h ***/ + +#pragma once +#define GHETTOCRYPT_VERSION 0.21 + /*** ./../GhettoCrypt/SecureBitset.h ***/ #pragma once @@ -382,6 +421,17 @@ inline std::istream& operator>>(std::istream& ifs, const SecureBitset& bs) } } +/*** ./../GhettoCrypt/Halfblock.h ***/ + +#pragma once +#include + +namespace GhettoCipher +{ + constexpr std::size_t HALFBLOCK_SIZE = (BLOCK_SIZE / 2); + typedef SecureBitset Halfblock; +} + /*** ./../GhettoCrypt/Block.h ***/ #pragma once @@ -391,12 +441,33 @@ namespace GhettoCipher typedef SecureBitset Block; } +/*** ./../GhettoCrypt/InitializationVector.h ***/ + +#pragma once + +namespace GhettoCipher +{ + /** Will create a sudo-random Block based on a seed + */ + class InitializationVector + { + public: + InitializationVector(const GhettoCipher::Block& seed); + + operator GhettoCipher::Block() const; + + private: + GhettoCipher::Block iv; + }; +} + /*** ./../GhettoCrypt/Util.h ***/ #pragma once #include #include #include +#include namespace GhettoCipher { @@ -482,8 +553,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; @@ -496,9 +567,22 @@ namespace GhettoCipher 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); - //! Will convert a flexible data block to a string - inline std::string BitsToString(const Flexblock& bits) + // Dümp excess nullbytes + text.resize(strlen(text.data())); + + return text; + } + + //! Will convert a flexible data block to a bytestring + inline std::string BitsToBytes(const Flexblock& bits) { std::stringstream ss; @@ -512,6 +596,19 @@ namespace GhettoCipher 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())); + + return text; + } + //! Turns a fixed-size data block into a hex-string inline std::string BitblockToHexstring(const Block& b) { @@ -594,8 +691,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; @@ -606,7 +705,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 @@ -637,7 +736,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); @@ -652,42 +751,6 @@ namespace GhettoCipher } } -/*** ./../GhettoCrypt/GhettoCryptWrapper.h ***/ - -#pragma once -#include - -namespace GhettoCipher -{ - /** This class is a wrapper to make working with the GhettoCipher super easy with a python-like syntax - */ - class GhettoCryptWrapper - { - public: - //! Will encrypt a string and return it hexadecimally encoded. - static std::string EncryptString(const std::string& cleartext, const std::string& password); - - //! Will decrypt a hexadecimally encoded string. - static std::string DecryptString(const std::string& ciphertext, const std::string& password); - - //! Will encrypt a file. - //! Returns false if anything goes wrong (like, file-access). - //! @filename_in The file to be read. - //! @filename_out The file the encrypted version should be saved in. - static bool EncryptFile(const std::string& filename_in, const std::string& filename_out, const std::string& password, bool printProgressReport = false); - - //! Will decrypt a file. - //! Returns false if anything goes wrong (like, file-access). - //! @filename_in The file to be read. - //! @filename_out The file the decrypted version should be saved in. - static bool DecryptFile(const std::string& filename_in, const std::string& filename_out, const std::string& password, bool printProgressReport = false); - - private: - // No instanciation! >:( - GhettoCryptWrapper(); - }; -} - /*** ./../GhettoCrypt/Keyset.h ***/ #pragma once @@ -698,17 +761,6 @@ namespace GhettoCipher typedef std::array Keyset; } -/*** ./../GhettoCrypt/Halfblock.h ***/ - -#pragma once -#include - -namespace GhettoCipher -{ - constexpr std::size_t HALFBLOCK_SIZE = (BLOCK_SIZE / 2); - typedef SecureBitset Halfblock; -} - /*** ./../GhettoCrypt/Feistel.h ***/ #pragma once @@ -732,14 +784,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); @@ -807,6 +859,6 @@ namespace GhettoCipher void ZeroKeyMemory(); // Initial value for cipher block chaining - static const Block emptyBlock; + Block initializationVector; }; }