diff --git a/GCryptLib/include/GCrypt/Flexblock.h b/GCryptLib/include/GCrypt/Flexblock.h index 80f9174..5e551b2 100644 --- a/GCryptLib/include/GCrypt/Flexblock.h +++ b/GCryptLib/include/GCrypt/Flexblock.h @@ -4,7 +4,11 @@ #include namespace Leonetienne::GCrypt { - //! A "bitset" of variable length + //! A type used for conveying "bitstrings". e.g. "10101001001" + //! These should generally not be used, as they are really really slow. + //! The only valid usecase I can think of is when using GHash for example, because for hashing + //! an absolute input length is required. + //! If you need to, you can use the StringToBits() and BitsToString() functions defined in Util.h. typedef std::string Flexblock; } diff --git a/GCryptLib/include/GCrypt/Util.h b/GCryptLib/include/GCrypt/Util.h index adb1bf2..8a108f8 100644 --- a/GCryptLib/include/GCrypt/Util.h +++ b/GCryptLib/include/GCrypt/Util.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "GCrypt/Block.h" #include "GCrypt/Flexblock.h" #include "GCrypt/Config.h" @@ -28,12 +29,22 @@ namespace Leonetienne::GCrypt { //! Will convert a string to a flexible data block Flexblock StringToBits(const std::string& s); - //! Will convert a fixed-size data block to a bytestring - std::string BitblockToBytes(const Block& bits); + //! Will convert a string to a vector of blocks + std::vector StringToBitblocks(const std::string& s); - //! Will convert a fixed-size data block to a string + //! Will convert a fixed-size data block to a bytestring + std::string BitblockToBytes(const Block& block); + + //! Will convert an array of data blocks to a bytestring + std::string BitblocksToBytes(const std::vector& bits); + + //! Will convert a fixed-size data blocks to a textstring //! The difference to BitblockToBytes() is, that it strips excess nullbytes - std::string BitblockToString(const Block& bits); + std::string BitblockToString(const Block& block); + + //! Will convert an array of blocks to a character-string + //! The difference to BitblocksToBytes() is, that it strips excess nullbytes + std::string BitblocksToString(const std::vector& blocks); //! Will convert a flexible data block to a bytestring std::string BitsToBytes(const Flexblock& bits); @@ -59,6 +70,12 @@ namespace Leonetienne::GCrypt { //! Will save bits to a binary file void WriteBitsToFile(const std::string& filepath, const Flexblock& bits); + + //! Will read a file directly to data blocks + std::vector ReadFileToBlocks(const std::string& filepath); + + //! Will write data blocks directly to a file + void WriteBlocksToFile(const std::string& filepath, const std::vector& blocks); } #endif diff --git a/GCryptLib/include/GCrypt/Version.h b/GCryptLib/include/GCrypt/Version.h index 655c9f3..171c1f9 100644 --- a/GCryptLib/include/GCrypt/Version.h +++ b/GCryptLib/include/GCrypt/Version.h @@ -1,7 +1,7 @@ #ifndef GCRYPT_VERSION_H #define GCRYPT_VERSION_H -#define GCRYPT_VERSION 0.233 +#define GCRYPT_VERSION 0.234 #endif diff --git a/GCryptLib/src/GWrapper.cpp b/GCryptLib/src/GWrapper.cpp index 76720a3..5cf15ac 100644 --- a/GCryptLib/src/GWrapper.cpp +++ b/GCryptLib/src/GWrapper.cpp @@ -46,14 +46,23 @@ namespace Leonetienne::GCrypt { bool printProgressReport) { try { - // Read the file to bits - const Flexblock cleartext_bits = ReadFileToBits(filename_in); + // Read the file to blocks + const std::vector cleartext_blocks = ReadFileToBlocks(filename_in); - // Encrypt our cleartext bits - const Flexblock ciphertext_bits = CipherFlexblock(cleartext_bits, key, GCipher::DIRECTION::ENCIPHER); + // Encrypt our cleartext blocks + std::vector ciphertext_blocks; + ciphertext_blocks.reserve(cleartext_blocks.size()); - // Write our ciphertext bits to file - WriteBitsToFile(filename_out, ciphertext_bits); + // Create cipher instance + GCipher cipher(key, GCipher::DIRECTION::ENCIPHER); + + // Encrypt all blocks + for (const Block& clearBlock : cleartext_blocks) { + ciphertext_blocks.emplace_back(cipher.Digest(clearBlock)); + } + + // Write our ciphertext blocks to file + WriteBlocksToFile(filename_out, ciphertext_blocks); return true; } @@ -69,14 +78,23 @@ namespace Leonetienne::GCrypt { bool printProgressReport) { try { - // Read the file to bits - const Flexblock ciphertext_bits = ReadFileToBits(filename_in); + // Read the file to blocks + const std::vector ciphertext_blocks = ReadFileToBlocks(filename_in); - // Decrypt the ciphertext bits - const Flexblock cleartext_bits = CipherFlexblock(ciphertext_bits, key, GCipher::DIRECTION::DECIPHER); + // Decrypt our cleartext blocks + std::vector cleartext_blocks; + cleartext_blocks.reserve(ciphertext_blocks.size()); - // Write our cleartext bits to file - WriteBitsToFile(filename_out, cleartext_bits); + // Create cipher instance + GCipher cipher(key, GCipher::DIRECTION::DECIPHER); + + // Decrypt all blocks + for (const Block& cipherBlock : ciphertext_blocks) { + cleartext_blocks.emplace_back(cipher.Digest(cipherBlock)); + } + + // Write our cleartext blocks to file + WriteBlocksToFile(filename_out, cleartext_blocks); return true; } diff --git a/GCryptLib/src/Key.cpp b/GCryptLib/src/Key.cpp index 97ecb4d..a6d80da 100644 --- a/GCryptLib/src/Key.cpp +++ b/GCryptLib/src/Key.cpp @@ -19,17 +19,14 @@ namespace Leonetienne::GCrypt { std::random_device rng; constexpr std::size_t bitsPerCall = sizeof(std::random_device::result_type) * 8; - // Fetch BLOCK_SIZE bits - std::stringstream ss; - for (std::size_t i = 0; i < Key::BLOCK_SIZE_BITS / bitsPerCall; i++) { - ss << std::bitset(rng()); + // Create a new key, and assign 16 random values + Key key; + for (std::size_t i = 0; i < 16; i++) { + key[i] = rng(); } - // Verify that we actually have the correct size - assert(ss.str().length() == Key::BLOCK_SIZE_BITS); - - // Return them as a key - return Key(Block(ss.str())); + // Return it + return key; } Key Key::LoadFromFile(const std::string& path) { @@ -44,36 +41,23 @@ namespace Leonetienne::GCrypt { throw std::runtime_error(std::string("Unable to open ifilestream for keyfile \"") + path + "\"! Aborting..."); } - // Read these chars to buffer - char* ckeyfileContent = new char[maxChars]; - memset(ckeyfileContent, 0, maxChars * sizeof(char)); - ifs.read(ckeyfileContent, maxChars); - ifs.close(); + // Create a new key, and zero it + Key key; + key.Reset(); - // Convert the buffer to a bit block of key size - std::stringstream ss; - for (std::size_t i = 0; i < maxChars; i++) - ss << std::bitset<8>(ckeyfileContent[i]); - - Block key(ss.str()); - - // And delete the buffer - delete[] ckeyfileContent; - ckeyfileContent = nullptr; + // Read into it + ifs.read((char*)(void*)key.Data(), Key::BLOCK_SIZE); // Return it return key; } void Key::WriteToFile(const std::string& path) { - // Transform key to bytes - const std::string keybytes = BitsToBytes(ToString()); - // Create an ofilestream std::ofstream ofs(path, std::ios::out | std::ios::binary); // Write the key - ofs.write(keybytes.data(), Key::BLOCK_SIZE_BITS / 8); + ofs.write((char*)(void*)Data(), Key::BLOCK_SIZE); // Close the file handle ofs.close(); diff --git a/GCryptLib/src/Util.cpp b/GCryptLib/src/Util.cpp index 3caaa06..2dcd0ee 100644 --- a/GCryptLib/src/Util.cpp +++ b/GCryptLib/src/Util.cpp @@ -1,5 +1,6 @@ #include "GCrypt/Util.h" #include "GCrypt/GHash.h" +#include namespace Leonetienne::GCrypt { @@ -50,13 +51,22 @@ namespace Leonetienne::GCrypt { return Flexblock(ss.str()); } - std::string BitblockToBytes(const Block& bits) { + std::string BitblockToBytes(const Block& block) { std::stringstream ss; - const std::string bitstring = bits.ToString(); + std::uint8_t* curByte = (std::uint8_t*)(void*)block.Data(); + for (std::size_t j = 0; j < Block::BLOCK_SIZE; j++) { + ss << *curByte++; + } - for (std::size_t i = 0; i < Block::BLOCK_SIZE_BITS; i += 8) { - ss << (char)std::bitset<8>(bitstring.substr(i, 8)).to_ulong(); + return ss.str(); + } + + std::string BitblocksToBytes(const std::vector& blocks) { + std::stringstream ss; + + for (const Block& block : blocks) { + ss << BitblockToBytes(block); } return ss.str(); @@ -72,6 +82,16 @@ namespace Leonetienne::GCrypt { return text; } + std::string BitblocksToString(const std::vector& blocks) { + // Decode to bytes + std::string text = BitblocksToBytes(blocks); + + // Dümp excess nullbytes + text.resize(strlen(text.data())); + + return text; + } + std::string BitsToBytes(const Flexblock& bits) { std::stringstream ss; @@ -211,5 +231,113 @@ namespace Leonetienne::GCrypt { return; } + + std::vector ReadFileToBlocks(const std::string& filepath) { + // Read file + + // "ate" specifies that the read-pointer is already at the end of the file + // this allows to estimate the file size + std::ifstream ifs(filepath, std::ios::binary | std::ios::ate); + + if (!ifs.good()) { + throw std::runtime_error("Unable to open ifilestream!"); + } + + // Create our vector of blocks, and resorve a good guess + // of memory + std::vector blocks; + blocks.reserve((ifs.tellg() / Block::BLOCK_SIZE) + 1); + + // Move read head to the file beginning + ifs.seekg(std::ios_base::beg); + + // Whilst not reached eof, read into blocks + while (!ifs.eof()) { + // Create a new block, and zero it + Block block; + block.Reset(); + + // Read data into the block + ifs.read((char*)(void*)block.Data(), Block::BLOCK_SIZE); + const std::size_t n_bytes_read = ifs.gcount(); + + if (n_bytes_read > 0) { + // Append the block to our vector + blocks.emplace_back(block); + } + } + + // Close the filehandle + ifs.close(); + + return blocks; + } + + void WriteBlocksToFile( + const std::string& filepath, + const std::vector& blocks + ){ + + // Create outfile file handle + std::ofstream ofs(filepath, std::ios::binary); + + if (!ofs.good()) { + throw std::runtime_error("Unable to open ofilestream!"); + } + + // Write all the blocks + for (const Block& block : blocks) { + ofs.write((char*)(void*)block.Data(), Block::BLOCK_SIZE); + } + + // Close the filehandle + ofs.close(); + + return; + } + + std::vector StringToBitblocks(const std::string& s) { + + // Create our block vector, and reserve exactly + // how many blocks are required to store this string + const std::size_t num_blocks = (s.length() / Block::BLOCK_SIZE) + 1; + std::vector blocks; + blocks.reserve(num_blocks); + + for (std::size_t i = 0; i < num_blocks; i++) { + // Create new block, and zero it + Block block; + block.Reset(); + + std::size_t bytes_copied = 0; + + // Iterate over all bytes in the block + std::uint8_t* curByte = (std::uint8_t*)(void*)block.Data(); + for (std::size_t j = 0; j < Block::BLOCK_SIZE; j++) { + curByte++; + + // Carry our character over + + const std::size_t strIdx = i*Block::BLOCK_SIZE + j; + // The string still has chars to give + if (strIdx < s.length()) { + *curByte = s[j]; + bytes_copied++; + } + // We've reached the end of the string + else { + // Save our block, if it contains any bytes + if (bytes_copied) { + blocks.emplace_back(block); + } + + // Return our blocks + return blocks; + } + } + } + + return blocks; + } } diff --git a/GCryptLib/test/GCWrapper.cpp b/GCryptLib/test/GCWrapper.cpp index e0d228c..ef59209 100644 --- a/GCryptLib/test/GCWrapper.cpp +++ b/GCryptLib/test/GCWrapper.cpp @@ -46,13 +46,11 @@ TEST_CASE(__FILE__"/Encrypting and decrypting files works", "[Wrapper]") { GWrapper::DecryptFile(filename_encrypted, filename_decrypted, key); // Read in both the base, and the decrypted file - const Flexblock plainfile = ReadFileToBits(filename_plain); - const Flexblock decryptfile = ReadFileToBits(filename_decrypted); + const std::vector plainfile = ReadFileToBlocks(filename_plain); + const std::vector decryptfile = ReadFileToBlocks(filename_decrypted); // Assertion (If this fails, maybe check if the image is even readable by an image viewer) - REQUIRE( - PadStringToLength(plainfile, decryptfile.length(), '0', false) == - decryptfile - ); + REQUIRE(plainfile.size() == decryptfile.size()); + REQUIRE(plainfile == decryptfile); }