diff --git a/GCryptLib/include/GCrypt/Block.h b/GCryptLib/include/GCrypt/Block.h index 032ebfc..a112fcc 100644 --- a/GCryptLib/include/GCrypt/Block.h +++ b/GCryptLib/include/GCrypt/Block.h @@ -26,12 +26,31 @@ namespace Leonetienne::GCrypt { ~Basic_Block(); - //! Will construct this block from a string like "011101..". Length MUST be 512. - void FromString(const std::string& str); + //! Will construct this block from a string like "011101..". + void FromBinaryString(const std::string& str); + + //! Will construct this block from a hexstring + void FromHexString(const std::string& str); + + //! Will construct this block from a bytestring (any characters) + void FromByteString(const std::string& str); + + //! Will construct this block from a textstring (any length) + void FromTextString(const std::string& str); //! Will create a bitset-compatible string ("0101110..") representation //! of this block. Length will always be 512. - std::string ToString() const; + std::string ToBinaryString() const; + + //! Will create a hexstring representation of this block. + std::string ToHexString() const; + + //! Will create a bytestring representation of this block. + std::string ToByteString() const; + + //! Will create a textstring representation of this block. + //! The difference to a bytestring is that it gets trimmed after a nullterminator. + std::string ToTextString() const; //! Will matrix-multiply two blocks together. //! Since the matrices values are pretty much sudo-random, diff --git a/GCryptLib/include/GCrypt/GHash.h b/GCryptLib/include/GCrypt/GHash.h index f962529..4cf6080 100644 --- a/GCryptLib/include/GCrypt/GHash.h +++ b/GCryptLib/include/GCrypt/GHash.h @@ -4,6 +4,7 @@ #include "GCrypt/Flexblock.h" #include "GCrypt/Block.h" #include "GCrypt/GCipher.h" +#include namespace Leonetienne::GCrypt { /** This class implements a hash function, based on the GCrypt cipher @@ -21,8 +22,14 @@ namespace Leonetienne::GCrypt { //! Will return the current hashsum const Block& GetHashsum() const; - //! Will calculate a hashsum for `data`. - static Block CalculateHashsum(const Flexblock& data); + //! Will calculate a hashsum for `blocks`. + //! Whilst n_bytes is optional, it is HIGHLY recommended to supply. + //! Without specifying the size of the input (doesn't always have to be 512*n bits) + //! b'293eff' would hash to the exact same values as b'293eff0000' + static Block CalculateHashsum(const std::vector& blocks, std::size_t n_bytes = std::string::npos); + + //! Will calculate a hashsum for a string + static Block HashString(const std::string& str); void operator=(const GHash& other); diff --git a/GCryptLib/include/GCrypt/Util.h b/GCryptLib/include/GCrypt/Util.h index 8a108f8..1d9dce7 100644 --- a/GCryptLib/include/GCrypt/Util.h +++ b/GCryptLib/include/GCrypt/Util.h @@ -30,7 +30,7 @@ namespace Leonetienne::GCrypt { Flexblock StringToBits(const std::string& s); //! Will convert a string to a vector of blocks - std::vector StringToBitblocks(const std::string& s); + std::vector StringToBitblocks(const std::string& str); //! Will convert a fixed-size data block to a bytestring std::string BitblockToBytes(const Block& block); @@ -71,6 +71,9 @@ 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, and yield the amount of bytes read + std::vector ReadFileToBlocks(const std::string& filepath, std::size_t& bytes_read); + //! Will read a file directly to data blocks std::vector ReadFileToBlocks(const std::string& filepath); diff --git a/GCryptLib/src/Block.cpp b/GCryptLib/src/Block.cpp index 3a83d2c..3e3ef9d 100644 --- a/GCryptLib/src/Block.cpp +++ b/GCryptLib/src/Block.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include // Just to be sure, the compiler will optimize this // little formula out, let's do it in the preprocessor @@ -17,7 +19,7 @@ namespace Leonetienne::GCrypt { template Basic_Block::Basic_Block(const std::string& str) { - FromString(str); + FromBinaryString(str); } template @@ -26,9 +28,13 @@ namespace Leonetienne::GCrypt { } template - void Basic_Block::FromString(const std::string& str) { + void Basic_Block::FromBinaryString(const std::string& str) { - assert(str.length() == BLOCK_SIZE_BITS); + if (str.length() != BLOCK_SIZE_BITS) { + throw std::invalid_argument( + std::string("Unable to read binary block: \"") + str + "\": Length is not BLOCK_SIZE_BITS." + ); + } for (std::size_t i = 0; i < data.size(); i++) { data[i] = std::bitset( @@ -40,7 +46,58 @@ namespace Leonetienne::GCrypt { } template - std::string Basic_Block::ToString() const { + void Basic_Block::FromHexString(const std::string& str) { + + if (str.length() != BLOCK_SIZE*2) { + throw std::invalid_argument( + std::string("Unable to read hex block: \"") + str + "\": Length is not BLOCK_SIZE*2." + ); + } + + for (std::size_t i = 0; i < str.length(); i += CHUNK_SIZE*2) { + const std::string hexChunk = str.substr(i, CHUNK_SIZE*2); + try { + data[i / (CHUNK_SIZE*2)] = std::stoul(hexChunk, NULL, 16); + } + catch (std::invalid_argument&) { + throw std::invalid_argument( + std::string("Unable to read hex block: \"") + hexChunk + "\"." + ); + } + } + + return; + } + + template + void Basic_Block::FromByteString(const std::string& str) { + + if (str.length() != BLOCK_SIZE) { + throw std::invalid_argument( + std::string("Unable to read byte block: \"") + str + "\": Length is not BLOCK_SIZE." + ); + } + + // Iterate over all bytes in the block + std::uint8_t* curByte = (std::uint8_t*)(void*)Data(); + const char* strIt = 0; + for (std::size_t i = 0; i < BLOCK_SIZE; i++) { + *curByte++ = str[i]; + } + + return; + } + + template + void Basic_Block::FromTextString(const std::string& str) { + // Just pad the input string to lenght, and treat it as a byte string + FromByteString( + PadStringToLength(str, BLOCK_SIZE, '\0', false) + ); + } + + template + std::string Basic_Block::ToBinaryString() const { std::stringstream ss; for (std::size_t i = 0; i < data.size(); i++) { @@ -49,6 +106,41 @@ namespace Leonetienne::GCrypt { return ss.str(); } + template + std::string Basic_Block::ToHexString() const { + + std::stringstream ss; + for (std::size_t i = 0; i < data.size(); i++) { + ss + << std::setfill('0') + << std::setw(CHUNK_SIZE*2) + << std::hex + << data[i] + ; + } + + return ss.str(); + } + + template + std::string Basic_Block::ToByteString() const { + + std::stringstream ss; + ss.write((const char*)(void*)Data(), BLOCK_SIZE); + return ss.str(); + } + + template + std::string Basic_Block::ToTextString() const { + + std::string bytes = ToByteString(); + + // Trim extra nullterminators + bytes.resize(strlen(bytes.data())); + + return bytes; + } + template Basic_Block Basic_Block::MMul(const Basic_Block& o) const { diff --git a/GCryptLib/src/GHash.cpp b/GCryptLib/src/GHash.cpp index 9d1bc44..24a3614 100644 --- a/GCryptLib/src/GHash.cpp +++ b/GCryptLib/src/GHash.cpp @@ -31,34 +31,55 @@ namespace Leonetienne::GCrypt { return block; } - Block GHash::CalculateHashsum(const Flexblock& data) { - // Split input into blocks - std::vector blocks; + Block GHash::CalculateHashsum(const std::vector& data, std::size_t n_bytes) { - for (std::size_t i = 0; i < data.size(); i += Block::BLOCK_SIZE_BITS) { - blocks.push_back(Block( - PadStringToLength(data.substr(i, Block::BLOCK_SIZE_BITS), Block::BLOCK_SIZE_BITS, '0', false)) - ); + // If we have no supplied n_bytes, let's just assume sizeof(data). + if (n_bytes == std::string::npos) { + n_bytes = data.size() * Block::BLOCK_SIZE; } - // Add an additional block, containing the length of the input - std::stringstream ss; - ss << data.length(); - const Block lengthBlock = StringToBitblock(ss.str()); - blocks.push_back(lengthBlock); - // Create hasher instance GHash hasher; // Digest all blocks - for (Block& block : blocks) { + for (const Block& block : data) { hasher.DigestBlock(block); } + // Add an additional block, containing the length of the input + + // Here it is actually good to use a binary string ("10011"), + // because std::size_t is not fixed to 32-bits. It may aswell + // be 64 bits, depending on the platform. + // Then it would be BAD to just cram it into a 32-bit uint32. + // This way, in case of 64-bits, it would just occupy 2 uint32's. + + // Also, this operation gets done ONCE per n blocks. This won't + // hurt performance. + + // I know that we are first converting n_bytes to str(n_bytes), + // and then converting this to a binstring, making it unnecessarily large, + // but who cares. It has a whole 512 bit block to itself. + // The max size (2^64) would occupy 155 bits at max. (log10(2^64)*8 = 155) + + std::stringstream ss; + ss << n_bytes; + const Block lengthBlock = StringToBitblock(ss.str()); + + // Digest the length block + hasher.DigestBlock(lengthBlock); + // Return the total hashsum return hasher.GetHashsum(); } + Block GHash::HashString(const std::string& str) { + const std::vector blocks = StringToBitblocks(str); + const std::size_t n_bytes = str.length(); + + return CalculateHashsum(blocks, n_bytes); + } + void GHash::operator=(const GHash& other) { cipher = other.cipher; diff --git a/GCryptLib/src/GPrng.cpp b/GCryptLib/src/GPrng.cpp index b1731bf..524192c 100644 --- a/GCryptLib/src/GPrng.cpp +++ b/GCryptLib/src/GPrng.cpp @@ -78,7 +78,7 @@ namespace Leonetienne::GCrypt { // Performance improvement over the previous method: // (generating 1.000.000 integers): - // 5.26 seconds -> 3.84 seconds + // 5.26 seconds -> 3.75 seconds // Advance our pointer to the next whole uint32 diff --git a/GCryptLib/src/GWrapper.cpp b/GCryptLib/src/GWrapper.cpp index 5cf15ac..ced5957 100644 --- a/GCryptLib/src/GWrapper.cpp +++ b/GCryptLib/src/GWrapper.cpp @@ -10,33 +10,64 @@ namespace Leonetienne::GCrypt { const Key& key) { // Recode the ascii-string to bits - const Flexblock cleartext_bits = StringToBits(cleartext); + const std::vector cleartext_blocks = StringToBitblocks(cleartext); - // Encrypt our cleartext bits - const Flexblock ciphertext_bits = CipherFlexblock(cleartext_bits, key, GCipher::DIRECTION::ENCIPHER); + // Create cipher instance + GCipher cipher(key, GCipher::DIRECTION::ENCIPHER); - // Recode the ciphertext bits to a hex-string - const std::string ciphertext = BitsToHexstring(ciphertext_bits); + // Encrypt all blocks + std::vector ciphertext_blocks; + + for (const Block& clearBlock : cleartext_blocks) { + ciphertext_blocks.emplace_back(cipher.Digest(clearBlock)); + } + + // Recode the ciphertext blocks to a hex-string + std::stringstream ss; + for (const Block& block : ciphertext_blocks) { + ss << block.ToHexString(); + } // Return it - return ciphertext; + return ss.str(); } std::string GWrapper::DecryptString( const std::string& ciphertext, const Key& key) { - // Recode the hex-string to bits - const Flexblock ciphertext_bits = HexstringToBits(ciphertext); + // Make sure our ciphertext is a multiple of block size + if (ciphertext.length() % Block::BLOCK_SIZE*2 != 0) { // Two chars per byte + throw std::runtime_error("Leonetienne::GCrypt::GWrapper::DecryptString() received ciphertext of length not a multiple of block size."); + } - // Decrypt the ciphertext bits - const std::string cleartext_bits = CipherFlexblock(ciphertext_bits, key, GCipher::DIRECTION::DECIPHER); + // Recode the hex-string to blocks + std::vector ciphertext_blocks; + ciphertext_blocks.reserve(ciphertext.length() / (Block::BLOCK_SIZE*2)); + for (std::size_t i = 0; i < ciphertext.length(); i += Block::BLOCK_SIZE*2) { + Block block; + block.FromHexString(ciphertext.substr(i, Block::BLOCK_SIZE*2)); - // Recode the cleartext bits to an ascii-string - const std::string cleartext = BitsToString(cleartext_bits); + ciphertext_blocks.emplace_back(block); + } + + // Create cipher instance + GCipher cipher(key, GCipher::DIRECTION::DECIPHER); + + // Decrypt all blocks + std::vector cleartext_blocks; + for (const Block& cipherBlock : ciphertext_blocks) { + cleartext_blocks.emplace_back(cipher.Digest(cipherBlock)); + } + + // Recode the cleartext blocks to bytes + std::stringstream ss; + for (const Block& block : cleartext_blocks) { + ss << block.ToTextString(); + } // Return it - return cleartext; + return ss.str(); } bool GWrapper::EncryptFile( diff --git a/GCryptLib/src/Key.cpp b/GCryptLib/src/Key.cpp index f129c03..0dc23b1 100644 --- a/GCryptLib/src/Key.cpp +++ b/GCryptLib/src/Key.cpp @@ -9,9 +9,7 @@ namespace Leonetienne::GCrypt { Key Key::FromPassword(const std::string& password) { - return GHash::CalculateHashsum( - StringToBits(password) - ); + return GHash::HashString(password); } Key Key::Random() { diff --git a/GCryptLib/src/Util.cpp b/GCryptLib/src/Util.cpp index 2dcd0ee..83d412b 100644 --- a/GCryptLib/src/Util.cpp +++ b/GCryptLib/src/Util.cpp @@ -117,7 +117,7 @@ namespace Leonetienne::GCrypt { std::string BitblockToHexstring(const Block& b) { std::stringstream ss; const std::string charset = "0123456789abcdef"; - const std::string bstr = b.ToString(); + const std::string bstr = b.ToBinaryString(); for (std::size_t i = 0; i < bstr.size(); i += 4) { ss << charset[std::bitset<4>(bstr.substr(i, 4)).to_ulong()]; @@ -232,8 +232,9 @@ namespace Leonetienne::GCrypt { return; } - std::vector ReadFileToBlocks(const std::string& filepath) { + std::vector ReadFileToBlocks(const std::string& filepath, std::size_t& bytes_read) { // Read file + bytes_read = 0; // "ate" specifies that the read-pointer is already at the end of the file // this allows to estimate the file size @@ -259,9 +260,10 @@ namespace Leonetienne::GCrypt { // Read data into the block ifs.read((char*)(void*)block.Data(), Block::BLOCK_SIZE); - const std::size_t n_bytes_read = ifs.gcount(); + const std::size_t n_bytes_read_block = ifs.gcount(); + bytes_read += n_bytes_read_block; - if (n_bytes_read > 0) { + if (n_bytes_read_block > 0) { // Append the block to our vector blocks.emplace_back(block); } @@ -273,6 +275,11 @@ namespace Leonetienne::GCrypt { return blocks; } + std::vector ReadFileToBlocks(const std::string& filepath) { + std::size_t bytes_read_dummy; // Create a dumme for the parameter + return ReadFileToBlocks(filepath, bytes_read_dummy); + } + void WriteBlocksToFile( const std::string& filepath, const std::vector& blocks @@ -296,45 +303,19 @@ namespace Leonetienne::GCrypt { return; } - std::vector StringToBitblocks(const std::string& s) { + std::vector StringToBitblocks(const std::string& str) { // 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; + const std::size_t num_blocks = (str.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 + for (std::size_t i = 0; i < str.length(); i += Block::BLOCK_SIZE) { Block block; - block.Reset(); + block.FromTextString(str.substr(i, Block::BLOCK_SIZE)); - 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; - } - } + blocks.emplace_back(block); } return blocks; diff --git a/GCryptLib/test/Block.cpp b/GCryptLib/test/Block.cpp index 7ac92dc..f643ccd 100644 --- a/GCryptLib/test/Block.cpp +++ b/GCryptLib/test/Block.cpp @@ -62,8 +62,8 @@ TEST_CASE(__FILE__"/operator=", "[Block]") { } } -// Tests that converting to, and from, strings works -TEST_CASE(__FILE__"/StringConversion", "[Block]") { +// Tests that converting to, and from, binary strings works +TEST_CASE(__FILE__"/BinaryStringConversion", "[Block]") { // Setup srand(time(0)); @@ -77,7 +77,60 @@ TEST_CASE(__FILE__"/StringConversion", "[Block]") { Block block(ss.str()); // Verify - REQUIRE(block.ToString() == ss.str()); + REQUIRE(block.ToBinaryString() == ss.str()); +} + +// Tests that converting to, and from, hexstrings works +TEST_CASE(__FILE__"/HexStringConversion", "[Block]") { + + // Setup + srand(time(0)); + std::stringstream ss; + + const std::string charset = "0123456789abcdef"; + for (std::size_t i = 0; i < 128; i++) { + ss << charset[rand() % charset.length()]; + } + + // Exercise + Block block; + block.FromHexString(ss.str()); + + // Verify + REQUIRE(block.ToHexString() == ss.str()); +} + +// Tests that converting to, and from, bytestrings works +TEST_CASE(__FILE__"/ByteStringConversion", "[Block]") { + + // Setup + srand(time(0)); + std::stringstream ss; + + for (std::size_t i = 0; i < 64; i++) { + ss << (char)(rand() % 256); + } + + // Exercise + Block block; + block.FromByteString(ss.str()); + + // Verify + REQUIRE(block.ToByteString() == ss.str()); +} + +// Tests that converting to, and from, textstrings works +TEST_CASE(__FILE__"/TextStringConversion", "[Block]") { + + // Setup + const std::string textstr = "Hello, World :3"; + + // Exercise + Block block; + block.FromTextString(textstr); + + // Verify + REQUIRE(block.ToTextString() == textstr); } // Tests that operator* is the same as *= @@ -646,7 +699,7 @@ TEST_CASE(__FILE__"/get-bit", "[Block]") { } // Verify - REQUIRE(ss.str() == a.ToString()); + REQUIRE(ss.str() == a.ToBinaryString()); } // Tests that the set-bit to-false method works @@ -690,7 +743,7 @@ TEST_CASE(__FILE__"/flip-bit", "[Block]") { // Setup Block a = Key::FromPassword("Halleluja"); - std::string compare = a.ToString(); + std::string compare = a.ToBinaryString(); compare[5] = compare[5] == '1' ? '0' : '1'; compare[15] = compare[15] == '1' ? '0' : '1'; compare[105] = compare[105] == '1' ? '0' : '1'; @@ -703,7 +756,7 @@ TEST_CASE(__FILE__"/flip-bit", "[Block]") { a.FlipBit(205); // Verify - REQUIRE(a.ToString() == compare); + REQUIRE(a.ToBinaryString() == compare); } // Tests that bitshifts (to the left) work @@ -732,8 +785,8 @@ TEST_CASE(__FILE__"/bitshift-left", "[Block]") { block = block.ShiftBitsLeft(); // Verify - REQUIRE(block.ToString().length() == shiftedBits.length()); - REQUIRE(block.ToString() == shiftedBits); + REQUIRE(block.ToBinaryString().length() == shiftedBits.length()); + REQUIRE(block.ToBinaryString() == shiftedBits); } // Tests that inplace-bitshifts to the left do the exact same as copy bitshifts @@ -782,8 +835,8 @@ TEST_CASE(__FILE__"/bitshift-right", "[Block]") { block = block.ShiftBitsRight(); // Verify - REQUIRE(block.ToString().length() == shiftedBits.length()); - REQUIRE(block.ToString() == shiftedBits); + REQUIRE(block.ToBinaryString().length() == shiftedBits.length()); + REQUIRE(block.ToBinaryString() == shiftedBits); } // Tests that inplace-bitshifts to the right do the exact same as copy bitshifts diff --git a/GCryptLib/test/Password2Key_CollisionResistance.cpp b/GCryptLib/test/Password2Key_CollisionResistance.cpp index a2fe221..7ab9526 100644 --- a/GCryptLib/test/Password2Key_CollisionResistance.cpp +++ b/GCryptLib/test/Password2Key_CollisionResistance.cpp @@ -70,7 +70,7 @@ TEST_CASE(__FILE__"/Password to key transformation collision resistance", "[Key const std::string password = Base10_2_X(i, charset, 0); // Generate key - const std::string newKeyBits = Key::FromPassword(password).ToString(); + const std::string newKeyBits = Key::FromPassword(password).ToBinaryString(); // Check if this block is already in our map if (keys.find(newKeyBits) != keys.cend()) {