Implement direct file i/o to- and from blocks.

This commit is contained in:
Leonetienne 2022-05-26 04:22:42 +02:00
parent 101a1e0fd6
commit 81a9570673
No known key found for this signature in database
GPG Key ID: C33879CD92E9708C
7 changed files with 205 additions and 56 deletions

View File

@ -4,7 +4,11 @@
#include <string> #include <string>
namespace Leonetienne::GCrypt { 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; typedef std::string Flexblock;
} }

View File

@ -5,6 +5,7 @@
#include <sstream> #include <sstream>
#include <fstream> #include <fstream>
#include <cstring> #include <cstring>
#include <vector>
#include "GCrypt/Block.h" #include "GCrypt/Block.h"
#include "GCrypt/Flexblock.h" #include "GCrypt/Flexblock.h"
#include "GCrypt/Config.h" #include "GCrypt/Config.h"
@ -28,12 +29,22 @@ namespace Leonetienne::GCrypt {
//! Will convert a string to a flexible data block //! Will convert a string to a flexible data block
Flexblock StringToBits(const std::string& s); Flexblock StringToBits(const std::string& s);
//! Will convert a fixed-size data block to a bytestring //! Will convert a string to a vector of blocks
std::string BitblockToBytes(const Block& bits); std::vector<Block> 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<Block>& bits);
//! Will convert a fixed-size data blocks to a textstring
//! The difference to BitblockToBytes() is, that it strips excess nullbytes //! 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<Block>& blocks);
//! Will convert a flexible data block to a bytestring //! Will convert a flexible data block to a bytestring
std::string BitsToBytes(const Flexblock& bits); std::string BitsToBytes(const Flexblock& bits);
@ -59,6 +70,12 @@ namespace Leonetienne::GCrypt {
//! Will save bits to a binary file //! Will save bits to a binary file
void WriteBitsToFile(const std::string& filepath, const Flexblock& bits); void WriteBitsToFile(const std::string& filepath, const Flexblock& bits);
//! Will read a file directly to data blocks
std::vector<Block> ReadFileToBlocks(const std::string& filepath);
//! Will write data blocks directly to a file
void WriteBlocksToFile(const std::string& filepath, const std::vector<Block>& blocks);
} }
#endif #endif

View File

@ -1,7 +1,7 @@
#ifndef GCRYPT_VERSION_H #ifndef GCRYPT_VERSION_H
#define GCRYPT_VERSION_H #define GCRYPT_VERSION_H
#define GCRYPT_VERSION 0.233 #define GCRYPT_VERSION 0.234
#endif #endif

View File

@ -46,14 +46,23 @@ namespace Leonetienne::GCrypt {
bool printProgressReport) bool printProgressReport)
{ {
try { try {
// Read the file to bits // Read the file to blocks
const Flexblock cleartext_bits = ReadFileToBits(filename_in); const std::vector<Block> cleartext_blocks = ReadFileToBlocks(filename_in);
// Encrypt our cleartext bits // Encrypt our cleartext blocks
const Flexblock ciphertext_bits = CipherFlexblock(cleartext_bits, key, GCipher::DIRECTION::ENCIPHER); std::vector<Block> ciphertext_blocks;
ciphertext_blocks.reserve(cleartext_blocks.size());
// Write our ciphertext bits to file // Create cipher instance
WriteBitsToFile(filename_out, ciphertext_bits); 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; return true;
} }
@ -69,14 +78,23 @@ namespace Leonetienne::GCrypt {
bool printProgressReport) bool printProgressReport)
{ {
try { try {
// Read the file to bits // Read the file to blocks
const Flexblock ciphertext_bits = ReadFileToBits(filename_in); const std::vector<Block> ciphertext_blocks = ReadFileToBlocks(filename_in);
// Decrypt the ciphertext bits // Decrypt our cleartext blocks
const Flexblock cleartext_bits = CipherFlexblock(ciphertext_bits, key, GCipher::DIRECTION::DECIPHER); std::vector<Block> cleartext_blocks;
cleartext_blocks.reserve(ciphertext_blocks.size());
// Write our cleartext bits to file // Create cipher instance
WriteBitsToFile(filename_out, cleartext_bits); 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; return true;
} }

View File

@ -19,17 +19,14 @@ namespace Leonetienne::GCrypt {
std::random_device rng; std::random_device rng;
constexpr std::size_t bitsPerCall = sizeof(std::random_device::result_type) * 8; constexpr std::size_t bitsPerCall = sizeof(std::random_device::result_type) * 8;
// Fetch BLOCK_SIZE bits // Create a new key, and assign 16 random values
std::stringstream ss; Key key;
for (std::size_t i = 0; i < Key::BLOCK_SIZE_BITS / bitsPerCall; i++) { for (std::size_t i = 0; i < 16; i++) {
ss << std::bitset<bitsPerCall>(rng()); key[i] = rng();
} }
// Verify that we actually have the correct size // Return it
assert(ss.str().length() == Key::BLOCK_SIZE_BITS); return key;
// Return them as a key
return Key(Block(ss.str()));
} }
Key Key::LoadFromFile(const std::string& path) { 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..."); throw std::runtime_error(std::string("Unable to open ifilestream for keyfile \"") + path + "\"! Aborting...");
} }
// Read these chars to buffer // Create a new key, and zero it
char* ckeyfileContent = new char[maxChars]; Key key;
memset(ckeyfileContent, 0, maxChars * sizeof(char)); key.Reset();
ifs.read(ckeyfileContent, maxChars);
ifs.close();
// Convert the buffer to a bit block of key size // Read into it
std::stringstream ss; ifs.read((char*)(void*)key.Data(), Key::BLOCK_SIZE);
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;
// Return it // Return it
return key; return key;
} }
void Key::WriteToFile(const std::string& path) { void Key::WriteToFile(const std::string& path) {
// Transform key to bytes
const std::string keybytes = BitsToBytes(ToString());
// Create an ofilestream // Create an ofilestream
std::ofstream ofs(path, std::ios::out | std::ios::binary); std::ofstream ofs(path, std::ios::out | std::ios::binary);
// Write the key // Write the key
ofs.write(keybytes.data(), Key::BLOCK_SIZE_BITS / 8); ofs.write((char*)(void*)Data(), Key::BLOCK_SIZE);
// Close the file handle // Close the file handle
ofs.close(); ofs.close();

View File

@ -1,5 +1,6 @@
#include "GCrypt/Util.h" #include "GCrypt/Util.h"
#include "GCrypt/GHash.h" #include "GCrypt/GHash.h"
#include <vector>
namespace Leonetienne::GCrypt { namespace Leonetienne::GCrypt {
@ -50,13 +51,22 @@ namespace Leonetienne::GCrypt {
return Flexblock(ss.str()); return Flexblock(ss.str());
} }
std::string BitblockToBytes(const Block& bits) { std::string BitblockToBytes(const Block& block) {
std::stringstream ss; 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) { return ss.str();
ss << (char)std::bitset<8>(bitstring.substr(i, 8)).to_ulong(); }
std::string BitblocksToBytes(const std::vector<Block>& blocks) {
std::stringstream ss;
for (const Block& block : blocks) {
ss << BitblockToBytes(block);
} }
return ss.str(); return ss.str();
@ -72,6 +82,16 @@ namespace Leonetienne::GCrypt {
return text; return text;
} }
std::string BitblocksToString(const std::vector<Block>& 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::string BitsToBytes(const Flexblock& bits) {
std::stringstream ss; std::stringstream ss;
@ -211,5 +231,113 @@ namespace Leonetienne::GCrypt {
return; return;
} }
std::vector<Block> 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<Block> 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<Block>& 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<Block> 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<Block> 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;
}
} }

View File

@ -46,13 +46,11 @@ TEST_CASE(__FILE__"/Encrypting and decrypting files works", "[Wrapper]") {
GWrapper::DecryptFile(filename_encrypted, filename_decrypted, key); GWrapper::DecryptFile(filename_encrypted, filename_decrypted, key);
// Read in both the base, and the decrypted file // Read in both the base, and the decrypted file
const Flexblock plainfile = ReadFileToBits(filename_plain); const std::vector<Block> plainfile = ReadFileToBlocks(filename_plain);
const Flexblock decryptfile = ReadFileToBits(filename_decrypted); const std::vector<Block> decryptfile = ReadFileToBlocks(filename_decrypted);
// Assertion (If this fails, maybe check if the image is even readable by an image viewer) // Assertion (If this fails, maybe check if the image is even readable by an image viewer)
REQUIRE( REQUIRE(plainfile.size() == decryptfile.size());
PadStringToLength(plainfile, decryptfile.length(), '0', false) == REQUIRE(plainfile == decryptfile);
decryptfile
);
} }