Many methods now using vectors of blocks instead of flexblocks

This commit is contained in:
Leonetienne 2022-05-26 15:04:39 +02:00
parent 800140bafa
commit 143ec19bf3
No known key found for this signature in database
GPG Key ID: C33879CD92E9708C
11 changed files with 292 additions and 87 deletions

View File

@ -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,

View File

@ -4,6 +4,7 @@
#include "GCrypt/Flexblock.h"
#include "GCrypt/Block.h"
#include "GCrypt/GCipher.h"
#include <vector>
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<Block>& 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);

View File

@ -30,7 +30,7 @@ namespace Leonetienne::GCrypt {
Flexblock StringToBits(const std::string& s);
//! Will convert a string to a vector of blocks
std::vector<Block> StringToBitblocks(const std::string& s);
std::vector<Block> 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<Block> ReadFileToBlocks(const std::string& filepath, std::size_t& bytes_read);
//! Will read a file directly to data blocks
std::vector<Block> ReadFileToBlocks(const std::string& filepath);

View File

@ -4,6 +4,8 @@
#include <sstream>
#include <cassert>
#include <cstring>
#include <iomanip>
#include <ios>
// 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 <typename T>
Basic_Block<T>::Basic_Block(const std::string& str) {
FromString(str);
FromBinaryString(str);
}
template <typename T>
@ -26,9 +28,13 @@ namespace Leonetienne::GCrypt {
}
template <typename T>
void Basic_Block<T>::FromString(const std::string& str) {
void Basic_Block<T>::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<CHUNK_SIZE_BITS>(
@ -40,7 +46,58 @@ namespace Leonetienne::GCrypt {
}
template <typename T>
std::string Basic_Block<T>::ToString() const {
void Basic_Block<T>::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 <typename T>
void Basic_Block<T>::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 <typename T>
void Basic_Block<T>::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 <typename T>
std::string Basic_Block<T>::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 <typename T>
std::string Basic_Block<T>::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 <typename T>
std::string Basic_Block<T>::ToByteString() const {
std::stringstream ss;
ss.write((const char*)(void*)Data(), BLOCK_SIZE);
return ss.str();
}
template <typename T>
std::string Basic_Block<T>::ToTextString() const {
std::string bytes = ToByteString();
// Trim extra nullterminators
bytes.resize(strlen(bytes.data()));
return bytes;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::MMul(const Basic_Block<T>& o) const {

View File

@ -31,34 +31,55 @@ namespace Leonetienne::GCrypt {
return block;
}
Block GHash::CalculateHashsum(const Flexblock& data) {
// Split input into blocks
std::vector<Block> blocks;
Block GHash::CalculateHashsum(const std::vector<Block>& 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<Block> 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;

View File

@ -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

View File

@ -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<Block> 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<Block> 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<Block> 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<Block> 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(

View File

@ -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() {

View File

@ -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<Block> ReadFileToBlocks(const std::string& filepath) {
std::vector<Block> 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<Block> 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<Block>& blocks
@ -296,47 +303,21 @@ namespace Leonetienne::GCrypt {
return;
}
std::vector<Block> StringToBitblocks(const std::string& s) {
std::vector<Block> 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<Block> 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;
}
}
}
return blocks;
}
}

View File

@ -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

View File

@ -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()) {