Merge branch 'feature/relaunch' into master

This commit is contained in:
Leonetienne 2022-06-01 03:30:55 +02:00
commit 74883458ba
No known key found for this signature in database
GPG Key ID: C33879CD92E9708C
115 changed files with 24190 additions and 1947 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
build/
*.swp
*_/

6
.gitmodules vendored
View File

@ -7,3 +7,9 @@
[submodule "GeneralUtility"]
path = GeneralUtility
url = https://gitea.leonetienne.de/leonetienne/GeneralUtility.git
[submodule "GCryptLib/exec/Eule"]
path = GCryptLib/exec/Eule
url = https://gitea.leonetienne.de/leonetienne/Eule.git
[submodule "GCryptLib/exec/BmpPP"]
path = GCryptLib/exec/BmpPP
url = https://gitea.leonetienne.de/leonetienne/BmpPP.git

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(gecrypt)
project(gcrypt)
set(CMAKE_CXX_STANDARD 17)
@ -42,3 +42,37 @@ target_include_directories(${PROJECT_NAME} PRIVATE
${gcrypt_include}
)
target_compile_options(${PROJECT_NAME} PRIVATE
-Werror
-fdiagnostics-color=always
)
#########
# Tests #
#########
LIST(FILTER main_src EXCLUDE REGEX ".*/main.cpp")
FILE(GLOB test_src test/*.cpp)
add_executable(test
test/Catch2.h
${test_src}
${main_src}
${stringtools_src}
${generalutility_src}
${hazelnupp_src}
${gcrypt_src}
)
target_include_directories(test PRIVATE
include
${stringtools_include}
${generalutility_include}
${hazelnupp_include}
${gcrypt_include}
)
target_compile_options(test PRIVATE
-Werror
-fdiagnostics-color=always
)

View File

@ -1,6 +1,28 @@
#pragma once
#ifndef GCRYPTCLI_BASES_H
#define GCRYPTCLI_BASES_H
#include <vector>
#include <string>
#include <map>
// This lookup table holds how many digits a block is long
// in any iobase.
// This cannot be calculated on the fly, as it involves
// arithmetic with involving REALLY big numbers (like, 2^512).
// Here's how to calculate these numbers:
// Print an all 1's block in this format, and check the string size.
// That's it.
static auto blockLengthByBase =
std::map<Configuration::IOBASE_FORMAT, std::size_t>({
std::make_pair(Configuration::IOBASE_FORMAT::BASE_BYTES, 64),
std::make_pair(Configuration::IOBASE_FORMAT::BASE_2, 512),
std::make_pair(Configuration::IOBASE_FORMAT::BASE_8, 171),
std::make_pair(Configuration::IOBASE_FORMAT::BASE_10, 155),
std::make_pair(Configuration::IOBASE_FORMAT::BASE_16, 128),
std::make_pair(Configuration::IOBASE_FORMAT::BASE_64, 86),
std::make_pair(Configuration::IOBASE_FORMAT::BASE_UWU, 81),
std::make_pair(Configuration::IOBASE_FORMAT::BASE_UGH, 126)
});
// Actually useful bases
static const std::vector<std::string> BASE_2 = { "0","1" };
@ -116,3 +138,6 @@ static const std::vector<std::string> BASE_UGH = {
"Bah!",
"Wha-?"
};
#endif

View File

@ -1,4 +1,6 @@
#pragma once
#ifndef GCRYPTCLI_CLIINTERFACE_H
#define GCRYPTCLI_CLIINTERFACE_H
#include <Hazelnupp/CmdArgsInterface.h>
class CommandlineInterface
@ -14,8 +16,11 @@ private:
static void CrashWithMsg(const std::string& msg);
static void CatchVersionQueries();
CommandlineInterface() {};
static Hazelnp::CmdArgsInterface nupp;
// No instanciation! >:(
CommandlineInterface() {};
};
#endif

View File

@ -0,0 +1,61 @@
#ifndef GCRYPTCLI_CONFIGURATION_H
#define GCRYPTCLI_CONFIGURATION_H
#include <string>
class Configuration {
public:
static enum class INPUT_FROM {
STDIN,
FILE,
PARAMETER
} inputFrom;
static enum class OUTPUT_TO {
STDOUT,
FILE
} outputTo;
static enum class IOBASE_FORMAT {
BASE_BYTES,
BASE_2,
BASE_8,
BASE_10,
BASE_16,
BASE_64,
BASE_UWU,
BASE_UGH
}
formatIn,
formatOut;
static std::string inputFilename;
static std::string outputFilename;
static enum class MODULE {
ENCRYPTION,
DECRYPTION,
HASH,
GENERATE_KEY
} activeModule;
//! Will analyze the supplied cli parameters,
//! and decide what the configuration will be.
static void Parse();
private:
static void DecideInputFrom();
static void DecideOutputTo();
static void DecideCiphertextFormat();
static void MapCiphertextFormatToIOBases();
static void DecideModule();
// This is just an intermediary value, used between methods
static IOBASE_FORMAT ciphertextFormat;
// No instanciation! >:(
Configuration() {};
};
#endif

View File

@ -0,0 +1,58 @@
#ifndef GCRYPTCLI_DATAFORMATTER_H
#define GCRYPTCLI_DATAFORMATTER_H
#include <GCrypt/Block.h>
#include <string>
#include <vector>
#include "Configuration.h"
using namespace Leonetienne::GCrypt;
// This class has the task to format Blocks to various formats.
class DataFormatter {
public:
//! Will format a single block to a given iobase
static std::string FormatBlock(
const Block& block,
const Configuration::IOBASE_FORMAT base
);
//! Will parse a string of a given iobase to a block
static Block DecodeFormat(
const std::string& str,
const Configuration::IOBASE_FORMAT base
);
//! Will format a vector of blocks to a given iobase
static std::string FormatBlocks(
const std::vector<Block>& blocks,
const Configuration::IOBASE_FORMAT base
);
//! Will format a string making up multiple block in a given iobase into a vector of block
static std::vector<Block> DecodeFormatMultiblock(
const std::string& str,
const Configuration::IOBASE_FORMAT base
);
private:
static std::string Bin2CustomBase(
const std::string& bin,
const std::vector<std::string>& customSet,
const std::size_t minLen,
const std::string& seperator = ""
);
static std::string CustomBase2Bin(
const std::string& in,
const std::vector<std::string>& customSet,
const std::string& seperator = ""
);
// No instanciation! >:(
DataFormatter() {};
};
#endif

View File

@ -0,0 +1,75 @@
#ifndef GCRYPTCLI_DATAINGESTIONLAYER_H
#define GCRYPTCLI_DATAINGESTIONLAYER_H
#include <iosfwd>
#include <queue>
#include <GCrypt/Block.h>
#include "Configuration.h"
using namespace Leonetienne::GCrypt;
namespace IO {
// This class is used to read in data.
class DataIngestionLayer {
public:
//! Will initialize the ingestion
static void Init();
//! Will destruct the ingestion layer (like, closing file handles)
static void Destruct();
//! Will attempt to read a data block.
//! Requires Init() to have been called
static void ReadBlock();
//! Have we read in all available blocks?
static bool ReachedEOF();
//! Returns true if there are blocks to be fetched (GetNextBlock())
static bool IsBlockReady();
//! Will return the next block in the queue
static Block GetNextBlock();
//! Returns true, if EOF is reached, and there are no more blocks to fetch (GetNextBlock())
static bool IsFinished();
//! Returns how many blocks have been read, in total
static std::size_t NBlocksRead();
private:
static std::istream* in;
// Will read n bytes from the input.
// If EOF is reached, it will return a string of length <= 5
// and will set the approriate flags.
static std::string ReadBytes(const std::size_t n, std::size_t& out_bytes_read);
// We have to hold on to a reference to a filestream,
// even if we're always just reading from in.
// We still have to CLOSE the file handle afterwards!
static std::ifstream ifs;
static std::istringstream iss;
// Indicates whether EOF has been reached
static bool reachedEof;
// Indicates whether this class has been initialized
static bool initialized;
// Are we reading ciphertext or regular text?
static bool isReadingCiphertext;
// How many blocks have been read in total
static std::size_t nBlocksRead;
// All read blocks, that haven't been given out yet
static std::queue<Block> blocks;
// No instanciation! >:(
DataIngestionLayer();
};
}
#endif

View File

@ -0,0 +1,61 @@
#ifndef GCRYPTCLI_DATAOUTPUTLAYER_H
#define GCRYPTCLI_DATAOUTPUTLAYER_H
#include <iosfwd>
#include <queue>
#include <GCrypt/Block.h>
#include "Configuration.h"
using namespace Leonetienne::GCrypt;
namespace IO {
// This class is used to read in data.
class DataOutputLayer {
public:
//! Will initialize the output
static void Init();
//! Will destruct the output layer (like, closing file handles)
static void Destruct();
//! Will queue a block for writing
static void Enqueue(const Block& block);
//! Will attempt to write the next block
static void WriteBlock();
//! Indicates that no more blocks will be enqueued
static void ReachedEOF();
//! Returns true, if all blocks have been written, and an EOF signal as been received
static bool IsFinished();
private:
//! If we are finished, and are outputting to stdout,
//! and the user didn't specifically opt out, print a newline
static void AddTrailingLinebreakIfRequired();
static std::ostream* out;
// We have to hold on to a reference to a filestream,
// even if we're always just reading from in.
// We still have to CLOSE the file handle afterwards!
static std::ofstream ofs;
// Indicates whether EOF has been reached
static bool reachedEof;
// Indicates whether this class has been initialized
static bool initialized;
// All blocks, that haven't been written yet
static std::queue<Block> blocks;
//! No instanciation >:(
DataOutputLayer() {};
};
}
#endif

View File

@ -0,0 +1,30 @@
#ifndef GCRYPTCLI_KEYMANAGER_H
#define GCRYPTCLI_KEYMANAGER_H
#include <GCrypt/Key.h>
using namespace Leonetienne::GCrypt;
// This class has the task to prepare and supply the encryption key.
class KeyManager {
public:
//! Will prepare the key. Be it from cli, a file, or, random, or whatever.
static void PrepareKey();
//! Will return the key, if prepared.
static const Key& GetKey();
private:
//! Will ask for a password on stdin,
//! hiding the input.
static std::string PasswordPrompt();
//! The encryption key
static Key key;
// No instanciation! >:(
KeyManager() {};
};
#endif

View File

@ -0,0 +1,18 @@
#ifndef GCRYPTCLI_MODULE_DECRYPTION_H
#define GCRYPTCLI_MODULE_DECRYPTION_H
namespace Module {
//! This module will decrypt supplied input
class Decryption {
public:
//! Will run the module
static void Run();
private:
// No instanciation! >:(
Decryption() {};
};
}
#endif

View File

@ -0,0 +1,18 @@
#ifndef GCRYPTCLI_MODULE_ENCRYPTION_H
#define GCRYPTCLI_MODULE_ENCRYPTION_H
namespace Module {
//! This module will encrypt supplied input
class Encryption {
public:
//! Will run the module
static void Run();
private:
// No instanciation! >:(
Encryption() {};
};
}
#endif

View File

@ -0,0 +1,19 @@
#ifndef GCRYPTCLI_MODULE_GENERATEKEY_H
#define GCRYPTCLI_MODULE_GENERATEKEY_H
namespace Module {
// This module just generates a key, and outputs it.
// Can be used to create a keyfiles.
class GenerateKey {
public:
//! Will write the key to a file
static void Run();
private:
// No instanciation! >:(
GenerateKey() {};
};
}
#endif

View File

@ -0,0 +1,18 @@
#ifndef GCRYPTCLI_MODULE_HASHING_H
#define GCRYPTCLI_MODULE_HASHING_H
namespace Module {
//! This module will hash supplied input
class Hashing {
public:
//! Will run the module
static void Run();
private:
// No instanciation! >:(
Hashing() {};
};
}
#endif

View File

@ -0,0 +1,23 @@
#ifndef GCRYPTCLI_PROGRESSPRINTER_H
#define GCRYPTCLI_PROGRESSPRINTER_H
#include <iostream>
#include "CommandlineInterface.h"
// This class has the task to output progress to stderr, if requested
class ProgressPrinter {
public:
//! Will print progress to stderr, if requested, and the interval matches
static void PrintIfAppropriate(
const std::string& message,
const std::size_t current,
const std::size_t target
);
private:
// No instanciation! >:(
ProgressPrinter() {};
};
#endif

View File

@ -1,3 +1,7 @@
#pragma once
#define GHETTOCRYPTCLI_VERSION 0.1241
#ifndef GCRYPTCLI_VERSION_H
#define GCRYPTCLI_VERSION_H
#define GCRYPTCLI_VERSION 0.1251
#endif

View File

@ -1,62 +1,88 @@
# GCrypt CLI
Easy text and file encryption via the command line using Ghetto Crypt. Now supporting [*esoteric data formats*](#esoteric-data-formats)... :)
Easy text and file encryption via the command line using GCrypt. Now supporting [*esoteric data formats*](#esoteric-data-formats)... :)
Again, please only use this as an obfuscator or if the only other option would be no encryption at all.
Do you want to quickly and securely encrypt stuff via the command line? Use openssl-cli with the aes cipher. It's a bit more wordy but much faster and more secure.
Still want to use Ghetto Crypt cli? Here ya go!
Just clone this repository, navigate into this directory and run `cmake . && make`. It shouuuld *\*crosses fingers\** just build.
Still want to use GCrypt cli? Here ya go!
Just clone this repository, navigate into this directory and compile it:
## How do i use this?
### Prerequsities:
Have these depencies installed:
* git
* make
* cmake
* build-essentials (c++ >= 17) (might work with 11, if you're lucky)
### How2compile
1) Clone this repository.
2) Download all submodules: `git submodule update --init --recursive`.
3) Cd into /GCryptCLI: `cd GCryptCLI`.
4) Create a build directory: `cmake -B build`.
5) Cd into the build directory: `cd build`.
6) Compile: `make`.
The executable `gcrypt` should now lie in `build/`.
If you want to use this globally, you could move it to `/usr/bin/`, or some other location in your $PATH.
## How do i use this?
### Options and flags
All arguments and flags:
```
$ gecrypt --help
CLI for the ghettocrypt cipher/obfuscator
CLI for the GCrypt cipher/obfuscator
Copyright (c) 2022 Leon Etienne
Ghettocrypt v0.21
Ghettocrypt CLI v0.124
THIS IS EXPERIMENTAL SOFTWARE AND MUST BE CONSIDERED INSECURE. DO NOT USE THIS TO ENCRYPT SENSITIVE DATA! READ THE README FILES ACCESSIBLE AT "https://github.com/Leonetienne/GhettoCrypt/blob/master/readme.md" AND "https://github.com/Leonetienne/GhettoCrypt/blob/master/GhettoCryptCLI/readme.md"
GCrypt v0.236
GCrypt CLI v0.1241
THIS IS EXPERIMENTAL SOFTWARE AND MUST BE CONSIDERED INSECURE. DO NOT USE THIS TO ENCRYPT SENSITIVE DATA! READ THE README FILES ACCESSIBLE AT "https://gitea.leonetienne.de/leonetienne/GCrypt"
==== AVAILABLE PARAMETERS ====
--iobase-8 VOID incompatibilities=[--iobase-2, --iobase-10, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base8
--iobase-bytes VOID incompatibilities=[--iobase-2, --iobase-8, --iobase-10, --iobase-16, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and output ciphertexts as raw bytes.
--iobase-16 VOID incompatibilities=[--iobase-bytes, --iobase-2, --iobase-8, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base16 (hex)
--progress -p VOID Print digestion progress to stdout. May be advisable for large files, as the cipher is rather slow.
--cli-version VOID Will supply the version of ghettocrypt-cli used.
--cli-version -v VOID Will supply the version of GCryptCLI used.
--keyfile -kf STRING incompatibilities=[--key, --keyask, --hash] Read in the first {KEYSIZE}(=512) bits of this file and use that as an encryption key. WARNING: Arguments may be logged by the system!
--iobase-2 VOID incompatibilities=[--iobase-8, --iobase-10, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base2
--iobase-2 VOID incompatibilities=[--iobase-bytes, --iobase-8, --iobase-10, --iobase-16, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base2
--iobase-10 VOID incompatibilities=[--iobase-2, --iobase-8, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base10
--iobase-10 VOID incompatibilities=[--iobase-bytes, --iobase-2, --iobase-8, --iobase-16, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base10
--iobase-ugh VOID incompatibilities=[--iobase-2, --iobase-8, --iobase-10, --iobase-64, --iobase-uwu] Interpret and format ciphertexts in base ugh
--ofile -o STRING incompatibilities=[--ostdout, --hash] Write output in this file.
--iobase-uwu VOID incompatibilities=[--iobase-2, --iobase-8, --iobase-10, --iobase-64, --iobase-ugh] Interpret and format ciphertexts in base uwu
--iobase-64 VOID incompatibilities=[--iobase-2, --iobase-8, --iobase-10, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base64
--ostdout VOID incompatibilities=[--ofile, --hash] Output of digested files will be dumped to stdout instead of a file.
--encrypt -e VOID incompatibilities=[--decrypt, --hash] Use the encryption routine.
--infile -if STRING incompatibilities=[--intext] Encrypt this file. Saves as {filename}.crypt, if not specified otherwise.
--version -v VOID Will supply the version of ghettocrypt used.
--decrypt -d VOID incompatibilities=[--encrypt, --hash] Use decryption routine.
--hash -h VOID incompatibilities=[--encrypt, --decrypt] Use the ghetto cipher as a hash digest.
--intext -it STRING incompatibilities=[--infile] Encrypt this string. Dumps to stdout.
--ofile -o STRING incompatibilities=[--ostdout, --hash] Use this filename for output if --infile is specified. Gets ignored otherwise.
--key -k STRING incompatibilities=[--keyfile, --keyask, --hash] Use this value as a password to extrapolate the encryption key. WARNING: Arguments may be logged by the system!
--keyask -ka VOID incompatibilities=[--key, --keyfile, --hash] Read the encryption key from stdin.
--key -k STRING incompatibilities=[--keyfile, --keyask, --hash] Use this value as a password to extrapolate the encryption key. WARNING: Arguments may be logged by the system!
--no-newline VOID Don't postfix stdout output with a newline
--puffer-output VOID Will digest the entire data before initiating any output.
--puffer-input VOID Will read the entire input before beginning any digestion.
--decrypt -d VOID incompatibilities=[--encrypt, --hash, --generate-key] Use decryption module.
--lib-version VOID Will supply the version of GCryptLib used.
--iobase-ugh VOID incompatibilities=[--iobase-bytes, --iobase-2, --iobase-8, --iobase-10, --iobase-16, --iobase-64, --iobase-uwu] Interpret and format ciphertexts in base ugh
--infile -if STRING incompatibilities=[--intext] Encrypt this file.
--iobase-uwu VOID incompatibilities=[--iobase-bytes, --iobase-2, --iobase-8, --iobase-10, --iobase-16, --iobase-64, --iobase-ugh] Interpret and format ciphertexts in base uwu
--iobase-64 VOID incompatibilities=[--iobase-bytes, --iobase-2, --iobase-8, --iobase-10, --iobase-16, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base64
--iobase-8 VOID incompatibilities=[--iobase-bytes, --iobase-2, --iobase-10, --iobase-16, --iobase-64, --iobase-uwu, --iobase-ugh] Interpret and format ciphertexts in base8
--encrypt -e VOID incompatibilities=[--decrypt, --hash] Use the encryption module.
--generate-key VOID incompatibilities=[--encrypt, --decrypt, --hash] Use the key generation module. Will generate a random key based on hardware events, output it, and exit.
--hash -h VOID incompatibilities=[--encrypt, --decrypt, --generate-key] Use the GHash hash module to calculate a hashsum.
--intext -it STRING incompatibilities=[--infile] Encrypt this string.
```
### Examples
@ -64,13 +90,13 @@ Please note that commonly used arguments are supplied in their short form (`-e`
#### I want to encrypt text!
```sh
$ gecrypt -e --keyask --intext "hello, world!"
$ gcrypt -e --keyask --intext "hello, world!"
efbebc429c8370bf84f00b0d8ccbaf7858b3b87d71ff58cb1cfefa8fb0c68094c0865565873aa8a5254ede59be46e81a4d4917e679b18cb290dbd6669cb6207a
```
#### Now decrypt it
```sh
$ gecrypt -d --keyask --intext "efbebc429c8370bf84f00b0d8ccbaf7858b3b87d71ff58cb1cfefa8fb0c68094c0865565873aa8a5254ede59be46e81a4d4917e679b18cb290dbd6669cb6207a"
$ gcrypt -d --keyask --intext "efbebc429c8370bf84f00b0d8ccbaf7858b3b87d71ff58cb1cfefa8fb0c68094c0865565873aa8a5254ede59be46e81a4d4917e679b18cb290dbd6669cb6207a"
hello, world!
```
@ -80,95 +106,109 @@ hello, world!
> Outputting it in base-64 took just over ONE MINUTE. In base-2 over SEVEN MINUTES. The general trend seems to be, the larger the base, the better it performs.
```sh
$ gecrypt -e --keyask --intext "hello, world!" --iobase-2
$ gcrypt -e --keyask --intext "hello, world!" --iobase-2
111001001011100000011111000100010100110011100110000100100101001001110001001000101011110000000011011100001010111010001000110111110110011011100000001100110001001000100111011000101010010001011011111011001000011111100100101001011110011101110001010011000101011001111010000001001100101110000101101101101001110100100001101010111101010000100111101110000110011101100101101011000011101001000011010010011001111010001001101000101001100101010000100010111101101100010000000001001110010001001011001001011011010111001101000100
$ gecrypt -e --keyask --intext "hello, world!" --iobase-8
$ gcrypt -e --keyask --intext "hello, world!" --iobase-8
71134037042463460445116110536003341272106766334014611047305221337310374451363561230531720114560555516441527520475606354553035103223172115051452042755420011621131133271504
$ gecrypt -e --keyask --intext "hello, world!" --iobase-10
$ gcrypt -e --keyask --intext "hello, world!" --iobase-10
2994749439449970047518881970731547473115480925772565399786126223459744370490386223437520234266936877059618216185983047971748564015130703048737306773910340
$ gecrypt -e --keyask --intext "hello, world!" --iobase-64
$ gcrypt -e --keyask --intext "hello, world!" --iobase-64
Co/WjpV5nPrCaz0QMdrXAXzzOH5HODRsBNL22KZowmGMcTLwfmsQpzt7Ik+ViR5vOhUXowFQeR5x2vbcj1X5ae
```
I won't be pasting in stdout for every example. It would become too cluttered.
#### Passing the key as an argument
```sh
$ gecrypt -e --key "secretpassword" --intext "hello, world!"
$ gcrypt -e --key "secretpassword" --intext "hello, world!"
```
> :warning: Some operating systems will log cli arguments! THIS WOULD BE THE ENTIRE KEY!
#### Using keyfiles
```sh
$ gecrypt -e --keyfile "dog.jpg" --intext "hello, world!"
$ gcrypt -e --keyfile "dog.jpg" --intext "hello, world!"
```
#### Creating keyfiles
```sh
$ gcrypt --generate-key --ofile "my-keyfile.bin"
```
This will generate a random 512-bit keyfile from hardware events
> :warning: Some operating systems will log cli arguments! One might find your keyfile!
#### Encrypting files
```sh
$ gecrypt -e --keyask --infile "cat.jpg"
$ gcrypt -e --keyask --infile "cat.jpg"
```
File `cat.jpg.crypt` will be created.
#### Encrypting files to a target file name
```sh
$ gecrypt -e --keyask --infile "cat.jpg" -o "encrypted_cat.jpg"
$ gcrypt -e --keyask --infile "cat.jpg" -o "encrypted_cat.jpg"
```
File `encrypted_cat.jpg` will be created.
#### Decrypting files
```sh
$ gecrypt -d --keyask --infile "cat.jpg.crypt"
$ gcrypt -d --keyask --infile "cat.jpg.crypt"
```
File `cat.jpg.crypt.plain` will be created. Its contents match `cat.jpg`
> :warning: Since this is a block cipher, decrypted files may be tailpadded with a few nullbytes.
#### Decrypting files to a target file name
```sh
$ gecrypt -d --keyask --infile "cat.jpg.crypt" -o "decrypted_cat.jpg"
$ gcrypt -d --keyask --infile "cat.jpg.crypt" -o "decrypted_cat.jpg"
```
File `decrypted_cat.jpg` will be created. You can now open it again.
#### Encrypting large files takes time. How's the progress?
```sh
$ gecrypt -e --keyask --infile "cat.jpg" --progress
$ gcrypt -e --keyask --infile "cat.jpg" --progress
```
Something along the lines of `Encrypting... (Block 200 / 1148 - 17.4216%)` will be regularly, but not too often, printed to stdout.
#### Any cipher can also compute hashsums
```sh
$ gecrypt -h --intext "hello, world!"
$ gcrypt -h --intext "hello, world!"
a96f42c9d97e46b9e1ed7de5182770170d4ef9b7b8264f3fbd89b38dc60c1fe06232653f5856013307fc020fb1d35f2bea26bc0f373c5ac35a722c6b03d8254d
$ gecrypt -h --infile "cat.jpg"
$ gcrypt -h --infile "cat.jpg"
fe6bdfb6ec39771c4fdcdc40e52397bcd67fbfef0ad5a15ebbd8b9e4c2a815848b3984eda5ef6f727e9e420c23500c90c42ab80ac5659048be8969357741e3e5
```
The hashsum will always be of size BLOCK_SIZE. That is 512 bits by default.
> :warning: Calculating a hashsum takes about twice as long as regular encryption.
The hashsum will always be of size BLOCK_SIZE. That is 512.
#### What version am i running?
Depending on wether you want to know the GhettoCrypt version or the CLI's version, use either `--version` or `--cli-version`. It will print out a floating point number.
Depending on wether you want to know the GCrypt version or the CLI's version,
use either `--cli-version` or `--lib-version`.
It will print out a floating point number.
#### I want to stream the output of file en/decryption.
You can dump the binary result to stdout. In this example, we'll stream it into a jpg file,
but you could stream it to whatever you'd like.
// Easily! If you do not supply any output or input, stdout and stdin will be used instead!
```sh
$ gecrypt -d --keyask --infile "cat.jpg.crypt" --ostdout > "decrypted_cat.jpg"
# mpv is a media player, as an example
$ gcrypt -d --key "123" --infile "music.mp3.crypt" | mpv -
```
#### Don't want to stream input and output?
By default, gcrypt will read a block, digest it, and output the result immediately.
Sometimes you don't want that. Use these flags, respectively:
```
--puffer-input # Reads all input to memory before beginning to digest it
--puffer-output # Digests all input before beginning to write any
```
## Esoteric data formats
#### Base *UwU*
```sh
$ gecrypt -e --keyask --intext "hello, world!" --iobase-uwu
$ gcrypt -e --keyask --intext "hello, world!" --iobase-uwu
:) sewnpaiii tastieee uhh?! nappies cutewr twe best cutieee :O tastieee senpaiiiw favowite toesy-woesies ^.^ :3 best chomp whiffle uwu Awww sewnpaiii comfy-womfy :p keewl Awww youuu nyeko :O tasties hiiiii heeeey (*^_^*) youuu toot uhh..? smush (*^_^*) *bites-lip* whiffle haaaay nyah! comfy-womfy :) cutsie Owww haaaay snaffle haaaai haaaai nyeko *sweats* :) uhh..? boop toot *bites-lip* <3 whiiiich whiffskaws ^.^ twe whiffskaws hiiiii *sweats* Owww dewicious i tasties :P awe hewwo boop rawr uwu dewicious eughh twe cutsie xD
```
#### Base **UGH!**
```sh
$ gecrypt -e --keyask --intext "hello, world!" --iobase-ugh
$ gcrypt -e --keyask --intext "hello, world!" --iobase-ugh
Grr... Wha-? Aah! Aah! Uh-huh... Aah! Grr... Aah! Aah! Uh-huh... Ah... Ugh Grr... Ugh Pft! Nu-uh... Gah! Bah! Huh...? Ah... Uh-huh... Wha-? Pft! Nu-uh... Ugh Wha-? Psh! Agh! Ah... Aah! Nu-uh... Psh! Pft! Nu-uh... Psh! Shh! Gah! Ah... Pft! Gah! Shh! Bah! Gah! Uh-huh... Gah! Duh! Aah! Uh-huh... Er- Nu-uh... Gah! Wha-? Pft! Er- Shh! Ah... Huh...? Er- Wha-? Uh-huh... Ah... Shh! Ugh Bah! Wha-? Uaah! Ah... Nu-uh... Uh-huh... Ugh Pft! Pft! Gah! Shh! Shh! Wha-? Bah! Ugh Grr... Aah! Pft! Nu-uh... Ah... Aah! Agh! Er- Psh! Uaah! Nu-uh... Ugh Wha-? Uh-huh... Shh! Pft! Aah! Agh! Grr... Agh! Agh! Grr... Pft! Wha-? Wha-? Uh-huh... Aah! Ugh Aah! Pft! Gah! Bah! Huh...? Ugh Bah! Uaah! Gah! Bah! Duh! Duh! Uh-huh... Grr... Ah... Grr... Ugh Ah... Pft!
```

View File

@ -2,20 +2,19 @@
#include <iostream>
#include <sstream>
#include <GCrypt/Version.h>
#include <GCrypt/Config.h>
#include <GCrypt/Block.h>
#include "Version.h"
using namespace Hazelnp;
using namespace Leonetienne;
using namespace Leonetienne::GCrypt;
void CommandlineInterface::Init(int argc, const char* const* argv)
{
void CommandlineInterface::Init(int argc, const char* const* argv) {
/* General information */
std::stringstream ss;
ss << "CLI for the gcrypt cipher/obfuscator" << std::endl
ss << "CLI for the GCrypt cipher/obfuscator" << std::endl
<< "Copyright (c) 2022 Leon Etienne" << std::endl
<< "GCrypt v" << GHETTOCRYPT_VERSION << std::endl
<< "GCrypt CLI v" << GHETTOCRYPTCLI_VERSION << std::endl
<< "GCrypt v" << GCRYPT_VERSION << std::endl
<< "GCrypt CLI v" << GCRYPTCLI_VERSION << std::endl
<< "THIS IS EXPERIMENTAL SOFTWARE AND MUST BE CONSIDERED INSECURE. DO NOT USE THIS TO ENCRYPT SENSITIVE DATA! READ THE README FILES ACCESSIBLE AT \"https://gitea.leonetienne.de/leonetienne/GCrypt\"";
nupp.SetBriefDescription(ss.str());
ss.str("");
@ -23,39 +22,39 @@ void CommandlineInterface::Init(int argc, const char* const* argv)
nupp.SetCrashOnFail("true");
/* Builtin documentation */
nupp.RegisterDescription("--encrypt", "Use the encryption routine.");
nupp.RegisterDescription("--encrypt", "Use the encryption module.");
nupp.RegisterConstraint("--encrypt", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {"--decrypt", "--hash" }));
nupp.RegisterAbbreviation("-e", "--encrypt");
nupp.RegisterDescription("--decrypt", "Use decryption routine.");
nupp.RegisterConstraint("--decrypt", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--encrypt", "--hash" }));
nupp.RegisterDescription("--decrypt", "Use decryption module.");
nupp.RegisterConstraint("--decrypt", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--encrypt", "--hash", "--generate-key" }));
nupp.RegisterAbbreviation("-d", "--decrypt");
nupp.RegisterDescription("--hash", "Use the ghetto cipher as a hash digest.");
nupp.RegisterConstraint("--hash", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--encrypt", "--decrypt" }));
nupp.RegisterDescription("--hash", "Use the GHash hash module to calculate a hashsum.");
nupp.RegisterConstraint("--hash", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--encrypt", "--decrypt", "--generate-key" }));
nupp.RegisterAbbreviation("-h", "--hash");
nupp.RegisterDescription("--intext", "Encrypt this string. Dumps to stdout.");
nupp.RegisterDescription("--generate-key", "Use the key generation module. Will generate a random key based on hardware events, output it, and exit.");
nupp.RegisterConstraint("--generate-key", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--encrypt", "--decrypt", "--hash" }));
nupp.RegisterDescription("--intext", "Encrypt this string.");
nupp.RegisterConstraint("--intext", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--infile" }));
nupp.RegisterAbbreviation("-it", "--intext");
nupp.RegisterDescription("--infile", "Encrypt this file. Saves as {filename}.crypt, if not specified otherwise.");
nupp.RegisterDescription("--infile", "Encrypt this file.");
nupp.RegisterConstraint("--infile", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--intext" }));
nupp.RegisterAbbreviation("-if", "--infile");
nupp.RegisterDescription("--ofile", "Use this filename for output if --infile is specified. Gets ignored otherwise.");
nupp.RegisterDescription("--ofile", "Write output in this file.");
nupp.RegisterConstraint("--ofile", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--ostdout", "--hash" }));
nupp.RegisterAbbreviation("-of", "--ofile");
nupp.RegisterAbbreviation("-o", "--ofile");
nupp.RegisterDescription("--ostdout", "Output of digested files will be dumped to stdout instead of a file.");
nupp.RegisterConstraint("--ostdout", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--ofile", "--hash" }));
nupp.RegisterDescription("--key", "Use this value as a password to extrapolate the encryption key. WARNING: Arguments may be logged by the system!");
nupp.RegisterConstraint("--key", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--keyfile", "--keyask", "--hash" }));
nupp.RegisterAbbreviation("-k", "--key");
ss << "Read in the first {KEYSIZE}(=" << GCrypt::BLOCK_SIZE << ") bits of this file and use that as an encryption key. WARNING: Arguments may be logged by the system!";
ss << "Read in the first {KEYSIZE}(=" << Block::BLOCK_SIZE_BITS << ") bits of this file and use that as an encryption key. WARNING: Arguments may be logged by the system!";
nupp.RegisterDescription("--keyfile", ss.str());
ss.str("");
nupp.RegisterConstraint("--keyfile", ParamConstraint(true, DATA_TYPE::STRING, {}, false, { "--key", "--keyask", "--hash" }));
@ -65,34 +64,52 @@ void CommandlineInterface::Init(int argc, const char* const* argv)
nupp.RegisterConstraint("--keyask", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--key", "--keyfile", "--hash" }));
nupp.RegisterAbbreviation("-ka", "--keyask");
nupp.RegisterDescription("--progress", "Print digestion progress to stdout. May be advisable for large files, as the cipher is rather slow.");
nupp.RegisterDescription("--progress", "Print digestion progress to stderr. May be advisable for large files, as the cipher is rather slow.");
nupp.RegisterConstraint("--progress", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {}));
nupp.RegisterAbbreviation("-p", "--progress");
nupp.RegisterDescription("--progress-interval", "Print digestion progress reports every these many data blocks.");
nupp.RegisterConstraint("--progress-interval", ParamConstraint(true, DATA_TYPE::INT, { "1000" }, true, {}));
nupp.RegisterDescription("--iobase-bytes", "Interpret and output ciphertexts as raw bytes.");
nupp.RegisterConstraint("--iobase-bytes", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-2", "--iobase-8", "--iobase-10", "--iobase-16", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterDescription("--iobase-2", "Interpret and format ciphertexts in base2");
nupp.RegisterConstraint("--iobase-2", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-8", "--iobase-10", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterConstraint("--iobase-2", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-bytes", "--iobase-8", "--iobase-10", "--iobase-16", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterDescription("--iobase-8", "Interpret and format ciphertexts in base8");
nupp.RegisterConstraint("--iobase-8", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-2", "--iobase-10", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterConstraint("--iobase-8", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-bytes", "--iobase-2", "--iobase-10", "--iobase-16", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterDescription("--iobase-10", "Interpret and format ciphertexts in base10");
nupp.RegisterConstraint("--iobase-10", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-2", "--iobase-8", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterConstraint("--iobase-10", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-bytes", "--iobase-2", "--iobase-8", "--iobase-16", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterDescription("--iobase-16", "Interpret and format ciphertexts in base16 (hex)");
nupp.RegisterConstraint("--iobase-16", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-bytes", "--iobase-2", "--iobase-8", "--iobase-64", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterDescription("--iobase-64", "Interpret and format ciphertexts in base64");
nupp.RegisterConstraint("--iobase-64", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-2", "--iobase-8", "--iobase-10", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterConstraint("--iobase-64", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-bytes", "--iobase-2", "--iobase-8", "--iobase-10", "--iobase-16", "--iobase-uwu", "--iobase-ugh" }));
nupp.RegisterDescription("--iobase-uwu", "Interpret and format ciphertexts in base uwu");
nupp.RegisterConstraint("--iobase-uwu", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-2", "--iobase-8", "--iobase-10", "--iobase-64", "--iobase-ugh" }));
nupp.RegisterConstraint("--iobase-uwu", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-bytes", "--iobase-2", "--iobase-8", "--iobase-10", "--iobase-16", "--iobase-64", "--iobase-ugh" }));
nupp.RegisterDescription("--iobase-ugh", "Interpret and format ciphertexts in base ugh");
nupp.RegisterConstraint("--iobase-ugh", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-2", "--iobase-8", "--iobase-10", "--iobase-64", "--iobase-uwu" }));
nupp.RegisterConstraint("--iobase-ugh", ParamConstraint(true, DATA_TYPE::VOID, {}, false, { "--iobase-bytes", "--iobase-2", "--iobase-8", "--iobase-10", "--iobase-16", "--iobase-64", "--iobase-uwu" }));
nupp.RegisterDescription("--version", "Will supply the version of ghettocrypt used.");
nupp.RegisterConstraint("--version", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {}));
nupp.RegisterAbbreviation("-v", "--version");
nupp.RegisterDescription("--lib-version", "Will supply the version of GCryptLib used.");
nupp.RegisterConstraint("--lib-version", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {}));
nupp.RegisterDescription("--cli-version", "Will supply the version of ghettocrypt-cli used.");
nupp.RegisterDescription("--cli-version", "Will supply the version of GCryptCLI used.");
nupp.RegisterConstraint("--cli-version", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {}));
nupp.RegisterAbbreviation("-v", "--cli-version");
nupp.RegisterDescription("--puffer-input", "Will read the entire input before beginning any digestion.");
nupp.RegisterConstraint("--puffer-input", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {}));
nupp.RegisterDescription("--puffer-output", "Will digest the entire data before initiating any output.");
nupp.RegisterConstraint("--puffer-output", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {}));
nupp.RegisterDescription("--no-newline", "Don't postfix stdout output with a newline");
nupp.RegisterConstraint("--no-newline", ParamConstraint(true, DATA_TYPE::VOID, {}, false, {}));
/* Now parse */
nupp.Parse(argc, argv);
@ -103,45 +120,69 @@ void CommandlineInterface::Init(int argc, const char* const* argv)
return;
}
Hazelnp::CmdArgsInterface& CommandlineInterface::Get()
{
Hazelnp::CmdArgsInterface& CommandlineInterface::Get() {
return nupp;
}
void CommandlineInterface::SpecialCompatibilityChecking()
{
// Encryption key
// Do we have EITHER --hash (no key required), --key, --keyask or --keyfile given?
if (
(!nupp.HasParam("--hash")) &&
(!nupp.HasParam("--key")) &&
(!nupp.HasParam("--keyfile")) &&
(!nupp.HasParam("--keyask"))
)
CrashWithMsg("No encryption key supplied! Please supply either --key, --keyfile, or --keyask!");
void CommandlineInterface::SpecialCompatibilityChecking() {
// Encryption input
// Do we have EITHER --intext or --infile?
if (
(!nupp.HasParam("--intext")) &&
(!nupp.HasParam("--infile"))
)
CrashWithMsg("No encryption input supplied! Please supply either --intext or --infile!");
// Encryption mode
// Active module
// Do we have EITHER --encrypt or --decrypt or --hash?
if (
(!nupp.HasParam("--generate-key")) &&
(!nupp.HasParam("--hash")) &&
(!nupp.HasParam("--encrypt")) &&
(!nupp.HasParam("--decrypt"))
)
CrashWithMsg("No encryption mode supplied! Please supply either --encrypt, --decrypt, or --hash!");
) {
CrashWithMsg("No module supplied! Please supply either --encrypt, --decrypt, --hash, or --generate-key!");
}
// Encryption key
// Do we have EITHER --hash (no key required), --generate-key (no key required), --key, --keyask or --keyfile given?
if (
(!nupp.HasParam("--hash")) &&
(!nupp.HasParam("--generate-key")) &&
(!nupp.HasParam("--key")) &&
(!nupp.HasParam("--keyfile")) &&
(!nupp.HasParam("--keyask"))
) {
CrashWithMsg("No encryption key supplied! Please supply either --key, --keyfile, or --keyask!");
}
// Check that, if supplied, filename strings are not empty.
if (
(nupp.HasParam("--ofile")) &&
(nupp["--ofile"].GetString().length() == 0)
) {
CrashWithMsg("Length of --ofile is zero! That can't be a valid path!");
}
if (
(nupp.HasParam("--ifile")) &&
(nupp["--ifile"].GetString().length() == 0)
) {
CrashWithMsg("Length of --ifile is zero! That can't be a valid path!");
}
if (
(nupp.HasParam("--keyfile")) &&
(nupp["--keyfile"].GetString().length() == 0)
) {
CrashWithMsg("Length of --keyfile is zero! That can't be a valid path!");
}
if (
(nupp.HasParam("--progress")) &&
(!nupp.HasParam("--puffer-input"))
) {
CrashWithMsg("--progress requires --puffer-input to work!");
}
return;
}
void CommandlineInterface::CrashWithMsg(const std::string& msg)
{
void CommandlineInterface::CrashWithMsg(const std::string& msg) {
std::cerr
<< nupp.GetBriefDescription()
<< std::endl
@ -151,16 +192,17 @@ void CommandlineInterface::CrashWithMsg(const std::string& msg)
exit(-1);
}
void CommandlineInterface::CatchVersionQueries()
{
if (nupp.HasParam("--version"))
{
std::cout << GHETTOCRYPT_VERSION << std::endl;
void CommandlineInterface::CatchVersionQueries() {
if (
(nupp.HasParam("--version")) ||
(nupp.HasParam("--cli-version"))
) {
std::cout << GCRYPTCLI_VERSION << std::endl;
exit(0);
}
else if (nupp.HasParam("--cli-version"))
else if (nupp.HasParam("--lib-version"))
{
std::cout << GHETTOCRYPTCLI_VERSION << std::endl;
std::cout << GCRYPT_VERSION << std::endl;
exit(0);
}

View File

@ -0,0 +1,186 @@
#include "Configuration.h"
#include "CommandlineInterface.h"
void Configuration::Parse() {
DecideModule();
DecideInputFrom();
DecideOutputTo();
DecideCiphertextFormat();
MapCiphertextFormatToIOBases();
return;
}
void Configuration::DecideModule() {
if (CommandlineInterface::Get().HasParam("--encrypt")) {
activeModule = MODULE::ENCRYPTION;
return;
}
else if (CommandlineInterface::Get().HasParam("--decrypt")) {
activeModule = MODULE::DECRYPTION;
return;
}
else if (CommandlineInterface::Get().HasParam("--hash")) {
activeModule = MODULE::HASH;
return;
}
else if (CommandlineInterface::Get().HasParam("--generate-key")) {
activeModule = MODULE::GENERATE_KEY;
return;
}
throw std::runtime_error("No module option found. Is the CLI parser configuration correct?.");
return;
}
void Configuration::DecideInputFrom() {
if (CommandlineInterface::Get().HasParam("--intext")) {
inputFrom = INPUT_FROM::PARAMETER;
}
else if (CommandlineInterface::Get().HasParam("--infile")) {
inputFrom = INPUT_FROM::FILE;
inputFilename = CommandlineInterface::Get()["--infile"].GetString();
}
else {
inputFrom = INPUT_FROM::STDIN;
}
return;
}
void Configuration::DecideOutputTo() {
if (CommandlineInterface::Get().HasParam("--ofile")) {
outputTo = OUTPUT_TO::FILE;
outputFilename = CommandlineInterface::Get()["--ofile"].GetString();
}
else {
outputTo = OUTPUT_TO::STDOUT;
}
return;
}
void Configuration::DecideCiphertextFormat() {
// Do we have any iobase explicitly specified?
if (CommandlineInterface::Get().HasParam("--iobase-bytes")) {
ciphertextFormat = IOBASE_FORMAT::BASE_BYTES;
}
else if (CommandlineInterface::Get().HasParam("--iobase-2")) {
ciphertextFormat = IOBASE_FORMAT::BASE_2;
}
else if (CommandlineInterface::Get().HasParam("--iobase-8")) {
ciphertextFormat = IOBASE_FORMAT::BASE_8;
}
else if (CommandlineInterface::Get().HasParam("--iobase-10")) {
ciphertextFormat = IOBASE_FORMAT::BASE_10;
}
else if (CommandlineInterface::Get().HasParam("--iobase-16")) {
ciphertextFormat = IOBASE_FORMAT::BASE_16;
}
else if (CommandlineInterface::Get().HasParam("--iobase-64")) {
ciphertextFormat = IOBASE_FORMAT::BASE_64;
}
else if (CommandlineInterface::Get().HasParam("--iobase-uwu")) {
ciphertextFormat = IOBASE_FORMAT::BASE_UWU;
}
else if (CommandlineInterface::Get().HasParam("--iobase-ugh")) {
ciphertextFormat = IOBASE_FORMAT::BASE_UGH;
}
// So we have no iobase explicitly specified.. Let's default..
// If we are encrypting,
else if (activeModule == MODULE::ENCRYPTION) {
// and input comes from a parameter,
// and output goes to stdout,
// let's assume base-16.
if (
(inputFrom == INPUT_FROM::PARAMETER) &&
(outputTo == OUTPUT_TO::STDOUT)
) {
ciphertextFormat = IOBASE_FORMAT::BASE_16;
}
// Any other case whilst encrypting, we'll assume base-bytes.
else {
ciphertextFormat = IOBASE_FORMAT::BASE_BYTES;
return;
}
}
// Else, if we are hashing,
else if (activeModule == MODULE::HASH) {
// output is always defaults to base 16
ciphertextFormat = IOBASE_FORMAT::BASE_16;
}
// Else, if we are decrypting,
else if (activeModule == MODULE::DECRYPTION) {
// and input comes from a parameter, we'll assume base-16.
if (inputFrom == INPUT_FROM::PARAMETER) {
ciphertextFormat = IOBASE_FORMAT::BASE_16;
}
// Any other case whilst decrypting, we'll assume base-bytes.
else {
ciphertextFormat = IOBASE_FORMAT::BASE_BYTES;
}
}
// Else, if we are generating a key,
else if (activeModule == MODULE::GENERATE_KEY) {
// and we're outputting to stdout, we'll use base-16.
if (outputTo == OUTPUT_TO::STDOUT) {
ciphertextFormat = IOBASE_FORMAT::BASE_16;
}
// else, we're outputting to a file, use base-bytes.
else {
ciphertextFormat = IOBASE_FORMAT::BASE_BYTES;
}
}
// Fallback: Bytes
else {
ciphertextFormat = IOBASE_FORMAT::BASE_BYTES;
}
return;
}
void Configuration::MapCiphertextFormatToIOBases() {
// Now, map the ciphertextFormat to either formatIn or formatOut.
switch (activeModule) {
// For encryption, keygen, and hashing:
// input is bytes and output is ciphertext
case MODULE::ENCRYPTION:
case MODULE::HASH:
case MODULE::GENERATE_KEY:
formatIn = IOBASE_FORMAT::BASE_BYTES;
formatOut = ciphertextFormat;
break;
// For decryption:
// input is ciphertext and output is bytes
case MODULE::DECRYPTION:
formatIn = ciphertextFormat;
formatOut = IOBASE_FORMAT::BASE_BYTES;
break;
}
return;
}
std::string Configuration::inputFilename;
std::string Configuration::outputFilename;
Configuration::MODULE Configuration::activeModule;
Configuration::IOBASE_FORMAT Configuration::formatIn;
Configuration::IOBASE_FORMAT Configuration::formatOut;
Configuration::IOBASE_FORMAT Configuration::ciphertextFormat;
Configuration::INPUT_FROM Configuration::inputFrom;
Configuration::OUTPUT_TO Configuration::outputTo;

View File

@ -0,0 +1,299 @@
#include "DataFormatter.h"
#include "Bases.h"
#include <GeneralUtility/BaseConversion.h>
#include <StringTools/StringTools.h>
#include <GCrypt/Util.h>
using namespace Leonetienne::GCrypt;
using namespace Leonetienne::StringTools;
using namespace Leonetienne::GeneralUtility;
std::string DataFormatter::FormatBlock(
const Block& block,
const Configuration::IOBASE_FORMAT base
) {
switch (base) {
case Configuration::IOBASE_FORMAT::BASE_BYTES:
return block.ToByteString();
case Configuration::IOBASE_FORMAT::BASE_2:
return block.ToBinaryString();
case Configuration::IOBASE_FORMAT::BASE_8:
return Bin2CustomBase(
block.ToBinaryString(),
BASE_8,
blockLengthByBase[base]
);
case Configuration::IOBASE_FORMAT::BASE_10:
return Bin2CustomBase(
block.ToBinaryString(),
BASE_10,
blockLengthByBase[base]
);
case Configuration::IOBASE_FORMAT::BASE_16:
return block.ToHexString();
case Configuration::IOBASE_FORMAT::BASE_64:
return Bin2CustomBase(
block.ToBinaryString(),
BASE_64,
blockLengthByBase[base]
);
case Configuration::IOBASE_FORMAT::BASE_UWU:
return Bin2CustomBase(
block.ToBinaryString(),
BASE_UWU,
blockLengthByBase[base],
" "
);
case Configuration::IOBASE_FORMAT::BASE_UGH:
return Bin2CustomBase(
block.ToBinaryString(),
BASE_UGH,
blockLengthByBase[base],
" "
);
default:
throw std::invalid_argument("FormatBlock(): Iobase not found! Oh no. Anyway.");
}
}
std::string DataFormatter::FormatBlocks(
const std::vector<Block>& blocks,
const Configuration::IOBASE_FORMAT base
) {
std::stringstream ss;
std::size_t i = 0;
for (const Block& block : blocks) {
ss << FormatBlock(block, base);
// If we are dealing with a multichar base, we must append a
// seperator (space), but only if its not the last block.
if (
(base == Configuration::IOBASE_FORMAT::BASE_UWU) ||
(base == Configuration::IOBASE_FORMAT::BASE_UGH)
) {
if (i++ != blocks.size()) {
ss << " ";
}
}
}
return ss.str();
}
Block DataFormatter::DecodeFormat(
const std::string& str,
const Configuration::IOBASE_FORMAT base
) {
Block b;
switch (base) {
case Configuration::IOBASE_FORMAT::BASE_BYTES:
b.FromByteString(str);
break;
case Configuration::IOBASE_FORMAT::BASE_2:
b.FromBinaryString(str);
break;
case Configuration::IOBASE_FORMAT::BASE_8:
b.FromBinaryString(
CustomBase2Bin(
str,
BASE_8
)
);
break;
case Configuration::IOBASE_FORMAT::BASE_10:
b.FromBinaryString(
CustomBase2Bin(
str,
BASE_10
)
);
break;
case Configuration::IOBASE_FORMAT::BASE_16:
b.FromHexString(str);
break;
case Configuration::IOBASE_FORMAT::BASE_64:
b.FromBinaryString(
CustomBase2Bin(
str,
BASE_64
)
);
break;
case Configuration::IOBASE_FORMAT::BASE_UWU:
b.FromBinaryString(
CustomBase2Bin(
str,
BASE_UWU,
" "
)
);
break;
case Configuration::IOBASE_FORMAT::BASE_UGH:
b.FromBinaryString(
CustomBase2Bin(
str,
BASE_UGH,
" "
)
);
break;
default:
throw std::invalid_argument("StringToBlock(): Iobase now found! Oh no. Anyway.");
}
return b;
}
std::vector<Block> DataFormatter::DecodeFormatMultiblock(
const std::string& str,
const Configuration::IOBASE_FORMAT base
) {
std::vector<Block> blocks;
// A block is this many digits wide, in encoding
const std::size_t blockWidth = blockLengthByBase[base];
// How many blocks are we dealing with here?
const std::size_t n_blocks = (str.length() / blockWidth) + 1;
blocks.reserve(n_blocks);
// Iterate over the string, and parse all blocks
// We now have to differentiate between single-char digit sets (like hex),
// and multi-char digit sets (like uwu):
switch (base) {
case Configuration::IOBASE_FORMAT::BASE_BYTES:
case Configuration::IOBASE_FORMAT::BASE_2:
case Configuration::IOBASE_FORMAT::BASE_8:
case Configuration::IOBASE_FORMAT::BASE_10:
case Configuration::IOBASE_FORMAT::BASE_16:
case Configuration::IOBASE_FORMAT::BASE_64:
// Easy case: Each digit is exactly one char in size.
// We can just calculate how many bytes we should read.
for (std::size_t i = 0; i < str.length(); i += blockWidth) {
const std::string subs = str.substr(i, blockWidth);
Block newBlock = DecodeFormat(
subs,
base
);
blocks.emplace_back(newBlock);
}
break;
case Configuration::IOBASE_FORMAT::BASE_UWU:
case Configuration::IOBASE_FORMAT::BASE_UGH: {
// Hard case: Each digit is n digits long. Digits may vary in length.
// They are seperated by spaces.
// We have to parse them...
std::size_t digitsPassed = 0;
std::size_t blockStart = 0;
for (std::size_t i = 0; i < str.length(); i++) {
if (str[i] == ' ') {
digitsPassed++;
if (digitsPassed == blockWidth) {
const std::string subs = str.substr(
blockStart,
i - blockStart
);
Block newBlock = DecodeFormat(
subs,
base
);
blocks.emplace_back(newBlock);
digitsPassed = 0;
blockStart = i+1;
}
}
}
// Here should never be any digits left. A formatted block should ALWAYS be full length.
break;
}
default:
throw std::invalid_argument("DataFormatter::StringToBlocks() has been passed an unknown base! No switch-case matched!");
break;
}
return blocks;
}
std::string DataFormatter::Bin2CustomBase(
const std::string& bin,
const std::vector<std::string>& customSet,
const std::size_t minLen,
const std::string& seperator
) {
std::stringstream ss;
// Translate to custom set
const std::vector<std::string> vec_base_custom =
Leonetienne::GeneralUtility::BaseConversion::BaseX_2_Y<std::string, std::vector<std::string>>(
bin,
"01",
customSet,
minLen
);
// Convert to string
for (std::size_t i = 0; i < vec_base_custom.size(); i++) {
ss << vec_base_custom[i];
if (i != vec_base_custom.size() - 1) {
ss << seperator;
}
}
return ss.str();
}
std::string DataFormatter::CustomBase2Bin(
const std::string& in,
const std::vector<std::string>& customSet,
const std::string& seperator
) {
// Split input into symbols
const std::vector<std::string> in_symbols = StringTools::Split(in, seperator);
// Translate to binary
std::string binary =
Leonetienne::GeneralUtility::BaseConversion::BaseX_2_Y<std::vector<std::string>, std::string>(
in_symbols,
customSet,
std::string("01"),
Block::BLOCK_SIZE_BITS
);
if (binary.length() != Block::BLOCK_SIZE_BITS) {
throw std::invalid_argument("DataFormatter::CustomBase2Bin() received input that doesn't translate to a bitstring of length 512!");
}
return binary;
}

View File

@ -0,0 +1,369 @@
#include "DataIngestionLayer.h"
#include "CommandlineInterface.h"
#include "DataFormatter.h"
#include "Bases.h"
#include <StringTools/StringTools.h>
#include <iostream>
#include <istream>
#include <fstream>
#include <sstream>
#include <cstring>
using namespace IO;
using namespace Leonetienne::StringTools;
void DataIngestionLayer::Init() {
// Set our istream
switch (Configuration::inputFrom) {
// Are we reading from stdin?
case Configuration::INPUT_FROM::STDIN:
// Redirect our istream to stdin
in = &std::cin;
break;
// Are we reading from a file?
case Configuration::INPUT_FROM::FILE:
// Open the file
ifs.open(
Configuration::inputFilename,
std::ios::in | std::ios::binary
);
// A little bit of error handling
if (!ifs.good()) {
throw std::runtime_error("Unable to open infilestream!");
}
// Redirect our istream to this infilestream
in = &ifs;
break;
// Are we reading from a parameter?
case Configuration::INPUT_FROM::PARAMETER:
// Create an instringstream with our parameter
iss = std::istringstream(
CommandlineInterface::Get()["--intext"].GetString()
);
// Redirect our istream to this instringstream
in = &iss;
break;
}
// Derive from our the current module if we're reading ciphertext or not
if (
(Configuration::activeModule == Configuration::MODULE::DECRYPTION)
) {
isReadingCiphertext = true;
}
else {
isReadingCiphertext = false;
}
initialized = true;
reachedEof = false;
nBlocksRead = 0;
return;
}
void DataIngestionLayer::Destruct() {
if (Configuration::inputFrom == Configuration::INPUT_FROM::FILE) {
ifs.close();
}
return;
}
void DataIngestionLayer::ReadBlock() {
if (!initialized) {
throw std::runtime_error("Attempted to read on uninitialized DataIngestionLayer!");
}
if (!reachedEof) {
// A block is this many digits wide, in encoding
const std::size_t blockWidth = blockLengthByBase[Configuration::formatIn];
// Iterate over the string, and parse all blocks
// We now have to differentiate between single-char digit sets (like hex),
// and multi-char digit sets (like uwu):
switch (Configuration::formatIn) {
case Configuration::IOBASE_FORMAT::BASE_BYTES:
case Configuration::IOBASE_FORMAT::BASE_2:
case Configuration::IOBASE_FORMAT::BASE_8:
case Configuration::IOBASE_FORMAT::BASE_10:
case Configuration::IOBASE_FORMAT::BASE_16:
case Configuration::IOBASE_FORMAT::BASE_64: {
// Easy case: Each digit is exactly one char in size.
// We can just calculate how many bytes we have to read.
// bytesRead is always of the correct length, 0-padded.
std::size_t n_bytes_read;
const std::string dataRead = ReadBytes(blockWidth, n_bytes_read);
// If we've read 0 bytes, this was the last block
// and it's completely empty. We can abort without doing anything.
// The ReadBytes function takes care of setting the reachedEof flag.
if (n_bytes_read == 0) {
return;
}
// If we are reading ciphertext
// make sure we've read enough bytes to compose a block.
if (
(isReadingCiphertext) &&
(n_bytes_read < blockWidth)
) {
throw std::runtime_error("DataIngestionLayer::ReadBlock() read an input-data fragment that is smaller than a data block should be. Is your cipher text incomplete?");
}
// This should decode to a block just like this.
Block newBlock;
// Special-case: We are reading cleartext (no ciphertext)
// cleartext is always base_bytes
if (!isReadingCiphertext) {
// When just reading cleartext-bytes, we also allow shorter strings
// than BLOCK_SIZE. These will just get zero-padded.
newBlock.FromTextString(dataRead);
}
else {
// Else: recode to a block.
newBlock = DataFormatter::DecodeFormat(
dataRead,
Configuration::formatIn
);
}
blocks.emplace(newBlock);
nBlocksRead++;
break;
}
case Configuration::IOBASE_FORMAT::BASE_UWU:
case Configuration::IOBASE_FORMAT::BASE_UGH: {
// The whole of Italy doesn't have as much spaghetti as this is...
// Hard case: Each digit is n digits long. Digits may vary in length.
// They are seperated by spaces.
// We have to parse them...
std::string overshoot = ""; // this is how much we've read too much in the last iteration
// Gets terminated by a break statement
while (1) {
// We'll read chunks of 64 bytes... This should be a good
// median, to also support small multi-byte-digit sets
std::size_t n_bytes_read = 0;
int lastDigitPos = -1; // Should point the the space BEFORE it. Relative to chunk.
std::size_t digitsCollected = 0;
std::stringstream digits;
bool foundBlock = false;
// Remember to prepend our overshoot from the previous iteration this chunk
std::string chunk = overshoot + ReadBytes(64, n_bytes_read);
// We should also strip all linebreaks from the chunk, that may be a result of manual stdin input.
chunk = StringTools::Replace(chunk, '\n', "");
// We can't just check for completeness by n_bytes_read...
// It can be any number of bytes, since any digit is n bytes long...
// Parse the 64-byte chunk string we've just fetched:
for (std::size_t i = 0; i < chunk.size(); i++) {
// If we are near the end, and have still not found a complete block, let's load an additional chunk
if (i == chunk.size() - 2) {
const std::string nextChunk = ReadBytes(64, n_bytes_read);
if (n_bytes_read != 0) {
chunk += StringTools::Replace(nextChunk, '\n', "");
}
}
// If i is on a space, or at the end of the chunk,
// and, at least one of i, or lastDigitPos is on a space,
if (
(
(chunk[i] == ' ') ||
(i == chunk.size() - 1)
) &&
(
(chunk[i] == ' ') ||
lastDigitPos >= 0 // This basically does the same as discribed, but safer, as its default value is -1.
)
){
digitsCollected++;
// We have found a digit. Let's store it away...
// We're putting them in a stringstream, to fit the format required by the data formatter...
// We have a slight edgecase if we're currently on the last char. Then we do NOT want to read one short.
// This is because we ususally stand on a seperator char (' '), which we do not want to extract. But in that case,
// in which we're standing on the last char, it could be not a seperator char.
// note: chunk[i] != ' ' can only be true if we're on the last char.
if (chunk[i] == ' ') {
digits <<
chunk.substr(
lastDigitPos + 1,
(int)i - lastDigitPos - 1
)
;
} else {
digits <<
chunk.substr(
lastDigitPos + 1,
(int)i - lastDigitPos
)
;
}
// Add a seperator, if its not the last
if (digitsCollected != blockWidth) {
digits << ' ';
}
lastDigitPos = i;
// Do we have enough digits to form a block?
if (digitsCollected == blockWidth) {
// We've found a complete block!
// Trim excess nullbytes off out digit string
const std::string digitString = std::string(
digits.str().data(),
strlen(digits.str().data())
);
// Decode it to a block object
const Block newBlock = DataFormatter::DecodeFormat(
digitString,
Configuration::formatIn
);
// Enqueue it to be processed by some module
blocks.emplace(newBlock);
nBlocksRead++;
foundBlock = true;
// Now we have to calculate how many bytes we've read TOO MANY.
// We have to trim this current chunk to be our new overshoot.
// If we still have more than a byte left, leave out the current seperator char
if (i < chunk.size() - 1) {
overshoot = chunk.substr(i+1); // Take all bytes from the next iterator, to the end
}
// Else: we are on the last char: there is no overshoot
else {
overshoot = "";
}
// Stop the for loop
break;
}
}
}
// Exit-condition:
// We have not found any block, not even any digit.
if ((!foundBlock) && (digitsCollected == 0)) {
break;
}
// Hard-abort: We have not finished reading a block
if (!foundBlock) {
throw std::runtime_error("DataIngestionLayer reached EOF whilst parsing multi-byte-digit block...");
}
}
break;
}
default:
throw std::invalid_argument("DataFormatter::StringToBlocks() has been passed an unknown base! No switch-case matched!");
}
}
return;
}
std::string DataIngestionLayer::ReadBytes(const std::size_t n, std::size_t& out_bytes_read) {
// Prepare a buffer to read to
char* buf = new char[n+1];
memset(buf, 0, (n+1) * sizeof(buf[0]));
// Read
in->read(buf, n * sizeof(buf[0]));
// Fetch how much we've read
out_bytes_read = in->gcount();
// Is this fewer bytes than got requested?
if (out_bytes_read < n) {
// Yes: EOF reached.
reachedEof = true;
}
// Translate buffer to a standard string
const std::string sbuf(buf, n);
delete[] buf;
// Return our buffer
return sbuf;
}
bool DataIngestionLayer::ReachedEOF() {
return reachedEof;
}
bool DataIngestionLayer::IsBlockReady() {
// We're not ready, if we haven't reached EOF, if we should puffer
// the input.
if (
(CommandlineInterface::Get().HasParam("--puffer-input")) &&
(!reachedEof)
) {
return false;
}
// If we're not puffering, just return whether or not
// we have any blocks...
return blocks.size() > 0;
}
bool DataIngestionLayer::IsFinished() {
return (reachedEof) && (blocks.size() == 0);
}
Block DataIngestionLayer::GetNextBlock() {
if (!IsBlockReady()) {
throw std::runtime_error("Attempted to get the next block, but there are none left!");
}
// Why... why not just return a T in pop()???
const Block popped = blocks.front();
blocks.pop();
return popped;
}
std::size_t DataIngestionLayer::NBlocksRead() {
return nBlocksRead;
}
std::istream* DataIngestionLayer::in;
std::ifstream DataIngestionLayer::ifs;
std::istringstream DataIngestionLayer::iss;
bool DataIngestionLayer::reachedEof = false;
bool DataIngestionLayer::initialized = false;
bool DataIngestionLayer::isReadingCiphertext;
std::size_t DataIngestionLayer::nBlocksRead = 0;
std::queue<Block> DataIngestionLayer::blocks;

View File

@ -0,0 +1,149 @@
#include "DataOutputLayer.h"
#include "DataFormatter.h"
#include "CommandlineInterface.h"
#include <ostream>
#include <fstream>
#include <iostream>
using namespace IO;
void DataOutputLayer::Init() {
// Set our ostream
switch (Configuration::outputTo) {
// Are we writing to stdout?
case Configuration::OUTPUT_TO::STDOUT:
// Redirect our ostream to stdout
out = &std::cout;
break;
// Are we writing to a file?
case Configuration::OUTPUT_TO::FILE:
// Open the file
ofs.open(
Configuration::outputFilename,
std::ios::out | std::ios::binary
);
// A little bit of error handling
if (!ofs.good()) {
throw std::runtime_error("Unable to open outfilestream!");
}
// Redirect our ostream to this outfilestream
out = &ofs;
break;
}
initialized = true;
reachedEof = false;
return;
}
void DataOutputLayer::Destruct() {
if (Configuration::outputTo == Configuration::OUTPUT_TO::FILE) {
ofs.close();
}
return;
}
void DataOutputLayer::Enqueue(const Block& block) {
blocks.emplace(block);
return;
}
void DataOutputLayer::WriteBlock() {
// Some error checking
if (!initialized) {
throw std::runtime_error("Attempted to write on uninitialized DataOutputLayer!");
}
// Check if we have any block to write
// and if we should (output-puffering)
// Basically: only output if we have anything to output, and
// if --puffer-output is given, only output once we have reachedEof.
if (
(blocks.size() > 0) &&
(
(!CommandlineInterface::Get().HasParam("--puffer-output")) ||
(reachedEof)
)
) {
// Fetch the block to write
const Block block = blocks.front();
blocks.pop();
// Recode it to our output format
const std::string formattedBlock =
DataFormatter::FormatBlock(
block,
Configuration::formatOut
);
// Dump it
// This way we avoid zerobytes getting trimmed off...
out->write(formattedBlock.data(), formattedBlock.length());
// If this is not the last block, and the used iobase set
// requires it, append a seperator
if (
(!IsFinished()) &&
(
(Configuration::formatOut == Configuration::IOBASE_FORMAT::BASE_UWU) ||
(Configuration::formatOut == Configuration::IOBASE_FORMAT::BASE_UGH)
)
) {
*out << " ";
}
AddTrailingLinebreakIfRequired();
out->flush();
}
return;
}
void DataOutputLayer::ReachedEOF() {
reachedEof = true;
// Add the trailing linebreak here aswell, as, if input is ciphertext,
// ReachedEOF() may only be called after all blocks are already written
AddTrailingLinebreakIfRequired();
return;
}
bool DataOutputLayer::IsFinished() {
return (reachedEof) && (blocks.size() == 0);
}
void DataOutputLayer::AddTrailingLinebreakIfRequired() {
// If we are finished, and are outputting to stdout,
// and input format is not bytes,
// and the user didn't specifically opt out, print a newline
if (
(IsFinished()) &&
(Configuration::outputTo == Configuration::OUTPUT_TO::STDOUT) &&
(Configuration::formatOut != Configuration::IOBASE_FORMAT::BASE_BYTES) &&
(!CommandlineInterface::Get().HasParam("--no-newline"))
) {
*out << std::endl;
out->flush();
}
return;
}
std::ostream* DataOutputLayer::out;
std::ofstream DataOutputLayer::ofs;
bool DataOutputLayer::reachedEof = false;
bool DataOutputLayer::initialized = false;
std::queue<Block> DataOutputLayer::blocks;

View File

@ -0,0 +1,107 @@
#include "KeyManager.h"
#include "CommandlineInterface.h"
#include "Configuration.h"
#include <cstring>
#include <iostream>
// Required for hidden password input
#if defined _WIN32 || defined _WIN64
#include <Windows.h>
#elif defined __GNUG__
#include <termios.h>
#include <unistd.h>
#endif
void KeyManager::PrepareKey() {
// Special-case: We are hashing:
// no key needed.
if (Configuration::activeModule == Configuration::MODULE::HASH) {
return;
}
// Special-case: We are generating a keyfile:
// generate a random key from hardware events.
else if (Configuration::activeModule == Configuration::MODULE::GENERATE_KEY) {
key = Key::Random();
return;
}
// Else:
// Is a password passed on the command line?
else if (CommandlineInterface::Get().HasParam("--key")) {
// Fetch it, and hash it to a key
std::string password = CommandlineInterface::Get()["--key"].GetString();
key = Key::FromPassword(password);
// Don't forget to zero string memory.
memset(password.data(), 0, password.size());
return;
}
// Should we prompt for a password on stdin?
else if (CommandlineInterface::Get().HasParam("--keyask")) {
// Create a password prompt
std::string password = PasswordPrompt();
key = Key::FromPassword(password);
// Don't forget to zero string memory.
memset(password.data(), 0, password.size());
return;
}
// Else:
// Should we read from a keyfile?
else if (CommandlineInterface::Get().HasParam("--keyfile")) {
// Fetch the filename, and read it
const std::string filepath = CommandlineInterface::Get()["--keyfile"].GetString();
key = Key::LoadFromFile(filepath);
return;
}
throw std::runtime_error("No key option found. Is the CLI parser configuration correct?.");
return;
}
const Key& KeyManager::GetKey() {
return key;
}
std::string KeyManager::PasswordPrompt() {
// Disable stdin-echo
#if defined _WIN32 || defined _WIN64
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode = 0;
GetConsoleMode(hStdin, &mode);
SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT));
#elif defined __GNUG__
termios oldt;
tcgetattr(STDIN_FILENO, &oldt);
termios newt = oldt;
newt.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
#endif
// Prompt stdin
std::string password;
std::cin >> password;
// Restore previous config
#if defined _WIN32 || defined _WIN64
SetConsoleMode(hStdin, mode);
#elif defined __GNUG__
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
#endif
return password;
}
Key KeyManager::key;

View File

@ -0,0 +1,67 @@
#include "ModuleDecryption.h"
#include "DataIngestionLayer.h"
#include "DataOutputLayer.h"
#include "ProgressPrinter.h"
#include "KeyManager.h"
#include <GCrypt/GCipher.h>
using namespace Module;
using namespace Leonetienne::GCrypt;
void Decryption::Run() {
// Initialize the data ingestion layer
IO::DataIngestionLayer::Init();
// Initialize the data output layer
IO::DataOutputLayer::Init();
// Initialize a cipher
GCipher cipher(
KeyManager::GetKey(),
GCipher::DIRECTION::DECIPHER
);
std::size_t nBlocksDigested = 0;
while (!IO::DataOutputLayer::IsFinished()) {
// Read in new blocks, if not reached eof
if (!IO::DataIngestionLayer::ReachedEOF()) {
IO::DataIngestionLayer::ReadBlock();
}
// Process a block, if one is ready
if (IO::DataIngestionLayer::IsBlockReady()) {
// Print progress, if appropriate
ProgressPrinter::PrintIfAppropriate(
"Decrypting",
nBlocksDigested,
IO::DataIngestionLayer::NBlocksRead()
);
const Block cleartext = IO::DataIngestionLayer::GetNextBlock();
const Block ciphertext = cipher.Digest(cleartext);
nBlocksDigested++;
// Enqueue the block for output
IO::DataOutputLayer::Enqueue(ciphertext);
}
// Tell the data output layer that it received the
// last block, if it did
if (IO::DataIngestionLayer::IsFinished()) {
IO::DataOutputLayer::ReachedEOF();
}
// Attempt to write a block
IO::DataOutputLayer::WriteBlock();
}
// Destruct the data ingestion layer
IO::DataIngestionLayer::Destruct();
// Destruct the data output layer
IO::DataOutputLayer::Destruct();
return;
}

View File

@ -0,0 +1,68 @@
#include "ModuleEncryption.h"
#include "DataIngestionLayer.h"
#include "DataOutputLayer.h"
#include "KeyManager.h"
#include "ProgressPrinter.h"
#include <GCrypt/GCipher.h>
#include <iostream>
using namespace Module;
using namespace Leonetienne::GCrypt;
void Encryption::Run() {
// Initialize the data ingestion layer
IO::DataIngestionLayer::Init();
// Initialize the data output layer
IO::DataOutputLayer::Init();
// Initialize a cipher
GCipher cipher(
KeyManager::GetKey(),
GCipher::DIRECTION::ENCIPHER
);
std::size_t nBlocksDigested = 0;
while (!IO::DataOutputLayer::IsFinished()) {
// Read in new blocks, if not reached eof
if (!IO::DataIngestionLayer::ReachedEOF()) {
IO::DataIngestionLayer::ReadBlock();
}
// Process a block, if one is ready
if (IO::DataIngestionLayer::IsBlockReady()) {
// Print progress, if appropriate
ProgressPrinter::PrintIfAppropriate(
"Encrypting",
nBlocksDigested,
IO::DataIngestionLayer::NBlocksRead()
);
const Block cleartext = IO::DataIngestionLayer::GetNextBlock();
const Block ciphertext = cipher.Digest(cleartext);
nBlocksDigested++;
// Enqueue the block for output
IO::DataOutputLayer::Enqueue(ciphertext);
}
// Tell the data output layer that it just received the
// last block, if it did
if (IO::DataIngestionLayer::IsFinished()) {
IO::DataOutputLayer::ReachedEOF();
}
// Attempt to write a block
IO::DataOutputLayer::WriteBlock();
}
// Destruct the data ingestion layer
IO::DataIngestionLayer::Destruct();
// Destruct the data output layer
IO::DataOutputLayer::Destruct();
return;
}

View File

@ -0,0 +1,32 @@
#include "ModuleGenerateKey.h"
#include "DataOutputLayer.h"
#include "KeyManager.h"
#include "CommandlineInterface.h"
using namespace Leonetienne::GCrypt;
using namespace Module;
void GenerateKey::Run() {
// Initialize the data output layer
IO::DataOutputLayer::Init();
// Enqueue our single block of data
IO::DataOutputLayer::Enqueue(KeyManager::GetKey());
// Tell the data output layer, that is has received all blocks
IO::DataOutputLayer::ReachedEOF();
// Tell it to write all blocks
// (a single call should suffice, but a while-loop is the proper
// way to do it)
while (!IO::DataOutputLayer::IsFinished()) {
IO::DataOutputLayer::WriteBlock();
}
// Destruct the data output layer
IO::DataOutputLayer::Destruct();
return;
}

View File

@ -0,0 +1,64 @@
#include "ModuleHashing.h"
#include "DataIngestionLayer.h"
#include "DataOutputLayer.h"
#include "ProgressPrinter.h"
#include "KeyManager.h"
#include <GCrypt/GHash.h>
using namespace Module;
using namespace Leonetienne::GCrypt;
void Hashing::Run() {
// Initialize the data ingestion layer
IO::DataIngestionLayer::Init();
// Initialize the data output layer
IO::DataOutputLayer::Init();
// Initialize a ghasher
GHash hasher;
// Read in new blocks, if not reached eof
std::size_t nBlocksDigested = 0;
while (!IO::DataIngestionLayer::IsFinished()) {
if (!IO::DataIngestionLayer::ReachedEOF()) {
IO::DataIngestionLayer::ReadBlock();
}
// Process a block, if one is ready
if (IO::DataIngestionLayer::IsBlockReady()) {
// Print progress, if appropriate
ProgressPrinter::PrintIfAppropriate(
"Hashing",
nBlocksDigested,
IO::DataIngestionLayer::NBlocksRead()
);
const Block cleartext = IO::DataIngestionLayer::GetNextBlock();
hasher.Digest(cleartext);
nBlocksDigested++;
}
}
// Wait until we've finished digesting all blocks
// Enqueue that single block (the hash result) to the output layer
IO::DataOutputLayer::Enqueue(hasher.GetHashsum());
// Tell the data output layer that that was the last block
IO::DataOutputLayer::ReachedEOF();
// Dump it
while (!IO::DataOutputLayer::IsFinished()) {
IO::DataOutputLayer::WriteBlock();
}
// Destruct the data ingestion layer
IO::DataIngestionLayer::Destruct();
// Destruct the data output layer
IO::DataOutputLayer::Destruct();
return;
}

View File

@ -0,0 +1,28 @@
#include "ProgressPrinter.h"
#include "CommandlineInterface.h"
#include <iostream>
void ProgressPrinter::PrintIfAppropriate(
const std::string& message,
const std::size_t current,
const std::size_t target
) {
if (
(CommandlineInterface::Get().HasParam("--progress")) &&
(current % CommandlineInterface::Get()["--progress-interval"].GetInt32() == 0)
) {
std::cerr
<< message
<< "... (Block "
<< current + 1
<< " / "
<< target
<< " - " << ((float)(current+1)*100 / target)
<< "%)"
<< std::endl
;
}
return;
}

View File

@ -1,420 +1,41 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <sstream>
#include <vector>
#include <GCrypt/Util.h>
#include <GCrypt/Config.h>
#include <GCrypt/Cipher.h>
#include <GCrypt/Flexblock.h>
#include <GCrypt/Block.h>
#include "CommandlineInterface.h"
#include "Bases.h"
#include <GeneralUtility/BaseConversion.h>
#include <StringTools/StringTools.h>
// Required for hidden password input
#if defined _WIN32 || defined _WIN64
#include <Windows.h>
#elif defined __GNUG__
#include <termios.h>
#include <unistd.h>
#endif
using namespace ::Leonetienne::GCrypt;
using namespace ::Leonetienne::StringTools;
using namespace ::Leonetienne::GeneralUtility;
enum class OPERATION_MODE {
ENCRYPT,
DECRYPT,
HASH
};
namespace {
inline std::string Bin2CustomBase(const std::string& bin, const std::vector<std::string>& customSet, const std::string& seperator = "")
{
std::stringstream ss;
// Translate to custom set
const std::vector<std::string> vec_base_custom =
Leonetienne::GeneralUtility::BaseConversion::BaseX_2_Y<std::string, std::vector<std::string>>(bin, "01", customSet);
// Convert to string
for (std::size_t i = 0; i < vec_base_custom.size(); i++)
{
ss << vec_base_custom[i];
if (i != vec_base_custom.size() - 1)
ss << seperator;
}
return ss.str();
}
inline std::string CustomBase2Bin(const std::string& in, const std::vector<std::string>& customSet, const std::string& seperator = "")
{
// Split input into symbols
const std::vector<std::string> in_symbols = StringTools::Split(in, seperator);
// Translate to binary
std::string binary =
Leonetienne::GeneralUtility::BaseConversion::BaseX_2_Y<std::vector<std::string>, std::string>(in_symbols, customSet, std::string("01"));
// Apply padding to be a multiple of BLOCK_SIZE
// Count how many bits we need
std::size_t required_size = 0;
while (required_size < binary.length())
required_size += BLOCK_SIZE;
// Create a string of that many zeroes
std::string padding = "";
for (std::size_t i = binary.length(); i < required_size; i++)
padding += "0";
// Prepend it to our bitstring
binary = padding + binary;
return binary;
}
}
//! Will prompt a user password from stdin, hiding the input
std::string PasswordPrompt()
{
// Disable stdin-echo
#if defined _WIN32 || defined _WIN64
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode = 0;
GetConsoleMode(hStdin, &mode);
SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT));
#elif defined __GNUG__
termios oldt;
tcgetattr(STDIN_FILENO, &oldt);
termios newt = oldt;
newt.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
#endif
// Prompt stdin
std::string key;
std::cin >> key;
// Restore previous config
#if defined _WIN32 || defined _WIN64
SetConsoleMode(hStdin, mode);
#elif defined __GNUG__
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
#endif
// Return input
return key;
}
Block GetEncryptionKey()
{
// Easy-case: key supplied as param
if (CommandlineInterface::Get().HasParam("--key"))
return PasswordToKey(CommandlineInterface::Get()["--key"].GetString());
// Case: Ask for key
else if (CommandlineInterface::Get().HasParam("--keyask"))
return PasswordToKey(PasswordPrompt());
// Case: Read key from file
else if (CommandlineInterface::Get().HasParam("--keyfile"))
{
const std::string keyfilepath = CommandlineInterface::Get()["--keyfile"].GetString();
// Read this many chars
const std::size_t maxChars = BLOCK_SIZE / 8;
// Open ifilestream for keyfile
std::ifstream ifs(keyfilepath, std::ios::in | std::ios::binary);
// Is the file open now? Or were there any issues...
if (!ifs.good())
{
std::cerr << "Unable to open ifilestream for keyfile \"" << keyfilepath << "\"! Aborting..." << std::endl;
exit(-1);
}
// Read these chars to buffer
char* ckeyfileContent = new char[maxChars];
memset(ckeyfileContent, 0, maxChars * sizeof(char));
ifs.read(ckeyfileContent, maxChars);
ifs.close();
// 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;
// Return it
return key;
}
// Unreachable
throw std::runtime_error("This code should not have been reached. Most likely, the cli argument parser failed making sure at least one key method was supplied.");
}
const Flexblock GetInputText(const OPERATION_MODE operationMode)
{
// Easy-case: input text supplied as param
if (CommandlineInterface::Get().HasParam("--intext")) {
switch (operationMode) {
// Encryption, or hashing mode: We want to return the text as-is, as bits
case OPERATION_MODE::ENCRYPT:
case OPERATION_MODE::HASH:
return StringToBits(CommandlineInterface::Get()["--intext"].GetString());
// Decryption mode: We need to first convert our input to a bitstring
case OPERATION_MODE::DECRYPT:
const std::string userInput = CommandlineInterface::Get()["--intext"].GetString();
// Are we using iobase 2?
if (CommandlineInterface::Get().HasParam("--iobase-2"))
// Yes: convert to binary from base 2
return CustomBase2Bin(userInput, BASE_2);
// Are we using iobase 8?
else if (CommandlineInterface::Get().HasParam("--iobase-8"))
// Yes: convert to binary from base 8
return CustomBase2Bin(userInput, BASE_8);
// Are we using iobase 10?
else if (CommandlineInterface::Get().HasParam("--iobase-10"))
// Yes: convert to binary from base 10
return CustomBase2Bin(userInput, BASE_10);
// Are we using iobase 64?
else if (CommandlineInterface::Get().HasParam("--iobase-64"))
// Yes: convert to binary from base 64
return CustomBase2Bin(userInput, BASE_64);
// Are we using iobase uwu?
else if (CommandlineInterface::Get().HasParam("--iobase-uwu"))
// Yes: convert to binary from base uwu
return CustomBase2Bin(userInput, BASE_UWU, " ");
// Are we using iobase emoji?
else if (CommandlineInterface::Get().HasParam("--iobase-ugh"))
// Yes: convert to binary from base ugh
return CustomBase2Bin(userInput, BASE_UGH, " ");
// Else, no special output format specified, use hex
else
return HexstringToBits(userInput);
}
}
// Case: Read from file
else if (CommandlineInterface::Get().HasParam("--infile"))
return ReadFileToBits(CommandlineInterface::Get()["--infile"].GetString());
// Unreachable
throw std::runtime_error("This code should not have been reached. Most likely, the cli argument parser failed making sure at least one input method was supplied.");
}
const std::string GetOutfileName(const OPERATION_MODE operationMode)
{
// Do we have an output file name specified?
// Use it.
if (CommandlineInterface::Get().HasParam("--ofile"))
return CommandlineInterface::Get()["--ofile"].GetString();
// Else: append a custom postfix to the inputs filename
else
return CommandlineInterface::Get()["--infile"].GetString() +
(operationMode == OPERATION_MODE::ENCRYPT ? ".crypt" : ".plain");
}
int main(int argc, char** argv)
{
// Init cmdargs
CommandlineInterface::Init(argc, argv);
// Get operation modes
OPERATION_MODE opMode;
if (CommandlineInterface::Get().HasParam("--encrypt"))
opMode = OPERATION_MODE::ENCRYPT;
else if (CommandlineInterface::Get().HasParam("--hash"))
opMode = OPERATION_MODE::HASH;
else if (CommandlineInterface::Get().HasParam("--decrypt"))
opMode = OPERATION_MODE::DECRYPT;
else
// Unreachable
throw std::runtime_error("This code should not have been reached. Most likely, the cli argument parser failed making sure at least one mode of operation (-e, -d, -h) was supplied.");
const bool isFileMode = CommandlineInterface::Get().HasParam("--infile");
const bool printDigestionProgress = CommandlineInterface::Get().HasParam("--progress");
// Digest
Flexblock output;
switch (opMode) {
case OPERATION_MODE::ENCRYPT: {
// Gather input and key
const Block encryptionKey = GetEncryptionKey();
const Flexblock input = GetInputText(opMode);
// Create cipher
Cipher cipher(encryptionKey);
// Run it
output = cipher.Encipher(input, printDigestionProgress);
break;
}
case OPERATION_MODE::DECRYPT: {
// Gather input and key
const Block encryptionKey = GetEncryptionKey();
const Flexblock input = GetInputText(opMode);
// Create cipher
Cipher cipher(encryptionKey);
// Run it
output = cipher.Decipher(input, printDigestionProgress);
break;
}
case OPERATION_MODE::HASH: {
// Gather input
Flexblock input = GetInputText(opMode);
// Also add an extra left-padded block with the length of the input,
// to make sure, H(n) != H(n + 0x0)
input += PadStringToLength(Block(input.size()).to_string(), BLOCK_SIZE, '0');
// Derive an encryption key
// This is quiet sensitive, because it absolutely MUST be impossible
// to acquire K without already knowing M.
// This alone is making this cipher a one-way function.
// Just reducing the output is not a solution, because: If len(input) <= BLOCK_SIZE: H(m) = E(m)_k ^ k, with k being 0 <= k <= BLOCK_SIZE. That's not good.
//
// So here's what we're doing:
// - We're going to take the first BLOCK_SIZE bits of the input (it's length is at least that because of the size embedding)
// - We're creating a sudo-random initialization vector, seeded with this block.
// - That's our encryption key.
// This way, the encryption key can only be obtained by already knowing the first BLOCK_SIZE bits of the cleartext.
// By not taking in ALL the bits for deriving a key, we're saving a LOT on performance, as otherwise we would basically be encrypting the input THREE times.
// Why create an initialization vector at all? To make sure, even an input of only zeroes would provide a strong key
const Block encryptionKey = InitializationVector(
Block(input.substr(0, BLOCK_SIZE))
);
// Create cipher
Cipher cipher(encryptionKey);
// Run it
output = cipher.Encipher(input, printDigestionProgress);
if (printDigestionProgress) {
std::cout << "Done encrypting... Applying reduction function on ciphertext..." << std::endl;
std::cout << "This will take about just as long..." << std::endl;
}
// Reduce output to a single block
output = ReductionFunction_Flexblock2Block(output).to_string();
break;
}
}
// Now output the darn thing.
// Outputting to a file (only if not hashing)
if ((isFileMode) && (opMode != OPERATION_MODE::HASH))
{
// File mode is a bit different.
// Dump to stdout?
if (CommandlineInterface::Get().HasParam("--ostdout"))
{
const std::string outstr = BitsToBytes(output);
// We have to print char-by-char to prevent a nullbyte terminating output.
for (std::size_t i = 0; i < outstr.size(); i++)
std::cout << outstr[i];
}
// Else: Dump to file
else
{
const std::string outfileName = GetOutfileName(opMode);
WriteBitsToFile(outfileName, output);
}
}
// Else: we are just dealing with a string, or want to print a hashsum
else
{
switch (opMode) {
case OPERATION_MODE::ENCRYPT:
case OPERATION_MODE::HASH: {
// We are encrypting or hashing:
// Output to stdout as a formatted bytestring
std::string formattedCiphertext;
// Are we using iobase 2?
if (CommandlineInterface::Get().HasParam("--iobase-2"))
// Yes: convert hex to base 2
formattedCiphertext = Bin2CustomBase(output, BASE_2);
// Are we using iobase 8?
else if (CommandlineInterface::Get().HasParam("--iobase-8"))
// Yes: convert binary to base 8
formattedCiphertext = Bin2CustomBase(output, BASE_8);
// Are we using iobase 10?
else if (CommandlineInterface::Get().HasParam("--iobase-10"))
// Yes: convert binary to base 10
formattedCiphertext = Bin2CustomBase(output, BASE_10);
// Are we using iobase 64?
else if (CommandlineInterface::Get().HasParam("--iobase-64"))
// Yes: convert binary to base 64
formattedCiphertext = Bin2CustomBase(output, BASE_64);
// Are we using iobase uwu?
else if (CommandlineInterface::Get().HasParam("--iobase-uwu"))
// Yes: convert binary to base 64
formattedCiphertext = Bin2CustomBase(output, BASE_UWU, " ");
// Are we using iobase ugh?
else if (CommandlineInterface::Get().HasParam("--iobase-ugh"))
// Yes: convert binary to base 64
formattedCiphertext = Bin2CustomBase(output, BASE_UGH, " ");
// Else, no special output format specified, use hex
else
formattedCiphertext = BitsToHexstring(output);
std::cout << formattedCiphertext << std::endl;
break;
}
case OPERATION_MODE::DECRYPT:
// We are just decrypting:
// Just print it string-formatted
std::cout << BitsToString(output) << std::endl;
break;
}
}
return 0;
}
#include "CommandlineInterface.h"
#include "Configuration.h"
#include "KeyManager.h"
#include "ModuleGenerateKey.h"
#include "ModuleEncryption.h"
#include "ModuleDecryption.h"
#include "ModuleHashing.h"
int main(int argc, char* const* argv) {
// Init cli args
CommandlineInterface::Init(argc, argv);
// Parse configuration
Configuration::Parse();
// Prepare the key
KeyManager::PrepareKey();
// Launch our module
switch (Configuration::activeModule) {
case Configuration::MODULE::ENCRYPTION:
Module::Encryption::Run();
break;
case Configuration::MODULE::DECRYPTION:
Module::Decryption::Run();
break;
case Configuration::MODULE::GENERATE_KEY:
Module::GenerateKey::Run();
break;
case Configuration::MODULE::HASH:
Module::Hashing::Run();
break;
}
return 0;
}

17965
GCryptCLI/test/Catch2.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
#include <DataFormatter.h>
#include <GCrypt/Key.h>
#include <GCrypt/GPrng.h>
#include "Catch2.h"
using namespace Leonetienne::GCrypt;
// Tests that recoding iobase formats works for individual blocks, with random data
TEMPLATE_TEST_CASE_SIG(
__FILE__"Data formats can be converted, without changing in value, with indivudual blocks, with random data",
"[DataFormatter]",
((Configuration::IOBASE_FORMAT formatUnderTest), formatUnderTest),
Configuration::IOBASE_FORMAT::BASE_BYTES,
Configuration::IOBASE_FORMAT::BASE_2,
Configuration::IOBASE_FORMAT::BASE_8,
Configuration::IOBASE_FORMAT::BASE_10,
Configuration::IOBASE_FORMAT::BASE_16,
Configuration::IOBASE_FORMAT::BASE_64,
Configuration::IOBASE_FORMAT::BASE_UWU,
Configuration::IOBASE_FORMAT::BASE_UGH
) {
// Let's use a GPrng instead of Key::Random,
// because Key::Random is rather slow (because it's using hardware events).
// We just want randomized data for tests...
GPrng prng(Key::Random());
// Let's try 10 different random blocks per test
for (std::size_t i = 0; i < 10; i++) {
// Setup
const Block b_initial = prng.GetBlock();
// Exercise
// Convert to a custom base
const std::string b_format = DataFormatter::FormatBlock(
b_initial,
formatUnderTest
);
// Convert back to a block
const Block b_retrieved = DataFormatter::DecodeFormat(
b_format,
formatUnderTest
);
// Verify
// Do b_initial and b_retrieved match?
REQUIRE(b_initial == b_retrieved);
}
}
// Tests that recoding iobase format works
TEMPLATE_TEST_CASE_SIG(
__FILE__"Data formats can be converted, without changing in value, with indivudual blocks, with very little data (lots of nullbytes)",
"[DataFormatter]",
((Configuration::IOBASE_FORMAT formatUnderTest), formatUnderTest),
Configuration::IOBASE_FORMAT::BASE_BYTES,
Configuration::IOBASE_FORMAT::BASE_2,
Configuration::IOBASE_FORMAT::BASE_8,
Configuration::IOBASE_FORMAT::BASE_10,
Configuration::IOBASE_FORMAT::BASE_16,
Configuration::IOBASE_FORMAT::BASE_64,
Configuration::IOBASE_FORMAT::BASE_UWU,
Configuration::IOBASE_FORMAT::BASE_UGH
) {
// Setup
Block b_initial;
b_initial.FromTextString("Heyu");
// Exercise
// Convert to a custom base
const std::string b_format = DataFormatter::FormatBlock(
b_initial,
formatUnderTest
);
// Convert back to a block
const Block b_retrieved = DataFormatter::DecodeFormat(
b_format,
formatUnderTest
);
// Verify
// Do b_initial and b_retrieved match?
REQUIRE(b_initial == b_retrieved);
}

2
GCryptCLI/test/main.cpp Normal file
View File

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "Catch2.h"

View File

@ -15,6 +15,11 @@ target_include_directories(${PROJECT_NAME} PRIVATE
include
)
target_compile_options(${PROJECT_NAME} PRIVATE
-Werror
-fdiagnostics-color=always
)
#########
# Tests #
#########
@ -29,7 +34,13 @@ target_include_directories(test PRIVATE
include
)
## Move test images to build dir
target_compile_options(test PRIVATE
-Werror
-fdiagnostics-color=always
)
## Move test assest to build dir
ADD_CUSTOM_COMMAND(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
@ -37,16 +48,39 @@ ADD_CUSTOM_COMMAND(
)
##############
# Executable #
##############
FILE(GLOB exec_src exec/*.cpp)
add_executable(exec
${exec_src}
)
target_link_libraries(exec ${PROJECT_NAME})
target_include_directories(exec PRIVATE
###############
# Executables #
###############
FILE(GLOB bmpp_src exec/BmpPP/BmpPP/src/*.cpp)
FILE(GLOB eule_src exec/Eule/Eule/src/*.cpp)
add_compile_definitions(_EULE_NO_INTRINSICS_)
include_directories(
include
exec/BmpPP/BmpPP/include
exec/Eule/Eule/include
)
## Move exec assets to build dir
ADD_CUSTOM_COMMAND(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/exec/execAssets/ $<TARGET_FILE_DIR:${PROJECT_NAME}>/execAssets/
)
function(DECLARE_EXEC_EXAMPLE name)
add_executable(example-${name} exec/${name}.cpp ${bmpp_src} ${eule_src})
target_link_libraries(example-${name} ${PROJECT_NAME})
target_compile_options(example-${name} PRIVATE -Werror -fdiagnostics-color=always)
endfunction()
# These are the names of the cpp files in /exec/, without the ".cpp".
DECLARE_EXEC_EXAMPLE(encrypt-decrypt-files)
DECLARE_EXEC_EXAMPLE(encrypt-decrypt-strings)
DECLARE_EXEC_EXAMPLE(benchmark-encryption)
DECLARE_EXEC_EXAMPLE(benchmark-prng)
DECLARE_EXEC_EXAMPLE(visualize-singleblock-diffusion)
DECLARE_EXEC_EXAMPLE(visualize-multiblock-diffusion)
DECLARE_EXEC_EXAMPLE(visualize-extreme-input-diffusion)
DECLARE_EXEC_EXAMPLE(visualize-prng-distribution)
DECLARE_EXEC_EXAMPLE(visualize-hashing-distribution)

View File

@ -0,0 +1,21 @@
Old way (400 rounds, no matrix mult): 38.01s
- 400 was the minimum for good diffusion
New way (10 rounds, with matrix mult): 1.16s
- 10 rounds now give sufficient diffusion
- still using bitsets and strings
With new block class (instead of bitsets) (10 rounds): 0.35s
- still partially using bitsets and strings
With new block class (6 rounds, still good diffusion): 0.21s
With new templated block class (full block and half block): 0.14s
- finally no more bitsets
With new sbox, reduction, and expansion function: 0.03s
With additional jumbling in feistel rounds: 0.03s
With direct file reading/writing from data blocks: 0.02s

View File

@ -0,0 +1,10 @@
all:
# Copy all but the header of the readme here
tail ../../readme.md -n +2 > index.md
#
# Run doxygen
doxygen doxyfig
#
# Cleanup index.md
rm index.md

View File

@ -1,11 +0,0 @@
#!zsh
# Copy repository readme here to be used as a cover page
tail ../../readme.md -n +2 > index.md
# Run doxygen
doxygen doxyfig
# Cleanup index.md
rm -f index.md

View File

@ -0,0 +1,23 @@
#ifndef GCRYPTEXAMPLE_BENCHMARK_H
#define GCRYPTEXAMPLE_BENCHMARK_H
#include <functional>
#include <chrono>
#include <iostream>
void Benchmark(const std::string& brief, std::function<void()> toBenchmark) {
std::cout << "Benchmarking " << brief << "..." << std::endl;
auto start = std::chrono::steady_clock::now();
toBenchmark();
auto end = std::chrono::steady_clock::now();
double seconds = (double)std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 1000.0;
std::cout << seconds << " seconds." << std::endl << std::endl;
return;
}
#endif

1
GCryptLib/exec/BmpPP Submodule

@ -0,0 +1 @@
Subproject commit 522f9551ae31215cf63fcad88cacbfc8c818ccaf

1
GCryptLib/exec/Eule Submodule

@ -0,0 +1 @@
Subproject commit 1bfd756aa8bd1d484857735d63b4fdf8a15bbed5

View File

@ -0,0 +1,58 @@
#ifndef GCRYPTEXAMPLE_VISUALIZE_H
#define GCRYPTEXAMPLE_VISUALIZE_H
#include <GCrypt/Block.h>
#include <BmpPP/Bmp.h>
#include <string>
using namespace Leonetienne::GCrypt;
using namespace Leonetienne::Eule;
using namespace Leonetienne::BmpPP;
void VisualizeBlock(const Block& block, const std::string& name) {
BMP bmp(Vector2i(32, 16), Colormode::RGB);
std::size_t i = 0;
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const std::uint8_t pixel = block.GetBit(i) == false ? 255 : 0;
bmp.SetPixel(Vector2i(x, y), pixel);
i++;
}
bmp.Write(name);
return;
}
// size.x*size.y MUST equal blocks.size() * Block::BLOCK_SIZE_BITS. That should be, by defalt blocks.size * 512.
void VisualizeBlocks(const std::vector<Block>& blocks, const Vector2i& size, const std::string& name) {
//! A bit of error checking...
if (size.x*size.y != blocks.size() * Block::BLOCK_SIZE_BITS) {
throw std::invalid_argument("Supplied unfitting widht/height for visualization. Does not fit bits!");
}
BMP bmp(size, Colormode::RGB);
std::size_t i = 0;
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const std::size_t blockIndex = i / Block::BLOCK_SIZE_BITS;
const std::size_t relBitIndex = i - blockIndex * Block::BLOCK_SIZE_BITS;
const std::uint8_t pixel = blocks[blockIndex].GetBit(relBitIndex) == false ? 255 : 0;
bmp.SetPixel(Vector2i(x, y), pixel);
i++;
}
bmp.Write(name);
return;
}
#endif

View File

@ -0,0 +1,21 @@
#include <GCrypt/GWrapper.h>
#include "Benchmark.h"
using namespace Leonetienne::GCrypt;
int main() {
Benchmark(
"file encryption",
[]() {
GWrapper::EncryptFile(
"./execAssets/big-testfile.bmp",
"./execAssets/testimage.bmp.crypt",
Key::FromPassword("password1")
);
}
);
return 0;
}

View File

@ -0,0 +1,42 @@
#include <GCrypt/GPrng.h>
#include <iostream>
#include "Benchmark.h"
using namespace Leonetienne::GCrypt;
int main() {
Benchmark(
"generating 1.000.000 32-bit uints using prng.GetRandom<uint32_t>()",
[]() {
GPrng prng(Key::Random());
for (int i = 0; i < 1000000; i++) {
prng.GetRandom<std::uint32_t>();
}
}
);
Benchmark(
"generating 1.000.000 uint32_t using prng()",
[]() {
GPrng prng(Key::Random());
for (int i = 0; i < 1000000; i++) {
prng();
}
}
);
Benchmark(
"generating 100.000 data blocks using prng.GetBlock()",
[]() {
GPrng prng(Key::Random());
for (int i = 0; i < 100000; i++) {
prng.GetBlock();
}
}
);
return 0;
}

View File

@ -0,0 +1,25 @@
#include <GCrypt/GWrapper.h>
#include <iostream>
using namespace Leonetienne::GCrypt;
int main() {
std::cout << "Example on how to encrypt & decrypt any file:" << std::endl;
// Encrypt
GWrapper::EncryptFile(
"./execAssets/testimage.bmp",
"./execAssets/testimage.bmp.crypt",
Key::FromPassword("password1")
);
// Decrypt
GWrapper::DecryptFile(
"./execAssets/testimage.bmp.crypt",
"./execAssets/testimage.bmp.clear.bmp",
Key::FromPassword("password1"
));
return 0;
}

View File

@ -0,0 +1,30 @@
#include <GCrypt/GWrapper.h>
#include <iostream>
using namespace Leonetienne::GCrypt;
int main() {
std::cout << "Example on how to encrypt & decrypt strings:" << std::endl;
const std::string cleartext = "Hello, World :3";
std::cout << "Cleartext: " << cleartext << std::endl;
// Encrypt
const std::string ciphertext = GWrapper::EncryptString(
cleartext,
Key::FromPassword("password1")
);
std::cout << "Ciphertext: " << ciphertext << std::endl;
// Decrypt
const std::string decrypted = GWrapper::DecryptString(
ciphertext,
Key::FromPassword("password1")
);
std::cout << "Decrypted: " << decrypted << std::endl;
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -1,45 +0,0 @@
#include <iostream>
#include <GCrypt/GCryptWrapper.h>
#include <GCrypt/SecureBitset.h>
#include <GCrypt/Util.h>
#include <GCrypt/InitializationVector.h>
using namespace Leonetienne::GCrypt;
void ExampleString() {
std::cout << "Example on how to encrypt & decrypt a string:" << std::endl;
// Get some string
const std::string input = "I am a super secret message!";
std::cout << input << std::endl;
// Encrypt
const std::string encrypted = GCryptWrapper::EncryptString(input, "password1");
std::cout << encrypted << std::endl;
// Decrypt
const std::string decrypted = GCryptWrapper::DecryptString(encrypted, "password1");
std::cout << decrypted << std::endl;
return;
}
void ExampleFiles() {
std::cout << "Example on how to encrypt & decrypt any file:" << std::endl;
// Encrypt
GCryptWrapper::EncryptFile("main.cpp", "main.cpp.crypt", "password1");
// Decrypt
GCryptWrapper::DecryptFile("main.cpp.crypt", "main.cpp.clear", "password1");
return;
}
int main() {
ExampleString();
//ExampleFiles();
return 0;
}

View File

@ -0,0 +1,54 @@
#include <iostream>
#include <GCrypt/GWrapper.h>
#include <GCrypt/Key.h>
#include <GCrypt/Util.h>
#include <BmpPP/Bmp.h>
#include "Visualize.h"
const std::string execName = "visualize-extreme-input-diffusion";
using namespace Leonetienne::GCrypt;
using namespace Leonetienne::BmpPP;
using namespace Leonetienne::Eule;
int main() {
// These are magic values, which work for this specific input string.
// If you want to try another string, get the size of all blocks in bits (it gets printed),
// and find two integer factors which factor up to it. These are your images width, and height.
const Vector2i visualizationDimension = Vector2i(56, 64);
// Create a key
// The key is almost just zeores (it doesn't have an IV, so what you see is what goes in the cipher.)
// In case you're wondering, I can tell you right now that an all-zero input with an all-zero key will result in an all-zero ciphertext.
Key key;
key.Reset();
key.SetBit(199, true);
VisualizeBlock(key, execName+"-key.bmp");
// Create our input blocks
// All the input blocks are just zeroes!
std::vector<Block> input_blocks;
input_blocks.resize(7);
for (Block& block : input_blocks) {
block.Reset();
}
std::cout << "Input size is " << input_blocks.size() * Block::BLOCK_SIZE_BITS << " bits long." << std::endl;
VisualizeBlocks(input_blocks, visualizationDimension, execName+"-input.bmp");
// Encrypt it
std::vector<Block> ciphertext_blocks = GWrapper::CipherBlocks(input_blocks, key, GCipher::DIRECTION::ENCIPHER);
VisualizeBlocks(ciphertext_blocks, visualizationDimension, execName+"-output.bmp");
// Now flip a single bit in the input
input_blocks[3].FlipBit(156);
VisualizeBlocks(input_blocks, visualizationDimension, execName+"-input-flip.bmp");
// Encrypt it again
ciphertext_blocks = GWrapper::CipherBlocks(input_blocks, key, GCipher::DIRECTION::ENCIPHER);
VisualizeBlocks(ciphertext_blocks, visualizationDimension, execName+"-output-flip.bmp");
return 0;
}

View File

@ -0,0 +1,34 @@
#include <iostream>
#include <GCrypt/GHash.h>
#include <BmpPP/Bmp.h>
#include "Visualize.h"
const std::string execName = "visualize-hashing-distribution";
using namespace Leonetienne::GCrypt;
using namespace Leonetienne::BmpPP;
using namespace Leonetienne::Eule;
void HashAndVisualize(const Block& b, const std::string filename) {
GHash hasher;
hasher.Digest(b);
VisualizeBlock(hasher.GetHashsum(), filename);
}
int main() {
// Get some random input
Block a;
a.FromTextString("Hello, World :3");
VisualizeBlock(a, execName+"-input-a.bmp");
HashAndVisualize(a, execName+"-output-a.bmp");
// Now flip a bit
Block b = a;
b.FlipBit(4);
VisualizeBlock(b, execName+"-input-b.bmp");
HashAndVisualize(b, execName+"-output-b.bmp");
return 0;
}

View File

@ -0,0 +1,45 @@
#include <iostream>
#include <GCrypt/GWrapper.h>
#include <GCrypt/Key.h>
#include <GCrypt/Util.h>
#include <BmpPP/Bmp.h>
#include "Visualize.h"
const std::string execName = "visualize-multiblock-diffusion";
using namespace Leonetienne::GCrypt;
using namespace Leonetienne::BmpPP;
using namespace Leonetienne::Eule;
int main() {
// These are magic values, which work for this specific input string.
// If you want to try another string, get the size of all blocks in bits (it gets printed),
// and find two integer factors which factor up to it. These are your images width, and height.
const Vector2i visualizationDimension = Vector2i(56, 64);
// Create a key
const Key key = Key::Random();
VisualizeBlock(key, execName+"-key.bmp");
// Create our input blocks
const std::string input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
std::vector<Block> input_blocks = StringToBitblocks(input);
std::cout << "Input size is " << input_blocks.size() * Block::BLOCK_SIZE_BITS << " bits long." << std::endl;
VisualizeBlocks(input_blocks, visualizationDimension, execName+"-input.bmp");
// Encrypt it
std::vector<Block> ciphertext_blocks = GWrapper::CipherBlocks(input_blocks, key, GCipher::DIRECTION::ENCIPHER);
VisualizeBlocks(ciphertext_blocks, visualizationDimension, execName+"-output.bmp");
// Now flip a single bit in the input
input_blocks[3].FlipBit(156);
VisualizeBlocks(input_blocks, visualizationDimension, execName+"-input-flip.bmp");
// Encrypt it again
ciphertext_blocks = GWrapper::CipherBlocks(input_blocks, key, GCipher::DIRECTION::ENCIPHER);
VisualizeBlocks(ciphertext_blocks, visualizationDimension, execName+"-output-flip.bmp");
return 0;
}

View File

@ -0,0 +1,146 @@
#include <iostream>
#include <GCrypt/Key.h>
#include <GCrypt/GPrng.h>
#include <BmpPP/Bmp.h>
#include "Benchmark.h"
const std::string execName = "visualize-prng-distribution";
using namespace Leonetienne::GCrypt;
using namespace Leonetienne::BmpPP;
using namespace Leonetienne::Eule;
int main() {
// Black/white
Benchmark(
"black/white - GetBit()",
[]() {
Key seed = Key::Random();
BMP bmp(Vector2i(800, 800), Colormode::RGB);
GPrng prng(seed);
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const std::uint8_t pixel = prng.GetBit() ? 255 : 0;
bmp.SetPixel(Vector2i(x, y), pixel);
}
bmp.Write(execName+"-blackwhite.bmp");
}
);
// Grayscale (using GetRandom<>)
Benchmark(
"grayscale - GetRandom<std::uint8_t>()",
[]() {
Key seed = Key::Random();
BMP bmp(Vector2i(800, 800), Colormode::RGB);
GPrng prng(seed);
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const std::uint8_t pixel = prng.GetRandom<std::uint8_t>();
bmp.SetPixel(Vector2i(x, y), pixel);
}
bmp.Write(execName+"-getrandom-grayscale.bmp");
}
);
// Grayscale (using operator()) (this one returns a whole uint32)
Benchmark(
"grayscale - operator()",
[]() {
Key seed = Key::Random();
BMP bmp(Vector2i(800, 800), Colormode::RGB);
GPrng prng(seed);
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const std::uint8_t pixel = prng() % 256;
bmp.SetPixel(Vector2i(x, y), pixel);
}
bmp.Write(execName+"-operator-grayscale.bmp");
}
);
// Color (using GetRandom<>)
Benchmark(
"color - GetRandom<uint8_t>()",
[]() {
Key seed = Key::Random();
BMP bmp(Vector2i(800, 800), Colormode::RGB);
GPrng prng(seed);
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const std::uint8_t r = prng.GetRandom<std::uint8_t>();
const std::uint8_t g = prng.GetRandom<std::uint8_t>();
const std::uint8_t b = prng.GetRandom<std::uint8_t>();
bmp.SetPixel(Vector2i(x, y), r, g, b);
}
bmp.Write(execName+"-getrandom-color.bmp");
}
);
// Color (using operator()) (this one returns a whole uint32)
Benchmark(
"color - operator()",
[]() {
Key seed = Key::Random();
BMP bmp(Vector2i(800, 800), Colormode::RGB);
GPrng prng(seed);
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const std::uint8_t r = prng() % 256;
const std::uint8_t g = prng() % 256;
const std::uint8_t b = prng() % 256;
bmp.SetPixel(Vector2i(x, y), r, g, b);
}
bmp.Write(execName+"-operator-color.bmp");
}
);
// Color (using GetBlock())
//(this one returns a derivation of the current hashsum, without using up randomness)
Benchmark(
"color - GetBlock()",
[]() {
Key seed = Key::Random();
BMP bmp(Vector2i(800, 800), Colormode::RGB);
GPrng prng(seed);
std::size_t bytes_written = 0;
while (bytes_written < bmp.GetPixelbufferSize()) {
const Block block = prng.GetBlock();
std::size_t bytesToCopy = 0;
if (bmp.GetPixelbufferSize() - bytes_written < Block::BLOCK_SIZE) {
bytesToCopy = bmp.GetPixelbufferSize() - bytes_written;
}
else {
bytesToCopy = Block::BLOCK_SIZE;
}
memcpy(
(char*)bmp.GetPixelbuffer().data() + bytes_written,
(char*)block.Data(),
bytesToCopy
);
bytes_written += bytesToCopy;
}
bmp.Write(execName+"-getblock-color.bmp");
}
);
return 0;
}

View File

@ -0,0 +1,45 @@
#include <iostream>
#include <GCrypt/GWrapper.h>
#include <GCrypt/Key.h>
#include <BmpPP/Bmp.h>
#include "Visualize.h"
const std::string execName = "visualize-singleblock-diffusion";
using namespace Leonetienne::GCrypt;
using namespace Leonetienne::BmpPP;
using namespace Leonetienne::Eule;
int main() {
// Create input block
const std::string input_str = "Hello :3";
Block input;
input.FromTextString(input_str);
VisualizeBlock(input, execName+"-input.bmp");
// Create a key
const Key key = Key::Random();
VisualizeBlock(key, execName+"-key.bmp");
// Create a cipher
GCipher cipher(key, GCipher::DIRECTION::ENCIPHER);
// Encipher our block
Block cipherblock = cipher.Digest(input);
VisualizeBlock(cipherblock, execName+"-output.bmp");
// Now flip a bit in the input
input.FlipBit(35);
VisualizeBlock(input, execName+"-input-flip.bmp");
// Reset our cipher, and encipher the input with a flipped bit
cipher.Initialize(key, GCipher::DIRECTION::ENCIPHER);
// Encipher our block
cipherblock = cipher.Digest(input);
VisualizeBlock(cipherblock, execName+"-output-flip.bmp");
return 0;
}

View File

@ -1,8 +1,216 @@
#pragma once
#include "GCrypt/SecureBitset.h"
#include "GCrypt/Config.h"
#ifndef GCRYPT_BLOCK_H
#define GCRYPT_BLOCK_H
#include <cstdint>
#include <array>
#include <string>
#include <ostream>
#include <bitset>
namespace Leonetienne::GCrypt {
typedef SecureBitset<BLOCK_SIZE> Block;
/** This class represents a block of data,
* and provides functions to manipulate it
*/
template <typename T>
class Basic_Block {
public:
//! Will constuct an uninitialized data block
Basic_Block();
//! Will construct this block from a string like "101010".. Length MUST be 512.
Basic_Block(const std::string& other);
//! Copy-ctor
Basic_Block(const Basic_Block<T>& other);
~Basic_Block();
//! 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 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,
//! they will most likely integer-overflow.
//! So see this as a one-way function.
[[nodiscard]] Basic_Block<T> MMul(const Basic_Block<T>& other) const;
[[nodiscard]] Basic_Block<T> operator*(const Basic_Block<T>& other) const;
//! Will matrix-multiply two blocks together,
//! and directly write into this same block.
//! Since the matrices values are pretty much sudo-random,
//! they will most likely integer-overflow.
//! So see this as a one-way function.
void MMulInplace(const Basic_Block<T>& other);
Basic_Block<T>& operator*=(const Basic_Block<T>& other);
//! Will xor two blocks together
[[nodiscard]] Basic_Block<T> Xor(const Basic_Block<T>& other) const;
//! Will xor two blocks together
[[nodiscard]] Basic_Block<T> operator^(const Basic_Block<T>& other) const;
//! Will xor two blocks together, inplace
void XorInplace(const Basic_Block<T>& other);
//! Will xor two blocks together, inplace
Basic_Block<T>& operator^=(const Basic_Block<T>& other);
//! Will add all the integer making up this block, one by one
[[nodiscard]] Basic_Block<T> Add(const Basic_Block<T>& other) const;
//! Will add all the integer making up this block, one by one
[[nodiscard]] Basic_Block<T> operator+(const Basic_Block<T>& other) const;
//! Will add all the integer making up this block, one by one, inplace
void AddInplace(const Basic_Block<T>& other);
//! Will add all the integer making up this block, one by one, inplace
Basic_Block<T>& operator+=(const Basic_Block<T>& other);
//! Will subtract all the integer making up this block, one by one
[[nodiscard]] Basic_Block<T> Sub(const Basic_Block<T>& other) const;
//! Will subtract all the integer making up this block, one by one
[[nodiscard]] Basic_Block<T> operator-(const Basic_Block<T>& other) const;
//! Will subtract all the integer making up this block, one by one, inplace
void SubInplace(const Basic_Block<T>& other);
//! Will subtract all the integer making up this block, one by one, inplace
Basic_Block<T>& operator-=(const Basic_Block<T>& other);
//! Will shift rows upwards by 1
[[nodiscard]] Basic_Block<T> ShiftRowsUp() const;
//! Will shift rows upwards by 1
void ShiftRowsUpInplace();
//! Will shift matrix rows downwards by 1
[[nodiscard]] Basic_Block<T> ShiftRowsDown() const;
//! Will shift matrix rows downwards by 1
void ShiftRowsDownInplace();
//! Will shift matrix columns to the left by 1
[[nodiscard]] Basic_Block<T> ShiftColumnsLeft() const;
//! Will shift matrix columns to the left by 1
void ShiftColumnsLeftInplace();
//! Will shift matrix columns to the right by 1
[[nodiscard]] Basic_Block<T> ShiftColumnsRight() const;
//! Will shift matrix columns to the right by 1
void ShiftColumnsRightInplace();
//! Will shift array cells to the left by 1
[[nodiscard]] Basic_Block<T> ShiftCellsLeft() const;
//! Will shift array cells to the left by 1
void ShiftCellsLeftInplace();
//! Will shift array cells to the right by 1
[[nodiscard]] Basic_Block<T> ShiftCellsRight() const;
//! Will shift array cells to the right by 1
void ShiftCellsRightInplace();
//! Will copy a block
Basic_Block<T>& operator=(const Basic_Block<T>& other);
//! Will compare whether or not two blocks are equal
[[nodiscard]] bool operator==(const Basic_Block<T>& other) const;
//! Will compare whether or not two blocks are unequal
[[nodiscard]] bool operator!=(const Basic_Block<T>& other) const;
//! Will zero all data
void Reset();
//! Will return the state of any given bit
[[nodiscard]] bool GetBit(const std::size_t index) const;
//! Will set the state of any given bit
void SetBit(const std::size_t index, const bool state);
//! Will flip the state of any given bit
void FlipBit(const std::size_t index);
//! Will shift all bits to the left by 1
[[nodiscard]] Basic_Block<T> ShiftBitsLeft() const;
//! Will shift all bits to the left by 1, inplace
void ShiftBitsLeftInplace();
//! Will shift all bits to the right by 1
[[nodiscard]] Basic_Block<T> ShiftBitsRight() const;
//! Will shift all bits to the right by 1, inplace
void ShiftBitsRightInplace();
//! Returns 32-bit chunks of data, indexed by matrix coordinates (0-3)
[[nodiscard]] T& Get(const std::uint8_t row, const std::uint8_t column);
//! Returns 32-bit chunks of data, indexed by matrix coordinates (0-3)
[[nodiscard]] const T& Get(const std::uint8_t row, const std::uint8_t column) const;
//! Returns 32-bit chunks of data, indexed by a 1d-index (0-16)
[[nodiscard]] T& Get(const std::uint8_t index);
//! Returns 32-bit chunks of data, indexed by a 1d-index (0-16)
[[nodiscard]] const T& Get(const std::uint8_t index) const;
//! Returns 32-bit chunks of data, indexed by a 1d-index (0-16)
[[nodiscard]] T& operator[](const std::uint8_t index);
//! Returns 32-bit chunks of data, indexed by a 1d-index (0-16)
[[nodiscard]] const T& operator[](const std::uint8_t index) const;
//! Will return a reference to the data array
T* Data() noexcept;
//! Will return a reference to the data array
const T* Data() const noexcept;
static constexpr std::size_t CHUNK_SIZE = sizeof(T);
static constexpr std::size_t CHUNK_SIZE_BITS = CHUNK_SIZE * 8;
static constexpr std::size_t BLOCK_SIZE = CHUNK_SIZE * 16;
static constexpr std::size_t BLOCK_SIZE_BITS = CHUNK_SIZE_BITS * 16;
friend std::ostream& operator<<(std::ostream& os, const Basic_Block<T>& b) {
for (std::size_t i = 0; i < b.data.size(); i++) {
os << std::bitset<Basic_Block<T>::CHUNK_SIZE_BITS>(b.data[i]).to_string();
}
return os;
}
private:
std::array<T, 16> data;
};
//! This a full-sized 512-bit block
typedef Basic_Block<std::uint32_t> Block;
//! This is a half-block used within the feistel class
typedef Basic_Block<std::uint16_t> Halfblock;
}
#endif

View File

@ -1,39 +0,0 @@
#pragma once
#include "GCrypt/Feistel.h"
#include "GCrypt/Flexblock.h"
namespace Leonetienne::GCrypt {
/** Class to apply a block cipher to messages of arbitrary length in a distributed manner
*/
class Cipher {
public:
explicit Cipher(const Block& key);
explicit Cipher(const std::string& password);
Cipher(const Cipher& other) = delete;
Cipher(Cipher&& other) noexcept = delete;
~Cipher();
//! Will set the key
void SetKey(const Block& key);
//! Will set the key from a password
void SetPassword(const std::string& password);
//! Will encipher a flexblock of data
Flexblock Encipher(const Flexblock& data, bool printProgress = false) const;
//! Will decipher a flexblock of data
Flexblock Decipher(const Flexblock& data, bool printProgress = false) const;
private:
Block key;
//! Will zero the memory used by the key
void ZeroKeyMemory();
// Initial value for cipher block chaining
Block initializationVector;
};
}

View File

@ -1,10 +1,12 @@
#pragma once
#ifndef GCRYPT_CONFIG_H
#define GCRYPT_CONFIG_H
#include <cstddef>
namespace Leonetienne::GCrypt {
// MUST BE A POWER OF 2 > 4
constexpr std::size_t BLOCK_SIZE = 512;
// MUST BE > 2
constexpr std::size_t N_ROUNDS = 64;
constexpr std::size_t N_ROUNDS = 6;
}
#endif

View File

@ -1,14 +1,20 @@
#pragma once
#ifndef GCRYPT_FEISTEL_H
#define GCRYPT_FEISTEL_H
#include "GCrypt/Keyset.h"
#include "GCrypt/Block.h"
#include "GCrypt/Halfblock.h"
#include "GCrypt/Key.h"
namespace Leonetienne::GCrypt {
/** Class to perform a feistel block chipher
*/
class Feistel {
public:
explicit Feistel(const Block& key);
//! Empty initializer. If you use this, you must call SetKey()!
Feistel();
//! Will initialize the feistel cipher with a key
explicit Feistel(const Key& key);
Feistel(const Feistel& other) = delete;
Feistel(Feistel&& other) noexcept = delete;
@ -17,7 +23,7 @@ namespace Leonetienne::GCrypt {
//! Will set the seed-key for this feistel network.
//! Roundkeys will be derived from this.
void SetKey(const Block& key);
void SetKey(const Key& key);
//! Will encipher a data block via the set seed-key
Block Encipher(const Block& data);
@ -25,13 +31,15 @@ namespace Leonetienne::GCrypt {
//! Will decipher a data block via the set seed-key
Block Decipher(const Block& data);
void operator=(const Feistel& other);
private:
//! Will run the feistel rounds, with either regular key
//! order or reversed key order
Block Run(const Block& data, bool reverseKeys);
Block Run(const Block& data, bool modeEncrypt);
//! Arbitrary cipher function
static Halfblock F(Halfblock m, const Block& key);
static Halfblock F(Halfblock m, const Key& key);
//! Split a data block into two half blocks (into L and R)
static std::pair<Halfblock, Halfblock> FeistelSplit(const Block& block);
@ -42,18 +50,23 @@ namespace Leonetienne::GCrypt {
//! Will expand a halfblock to a fullblock
static Block ExpansionFunction(const Halfblock& block);
//! Will compress a fullblock to a halfblock
static Halfblock CompressionFunction(const Block& block);
//! Will reduce a fullblock to a halfblock
static Halfblock ReductionFunction(const Block& block);
//! Substitutes four bits by static random others
static std::string SBox(const std::string& in);
//! Substitutes eight bits by static random others, inplace
static void SBox(Block& block);
//! Will generate a the round keys
void GenerateRoundKeys(const Block& seedKey);
void GenerateRoundKeys(const Key& seedKey);
//! Will zero the memory used by the keyset
void ZeroKeyMemory();
Keyset roundKeys;
bool isInitialized = false;
};
}
#endif

View File

@ -1,7 +0,0 @@
#pragma once
#include <string>
namespace Leonetienne::GCrypt {
//! A "bitset" of variable length
typedef std::string Flexblock;
}

View File

@ -0,0 +1,54 @@
#ifndef GCRYPT_GCIPHER_H
#define GCRYPT_GCIPHER_H
#include "GCrypt/Feistel.h"
namespace Leonetienne::GCrypt {
/** Class to apply a block/-stream cipher to messages of arbitrary length in a distributed manner
*/
class GCipher {
public:
//! Describes the direction the cipher runs in
enum class DIRECTION {
ENCIPHER,
DECIPHER
};
//! Empty initializer. If you use this, you must call Initialize()!
GCipher();
//! Will initialize this cipher with a key
explicit GCipher(const Key& key, const DIRECTION direction);
// Disable copying
GCipher(const GCipher& other) = delete;
GCipher(GCipher&& other) noexcept = delete;
//! Will digest a data block, and return it
Block Digest(const Block& input);
//! Will update the base key used
void SetKey(const Key& key);
void operator=(const GCipher& other);
//! Will initialize the cipher with a key, and a mode.
//! If called on an existing object, it will reset its state.
void Initialize(const Key& key, const DIRECTION direction);
private:
DIRECTION direction;
//! The feistel instance to be used
Feistel feistel;
//! The last block, required for CBC.
Block lastBlock;
bool isInitialized = false;
};
}
#endif

View File

@ -0,0 +1,45 @@
#ifndef GCRYPT_GHASH_H
#define GCRYPT_GHASH_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
*/
class GHash {
public:
GHash();
//! Will add the hash value of the block `data` to the hashsum.
//! WARNING: If you compute hashes using this digestive method,
//! you REALLY REALLY should add a trailing block just containing the cleartext size!
//! You MOST LIKELY just want to use the wrapper function GHash::CalculateHashsum(Flexblock const&) instead!
void Digest(const Block& data);
//! Will return the current hashsum
const Block& GetHashsum() const;
//! 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);
private:
//! The cipher to use
GCipher cipher;
//! The current state of the hashsum
Block block;
};
}
#endif

View File

@ -0,0 +1,71 @@
#ifndef GCRYPT_GPRNG_H
#define GCRYPT_GPRNG_H
#include "GCrypt/GHash.h"
#include "GCrypt/Util.h"
#include <string.h>
#include <sstream>
#include <type_traits>
namespace Leonetienne::GCrypt {
/** This class implements a pseudo random number generator, based on the GCrypt hash function
*/
class GPrng {
public:
//! Will instanciate the prng with a seed. Seed could also be a GCrypt::Key.
GPrng(const Block& seed);
//! Will instanciate the GPrng with no seed. You should really seed it later.
GPrng();
//! Will reset and seed the prng. Seed could also be a GCrypt::Key.
void Seed(const Block& seed);
//! Will return a random bit.
bool GetBit();
//! Will return a randomized instance of any primitive.
template <typename T>
T GetRandom() {
static_assert(std::is_fundamental<T>::value, "Leonetienne::GCrypt::GPrng::GetRandom() may only be used with primitive types!");
// Pull the required amount of bits
std::stringstream ss;
for (std::size_t i = 0; i < sizeof(T)*8; i++) {
ss << (GetBit() ? '1' : '0');
}
// Transform to bytes
const std::string bits = ss.str();
ss.str("");
for (std::size_t i = 0; i < bits.size(); i += 8) {
ss << (char)std::bitset<8>(bits.substr(i, 8)).to_ulong();
}
const std::string bytes = ss.str();
// Cram bytes into type
T t;
memcpy(&t, bytes.data(), sizeof(T));
// Return our randomized primitive
return t;
}
//! Will return a random unsigned 32-bit integer
std::uint32_t operator()();
//! Will return a random block
Block GetBlock();
private:
//! Will generate the next block of random bits
void AdvanceBlock();
GHash hasher;
Block seed;
std::size_t nextBit = 0;
};
}
#endif

View File

@ -1,33 +1,45 @@
#pragma once
#ifndef GCRYPT_GWRAPPER_H
#define GCRYPT_GWRAPPER_H
#include "GCrypt/Block.h"
#include "GCrypt/GCipher.h"
#include "GCrypt/Key.h"
#include <string>
#include <vector>
namespace Leonetienne::GCrypt {
/** This class is a wrapper to make working with the GhettoCipher
/** This class is a wrapper to make working with the GCipher
* super easy with a python-like syntax
*/
class GCryptWrapper {
class GWrapper {
public:
//! Will encrypt a string and return it hexadecimally encoded.
static std::string EncryptString(const std::string& cleartext, const std::string& password);
static std::string EncryptString(const std::string& cleartext, const Key& key);
//! Will decrypt a hexadecimally encoded string.
static std::string DecryptString(const std::string& ciphertext, const std::string& password);
static std::string DecryptString(const std::string& ciphertext, const Key& key);
//! 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);
static bool EncryptFile(const std::string& filename_in, const std::string& filename_out, const Key& key, 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);
static bool DecryptFile(const std::string& filename_in, const std::string& filename_out, const Key& key, bool printProgressReport = false);
//! Will enncrypt or decrypt an entire flexblock of binary data, given a key.
static std::vector<Block> CipherBlocks(const std::vector<Block>& data, const Key& key, const GCipher::DIRECTION direction);
private:
// No instanciation! >:(
GCryptWrapper();
GWrapper();
};
}
#endif

View File

@ -1,9 +0,0 @@
#pragma once
#include <cstdint>
#include "GCrypt/SecureBitset.h"
#include "GCrypt/Config.h"
namespace Leonetienne::GCrypt {
constexpr std::size_t HALFBLOCK_SIZE = (BLOCK_SIZE / 2);
typedef SecureBitset<HALFBLOCK_SIZE> Halfblock;
}

View File

@ -0,0 +1,37 @@
#ifndef GCRYPT_KEY_H
#define GCRYPT_KEY_H
#include "GCrypt/Block.h"
#include <string>
namespace Leonetienne::GCrypt {
/** This class represents encryption keys.
* You can copy them, create them from data blocks,
* or even read from files.
*/
class Key : public Block {
public:
//! Will generate a key from a password
static Key FromPassword(const std::string& password);
//! Will generate a random key from actual randomness (std::random_device)
static Key Random();
//! Loads a keyfile
static Key LoadFromFile(const std::string& path);
//! Will save a keyfile
void WriteToFile(const std::string& path) const;
Key();
Key(const Key& k);
Key(const Block& b);
private:
};
}
#endif

View File

@ -1,8 +1,13 @@
#pragma once
#ifndef GCRYPT_KEYSET_H
#define GCRYPT_KEYSET_H
#include <array>
#include "GCrypt/Block.h"
#include "GCrypt/Key.h"
#include "GCrypt/Config.h"
namespace Leonetienne::GCrypt {
typedef std::array<Block, N_ROUNDS> Keyset;
typedef std::array<Key, N_ROUNDS> Keyset;
}
#endif

View File

@ -0,0 +1,46 @@
#ifndef GCRYPT_SBOX_LOOKUP
#define GCRYPT_SBOX_LOOKUP
#include <array>
namespace Leonetienne::GCrypt {
static const std::array<std::uint8_t, 256> sboxLookup {
0x23,0xF7,0xA2,0x31,0x5B,0x0C,0x13,0xEE,
0xF8,0x1B,0x0B,0xA9,0x37,0x9F,0x55,0xF3,
0x7D,0x71,0x9E,0x89,0x38,0x53,0x3D,0xE9,
0x47,0xA4,0x30,0xBF,0x82,0xE3,0x69,0x5C,
0x3C,0x88,0xDE,0x7F,0x29,0xEB,0x5F,0x02,
0x63,0x42,0xDC,0x36,0x20,0x6B,0x9C,0x4A,
0xA8,0x11,0x6D,0x67,0x3A,0xF4,0x33,0x61,
0x1C,0x4C,0x22,0x07,0x6F,0x80,0xA5,0x96,
0x77,0x62,0xDD,0x6A,0x60,0x05,0x08,0x72,
0xE5,0x1D,0x39,0xA7,0x58,0x3F,0x57,0xAD,
0xFF,0x09,0xC5,0xB2,0xE7,0x94,0xEA,0x34,
0x9A,0x5D,0x52,0xE2,0xAE,0x99,0xBB,0x44,
0xFD,0x73,0xEC,0x87,0xED,0xBA,0x7C,0xF5,
0xDA,0x01,0xC7,0xC1,0xB6,0x81,0x12,0xD5,
0x7A,0x16,0x18,0x97,0x74,0x32,0xBD,0x56,
0xFE,0x79,0x3E,0x95,0x93,0xAB,0x6C,0xC6,
0x2A,0x27,0x19,0x26,0x2E,0x41,0x0F,0x85,
0x66,0x59,0xEF,0x98,0x10,0xE8,0xE6,0x49,
0xAC,0x0D,0x2D,0xD3,0xF0,0x92,0x8F,0xD9,
0xAF,0xC8,0xB5,0xBC,0x6E,0xD6,0x78,0x8E,
0x17,0xCB,0x75,0x50,0x51,0x84,0xB7,0x4D,
0x70,0x9D,0x8A,0x2B,0x0E,0x35,0xB9,0x40,
0x00,0x21,0xC3,0xB3,0x43,0xCD,0x65,0xC0,
0x4E,0xCF,0xCA,0x28,0x45,0x46,0x54,0x8B,
0x0A,0x5A,0x1F,0x24,0xFA,0xA6,0xB4,0xD7,
0x3B,0xFC,0x5E,0xA3,0xB8,0x04,0xCE,0xF2,
0xFB,0xD4,0x8C,0xE4,0x90,0xB1,0x06,0x8D,
0x86,0xA1,0xE0,0x68,0xD1,0x2C,0x03,0x64,
0x9B,0x4F,0x14,0x1E,0x7B,0x76,0x48,0xCC,
0xC4,0x15,0xAA,0xC9,0xE1,0x91,0xF6,0xD0,
0x25,0xA0,0x7E,0xB0,0x1A,0xBE,0xC2,0x4B,
0xF1,0xD2,0xDF,0x2F,0xF9,0xDB,0xD8,0x83
};
}
#endif

View File

@ -1,286 +0,0 @@
#pragma once
#include <bitset>
#include <ostream>
#include <istream>
namespace Leonetienne::GCrypt {
/** Wrapper for std::bitset<T> that zeroes memory upon deletion.
* This does not include ALL methods, but the ones needed.
*
* Just creating a specialization of std::bitset<T> does not work.
*/
template <std::size_t T>
class SecureBitset {
public:
explicit SecureBitset();
explicit SecureBitset(const std::string& str);
explicit SecureBitset(const long long int i);
~SecureBitset();
bool operator==(const SecureBitset<T>& other) const;
bool operator!=(const SecureBitset<T>& other) const;
bool operator[](const std::size_t) const;
bool test(const std::size_t index) const;
bool all() const;
bool any() const;
bool none() const;
std::size_t count() const;
std::size_t size() const;
SecureBitset<T>& operator&=(const SecureBitset<T>& other);
SecureBitset<T>& operator|=(const SecureBitset<T>& other);
SecureBitset<T>& operator^=(const SecureBitset<T>& other);
SecureBitset<T> operator&(const SecureBitset<T>& other);
SecureBitset<T> operator|(const SecureBitset<T>& other);
SecureBitset<T> operator^(const SecureBitset<T>& other);
SecureBitset<T> operator~() const;
SecureBitset<T>& operator<<=(const std::size_t offset);
SecureBitset<T>& operator>>=(const std::size_t offset);
SecureBitset<T> operator<<(const std::size_t offset) const;
SecureBitset<T> operator>>(const std::size_t offset) const;
SecureBitset<T>& set();
SecureBitset<T>& set(const std::size_t index, bool value = true);
SecureBitset<T>& reset();
SecureBitset<T>& reset(const std::size_t index);
SecureBitset<T>& flip();
SecureBitset<T>& flip(const std::size_t index);
std::string to_string() const;
unsigned long to_ulong() const;
unsigned long long to_ullong() const;
std::bitset<T>& Get();
const std::bitset<T>& Get() const;
private:
std::bitset<T> bitset;
};
template<std::size_t T>
inline SecureBitset<T>::SecureBitset()
:
bitset() {
return;
}
template<std::size_t T>
inline SecureBitset<T>::SecureBitset(const std::string& str)
:
bitset(str) {
return;
}
template<std::size_t T>
inline SecureBitset<T>::SecureBitset(const long long int i)
:
bitset(i) {
return;
}
// Don't optimize the destructor out!!!
// These pragmas only work for MSVC and g++, as far as i know. Beware!!!
#if defined _WIN32 || defined _WIN64
#pragma optimize("", off )
#elif defined __GNUG__
#pragma GCC push_options
#pragma GCC optimize ("O0")
#endif
template<std::size_t T>
inline SecureBitset<T>::~SecureBitset() {
bitset.reset();
return;
}
#if defined _WIN32 || defined _WIN64
#pragma optimize("", on )
#elif defined __GNUG__
#pragma GCC pop_options
#endif
template<std::size_t T>
inline bool SecureBitset<T>::operator==(const SecureBitset<T>& other) const {
return bitset == other.bitset;
}
template<std::size_t T>
inline bool SecureBitset<T>::operator!=(const SecureBitset<T>& other) const {
return bitset != other.bitset;
}
template<std::size_t T>
inline bool SecureBitset<T>::operator[](const std::size_t index) const {
return bitset[index];
}
template<std::size_t T>
inline bool SecureBitset<T>::test(const std::size_t index) const {
return bitset.test(index);
}
template<std::size_t T>
inline bool SecureBitset<T>::all() const {
return bitset.all();
}
template<std::size_t T>
inline bool SecureBitset<T>::any() const {
return bitset.any();
}
template<std::size_t T>
inline bool SecureBitset<T>::none() const {
return bitset.none();
}
template<std::size_t T>
inline std::size_t SecureBitset<T>::count() const {
return bitset.count();
}
template<std::size_t T>
inline std::size_t SecureBitset<T>::size() const {
return bitset.count();
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::operator&=(const SecureBitset<T>& other) {
bitset &= other.bitset;
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::operator|=(const SecureBitset<T>& other) {
bitset |= other.bitset;
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::operator^=(const SecureBitset<T>& other) {
bitset ^= other.bitset;
return *this;
}
template<std::size_t T>
inline SecureBitset<T> SecureBitset<T>::operator&(const SecureBitset<T>& other) {
SecureBitset bs;
bs.bitset = bitset & other.bitset;
return bs;
}
template<std::size_t T>
inline SecureBitset<T> SecureBitset<T>::operator|(const SecureBitset<T>& other) {
SecureBitset bs;
bs.bitset = bitset | other.bitset;
return bs;
}
template<std::size_t T>
inline SecureBitset<T> SecureBitset<T>::operator^(const SecureBitset<T>& other) {
SecureBitset bs;
bs.bitset = bitset ^ other.bitset;
return bs;
}
template<std::size_t T>
inline SecureBitset<T> SecureBitset<T>::operator~() const {
SecureBitset bs;
bs.bitset = ~bitset;
return bs;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::operator<<=(const std::size_t offset) {
bitset <<= offset;
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::operator>>=(const std::size_t offset) {
bitset >>= offset;
return *this;
}
template<std::size_t T>
inline SecureBitset<T> SecureBitset<T>::operator<<(const std::size_t offset) const {
SecureBitset bs;
bs.bitset = bitset << offset;
return bs;
}
template<std::size_t T>
inline SecureBitset<T> SecureBitset<T>::operator>>(const std::size_t offset) const {
SecureBitset bs;
bs.bitset = bitset >> offset;
return bs;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::set() {
bitset.set();
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::set(const std::size_t index, bool value) {
bitset.set(index, value);
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::reset() {
bitset.reset();
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::reset(const std::size_t index) {
bitset.reset(index);
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::flip() {
bitset.flip();
return *this;
}
template<std::size_t T>
inline SecureBitset<T>& SecureBitset<T>::flip(const std::size_t index) {
bitset.flip(index);
return *this;
}
template<std::size_t T>
inline std::string SecureBitset<T>::to_string() const {
return bitset.to_string();
}
template<std::size_t T>
inline unsigned long SecureBitset<T>::to_ulong() const {
return bitset.to_ulong();
}
template<std::size_t T>
inline unsigned long long SecureBitset<T>::to_ullong() const {
return bitset.to_ullong();
}
template<std::size_t T>
inline std::bitset<T>& SecureBitset<T>::Get() {
return bitset;
}
template<std::size_t T>
inline const std::bitset<T>& SecureBitset<T>::Get() const {
return bitset;
}
template <std::size_t T>
inline std::ostream& operator<<(std::ostream& ofs, const SecureBitset<T>& bs) {
return ofs << bs.Get();
}
template <std::size_t T>
inline std::istream& operator>>(std::istream& ifs, const SecureBitset<T>& bs) {
return ifs >> bs.Get();
}
}

View File

@ -1,309 +1,44 @@
#pragma once
#ifndef GCRYPT_UTIL_H
#define GCRYPT_UTIL_H
#include <bitset>
#include <sstream>
#include <fstream>
#include <cstring>
#include "GCrypt/SecureBitset.h"
#include <vector>
#include "GCrypt/Block.h"
#include "GCrypt/Flexblock.h"
#include "GCrypt/Config.h"
#include "GCrypt/Cipher.h"
#include "GCrypt/GCipher.h"
#include "GCrypt/InitializationVector.h"
namespace Leonetienne::GCrypt {
//! Mod-operator that works with negative values
inline int Mod(const int numerator, const int denominator) {
return (denominator + (numerator % denominator)) % denominator;
}
//! Mod-operator that works with negative values
inline int Mod(const int numerator, const int denominator) {
return (denominator + (numerator % denominator)) % denominator;
}
//! Will perform a wrapping left-bitshift on a bitset
template <std::size_t T>
inline SecureBitset<T> Shiftl(const SecureBitset<T>& bits, const std::size_t amount) {
std::stringstream ss;
const std::string bitss = bits.to_string();
//! Will pad a string to a set length with a certain character
std::string PadStringToLength(const std::string& str, const std::size_t len, const char pad, const bool padLeft = true);
for (std::size_t i = 0; i < bitss.size(); i++) {
ss << bitss[Mod((int)(i + amount), (int)bitss.size())];
}
//! Will convert a string to a vector of blocks
std::vector<Block> StringToBitblocks(const std::string& str);
return SecureBitset<T>(ss.str());
}
//! Will convert an array of data blocks to a bytestring
std::string BitblocksToBytes(const std::vector<Block>& bits);
//! Will perform a wrapping right-bitshift on a bitset
template <std::size_t T>
inline SecureBitset<T> Shiftr(const SecureBitset<T>& bits, const std::size_t amount) {
std::stringstream ss;
const std::string bitss = bits.to_string();
//! 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);
for (std::size_t i = 0; i < bitss.size(); i++) {
ss << bitss[Mod((i - amount), bitss.size())];
}
//! 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);
return SecureBitset<T>(ss.str());
}
//! Will read a file directly to data blocks
std::vector<Block> ReadFileToBlocks(const std::string& filepath);
//! Will pad a string to a set length with a certain character
inline std::string PadStringToLength(const std::string& str, const std::size_t len, const char pad, const bool padLeft = true) {
// Fast-reject: Already above padded length
if (str.length() >= len) {
return str;
}
std::stringstream ss;
// Pad left:
if (padLeft) {
for (std::size_t i = 0; i < len - str.size(); i++) {
ss << pad;
}
ss << str;
}
// Pad right:
else {
ss << str;
for (std::size_t i = 0; i < len - str.size(); i++) {
ss << pad;
}
}
return ss.str();
}
//! Will convert a string to a fixed-size data block
inline Block StringToBitblock(const std::string& s) {
std::stringstream ss;
for (std::size_t i = 0; i < s.size(); i++) {
ss << std::bitset<8>(s[i]);
}
// Pad rest with zeores
return Block(PadStringToLength(ss.str(), 128, '0', false));
}
//! Will convert a string to a flexible data block
inline Flexblock StringToBits(const std::string& s) {
std::stringstream ss;
for (std::size_t i = 0; i < s.size(); i++) {
ss << std::bitset<8>(s[i]);
}
return Flexblock(ss.str());
}
//! Will convert a fixed-size data block to a bytestring
inline std::string BitblockToBytes(const Block& bits) {
std::stringstream ss;
const std::string bitstring = bits.to_string();
for (std::size_t i = 0; i < BLOCK_SIZE; i += 8) {
ss << (char)std::bitset<8>(bitstring.substr(i, 8)).to_ulong();
}
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);
// 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;
const std::string bitstring = bits;
for (std::size_t i = 0; i < bits.size(); i += 8) {
ss << (char)std::bitset<8>(bitstring.substr(i, 8)).to_ulong();
}
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) {
std::stringstream ss;
const std::string charset = "0123456789abcdef";
const std::string bstr = b.to_string();
for (std::size_t i = 0; i < bstr.size(); i += 4) {
ss << charset[std::bitset<4>(bstr.substr(i, 4)).to_ulong()];
}
return ss.str();
}
//! Turns a flexible data block into a hex-string
inline std::string BitsToHexstring(const Flexblock& b) {
std::stringstream ss;
const std::string charset = "0123456789abcdef";
const std::string bstr = b;
for (std::size_t i = 0; i < bstr.size(); i += 4) {
ss << charset[std::bitset<4>(bstr.substr(i, 4)).to_ulong()];
}
return ss.str();
}
//! Turns a hex string into a fixed-size data block
inline Block HexstringToBitblock(const std::string& hexstring) {
std::stringstream ss;
for (std::size_t i = 0; i < hexstring.size(); i++) {
const char c = hexstring[i];
// Get value
std::size_t value;
if ((c >= '0') && (c <= '9')) {
// Is it a number?
value = ((std::size_t)c - '0') + 0;
}
else if ((c >= 'a') && (c <= 'f')) {
// Else, it is a lowercase letter
value = ((std::size_t)c - 'a') + 10;
}
else {
throw std::logic_error("non-hex string detected in HexstringToBits()");
}
// Append to our bits
ss << std::bitset<4>(value);
}
return Block(ss.str());
}
//! Turns a hex string into a flexible data block
inline Flexblock HexstringToBits(const std::string& hexstring) {
std::stringstream ss;
for (std::size_t i = 0; i < hexstring.size(); i++) {
const char c = hexstring[i];
// Get value
std::size_t value;
if ((c >= '0') && (c <= '9')) {
// Is it a number?
value = ((std::size_t)c - '0') + 0;
}
else if ((c >= 'a') && (c <= 'f')) {
// Else, it is a lowercase letter
value = ((std::size_t)c - 'a') + 10;
}
else {
throw std::logic_error("non-hex string detected in HexstringToBits()");
}
// Append to our bits
ss << std::bitset<4>(value);
}
return ss.str();
}
//! Creates a key of size BLOCK_SIZE from a password of arbitrary length.
//! 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) {
// Let's provide a nice initial value to be sure even a password of length 0 results in a proper key
Block b = InitializationVector(StringToBitblock("3J7IipfQTDJbO8jtasz9PgWui6faPaEMOuVuAqyhB1S2CRcLw5caawewgDUEG1WN"));
// Segment the password in segments of key-size, and xor them together.
for (std::size_t i = 0; i < in.size(); i += BLOCK_SIZE / 8) {
const Block fragment = StringToBitblock(
PadStringToLength(in.substr(i, BLOCK_SIZE / 8), BLOCK_SIZE / 8, 0, false)
);
// To provide confusion, xor the blocks together
// To provide diffusion, hash fragment to fragment' first
b ^= Block(Cipher(fragment).Encipher(fragment.to_string()));
}
return b;
}
//! Will reduce a flexblock (they are of arbitrary length) to a single block.
//! This single block should change completely, if a single bit in the input flexblock changes anywhere.
inline Block ReductionFunction_Flexblock2Block(const Flexblock& in) {
Block b; // No initialization vector needed here
// Segment the input in segments of BLOCK_SIZE, and xor them together.
for (std::size_t i = 0; i < in.size(); i += BLOCK_SIZE) {
const Block fragment = Block(PadStringToLength(in.substr(i, BLOCK_SIZE), BLOCK_SIZE, 0, false));
// To provide confusion, xor the blocks together
// To provide diffusion, hash fragment to fragment' first
b ^= Block(Cipher(fragment).Encipher(fragment.to_string()));
}
return b;
}
//! Will read a file into a flexblock
inline Flexblock ReadFileToBits(const std::string& filepath) {
// Read file
std::ifstream ifs(filepath, std::ios::binary);
if (!ifs.good()) {
throw std::runtime_error("Unable to open ifilestream!");
}
std::stringstream ss;
std::copy(
std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>(),
std::ostreambuf_iterator<char>(ss)
);
ifs.close();
const std::string bytes = ss.str();
// Convert bytes to bits
return StringToBits(bytes);
}
//! Will save bits to a binary file
inline void WriteBitsToFile(const std::string& filepath, const Flexblock& bits) {
// Convert bits to bytes
const std::string bytes = BitsToBytes(bits);
// Write bits to file
std::ofstream ofs(filepath, std::ios::binary);
if (!ofs.good()) {
throw std::runtime_error("Unable to open ofilestream!");
}
ofs.write(bytes.data(), bytes.length());
ofs.close();
return;
}
//! Will write data blocks directly to a file
void WriteBlocksToFile(const std::string& filepath, const std::vector<Block>& blocks);
}
#endif

View File

@ -1,3 +1,7 @@
#pragma once
#define GHETTOCRYPT_VERSION 0.22
#ifndef GCRYPT_VERSION_H
#define GCRYPT_VERSION_H
#define GCRYPT_VERSION 0.236
#endif

838
GCryptLib/src/Block.cpp Normal file
View File

@ -0,0 +1,838 @@
#include "GCrypt/Block.h"
#include "GCrypt/Config.h"
#include "GCrypt/Util.h"
#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
#define MAT_INDEX(row, column) (column*4 + row)
namespace Leonetienne::GCrypt {
template <typename T>
Basic_Block<T>::Basic_Block() {
}
template <typename T>
Basic_Block<T>::Basic_Block(const std::string& str) {
FromBinaryString(str);
}
template <typename T>
Basic_Block<T>::Basic_Block(const Basic_Block<T>& other) {
data = other.data;
}
template <typename T>
void Basic_Block<T>::FromBinaryString(const std::string& str) {
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>(
str.substr(i*CHUNK_SIZE_BITS, CHUNK_SIZE_BITS)
).to_ulong();
}
return;
}
template <typename T>
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++) {
ss << std::bitset<CHUNK_SIZE_BITS>(data[i]).to_string();
}
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 {
Basic_Block<T> m;
// Maybe pre-calculate the 1d-index...?
m.Get(0, 0) = (this->Get(0, 0) * o.Get(0, 0)) + (this->Get(0, 1) * o.Get(1, 0)) + (this->Get(0, 2) * o.Get(2, 0)) + (this->Get(0, 3) * o.Get(3, 0));
m.Get(0, 1) = (this->Get(0, 0) * o.Get(0, 1)) + (this->Get(0, 1) * o.Get(1, 1)) + (this->Get(0, 2) * o.Get(2, 1)) + (this->Get(0, 3) * o.Get(3, 1));
m.Get(0, 2) = (this->Get(0, 0) * o.Get(0, 2)) + (this->Get(0, 1) * o.Get(1, 2)) + (this->Get(0, 2) * o.Get(2, 2)) + (this->Get(0, 3) * o.Get(3, 2));
m.Get(0, 3) = (this->Get(0, 0) * o.Get(0, 3)) + (this->Get(0, 1) * o.Get(1, 3)) + (this->Get(0, 2) * o.Get(2, 3)) + (this->Get(0, 3) * o.Get(3, 3));
m.Get(1, 0) = (this->Get(1, 0) * o.Get(0, 0)) + (this->Get(1, 1) * o.Get(1, 0)) + (this->Get(1, 2) * o.Get(2, 0)) + (this->Get(1, 3) * o.Get(3, 0));
m.Get(1, 1) = (this->Get(1, 0) * o.Get(0, 1)) + (this->Get(1, 1) * o.Get(1, 1)) + (this->Get(1, 2) * o.Get(2, 1)) + (this->Get(1, 3) * o.Get(3, 1));
m.Get(1, 2) = (this->Get(1, 0) * o.Get(0, 2)) + (this->Get(1, 1) * o.Get(1, 2)) + (this->Get(1, 2) * o.Get(2, 2)) + (this->Get(1, 3) * o.Get(3, 2));
m.Get(1, 3) = (this->Get(1, 0) * o.Get(0, 3)) + (this->Get(1, 1) * o.Get(1, 3)) + (this->Get(1, 2) * o.Get(2, 3)) + (this->Get(1, 3) * o.Get(3, 3));
m.Get(2, 0) = (this->Get(2, 0) * o.Get(0, 0)) + (this->Get(2, 1) * o.Get(1, 0)) + (this->Get(2, 2) * o.Get(2, 0)) + (this->Get(2, 3) * o.Get(3, 0));
m.Get(2, 1) = (this->Get(2, 0) * o.Get(0, 1)) + (this->Get(2, 1) * o.Get(1, 1)) + (this->Get(2, 2) * o.Get(2, 1)) + (this->Get(2, 3) * o.Get(3, 1));
m.Get(2, 2) = (this->Get(2, 0) * o.Get(0, 2)) + (this->Get(2, 1) * o.Get(1, 2)) + (this->Get(2, 2) * o.Get(2, 2)) + (this->Get(2, 3) * o.Get(3, 2));
m.Get(2, 3) = (this->Get(2, 0) * o.Get(0, 3)) + (this->Get(2, 1) * o.Get(1, 3)) + (this->Get(2, 2) * o.Get(2, 3)) + (this->Get(2, 3) * o.Get(3, 3));
m.Get(3, 0) = (this->Get(3, 0) * o.Get(0, 0)) + (this->Get(3, 1) * o.Get(1, 0)) + (this->Get(3, 2) * o.Get(2, 0)) + (this->Get(3, 3) * o.Get(3, 0));
m.Get(3, 1) = (this->Get(3, 0) * o.Get(0, 1)) + (this->Get(3, 1) * o.Get(1, 1)) + (this->Get(3, 2) * o.Get(2, 1)) + (this->Get(3, 3) * o.Get(3, 1));
m.Get(3, 2) = (this->Get(3, 0) * o.Get(0, 2)) + (this->Get(3, 1) * o.Get(1, 2)) + (this->Get(3, 2) * o.Get(2, 2)) + (this->Get(3, 3) * o.Get(3, 2));
m.Get(3, 3) = (this->Get(3, 0) * o.Get(0, 3)) + (this->Get(3, 1) * o.Get(1, 3)) + (this->Get(3, 2) * o.Get(2, 3)) + (this->Get(3, 3) * o.Get(3, 3));
return m;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::operator*(const Basic_Block<T>& other) const {
return this->MMul(other);
}
template <typename T>
void Basic_Block<T>::MMulInplace(const Basic_Block<T>& o) {
Basic_Block<T> m = *this;
// Maybe pre-calculate the 1d-index...?
this->Get(0, 0) = (m.Get(0, 0) * o.Get(0, 0)) + (m.Get(0, 1) * o.Get(1, 0)) + (m.Get(0, 2) * o.Get(2, 0)) + (m.Get(0, 3) * o.Get(3, 0));
this->Get(0, 1) = (m.Get(0, 0) * o.Get(0, 1)) + (m.Get(0, 1) * o.Get(1, 1)) + (m.Get(0, 2) * o.Get(2, 1)) + (m.Get(0, 3) * o.Get(3, 1));
this->Get(0, 2) = (m.Get(0, 0) * o.Get(0, 2)) + (m.Get(0, 1) * o.Get(1, 2)) + (m.Get(0, 2) * o.Get(2, 2)) + (m.Get(0, 3) * o.Get(3, 2));
this->Get(0, 3) = (m.Get(0, 0) * o.Get(0, 3)) + (m.Get(0, 1) * o.Get(1, 3)) + (m.Get(0, 2) * o.Get(2, 3)) + (m.Get(0, 3) * o.Get(3, 3));
this->Get(1, 0) = (m.Get(1, 0) * o.Get(0, 0)) + (m.Get(1, 1) * o.Get(1, 0)) + (m.Get(1, 2) * o.Get(2, 0)) + (m.Get(1, 3) * o.Get(3, 0));
this->Get(1, 1) = (m.Get(1, 0) * o.Get(0, 1)) + (m.Get(1, 1) * o.Get(1, 1)) + (m.Get(1, 2) * o.Get(2, 1)) + (m.Get(1, 3) * o.Get(3, 1));
this->Get(1, 2) = (m.Get(1, 0) * o.Get(0, 2)) + (m.Get(1, 1) * o.Get(1, 2)) + (m.Get(1, 2) * o.Get(2, 2)) + (m.Get(1, 3) * o.Get(3, 2));
this->Get(1, 3) = (m.Get(1, 0) * o.Get(0, 3)) + (m.Get(1, 1) * o.Get(1, 3)) + (m.Get(1, 2) * o.Get(2, 3)) + (m.Get(1, 3) * o.Get(3, 3));
this->Get(2, 0) = (m.Get(2, 0) * o.Get(0, 0)) + (m.Get(2, 1) * o.Get(1, 0)) + (m.Get(2, 2) * o.Get(2, 0)) + (m.Get(2, 3) * o.Get(3, 0));
this->Get(2, 1) = (m.Get(2, 0) * o.Get(0, 1)) + (m.Get(2, 1) * o.Get(1, 1)) + (m.Get(2, 2) * o.Get(2, 1)) + (m.Get(2, 3) * o.Get(3, 1));
this->Get(2, 2) = (m.Get(2, 0) * o.Get(0, 2)) + (m.Get(2, 1) * o.Get(1, 2)) + (m.Get(2, 2) * o.Get(2, 2)) + (m.Get(2, 3) * o.Get(3, 2));
this->Get(2, 3) = (m.Get(2, 0) * o.Get(0, 3)) + (m.Get(2, 1) * o.Get(1, 3)) + (m.Get(2, 2) * o.Get(2, 3)) + (m.Get(2, 3) * o.Get(3, 3));
this->Get(3, 0) = (m.Get(3, 0) * o.Get(0, 0)) + (m.Get(3, 1) * o.Get(1, 0)) + (m.Get(3, 2) * o.Get(2, 0)) + (m.Get(3, 3) * o.Get(3, 0));
this->Get(3, 1) = (m.Get(3, 0) * o.Get(0, 1)) + (m.Get(3, 1) * o.Get(1, 1)) + (m.Get(3, 2) * o.Get(2, 1)) + (m.Get(3, 3) * o.Get(3, 1));
this->Get(3, 2) = (m.Get(3, 0) * o.Get(0, 2)) + (m.Get(3, 1) * o.Get(1, 2)) + (m.Get(3, 2) * o.Get(2, 2)) + (m.Get(3, 3) * o.Get(3, 2));
this->Get(3, 3) = (m.Get(3, 0) * o.Get(0, 3)) + (m.Get(3, 1) * o.Get(1, 3)) + (m.Get(3, 2) * o.Get(2, 3)) + (m.Get(3, 3) * o.Get(3, 3));
return;
}
template <typename T>
Basic_Block<T>& Basic_Block<T>::operator*=(const Basic_Block<T>& other) {
MMulInplace(other);
return *this;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::Xor(const Basic_Block<T>& other) const {
Basic_Block<T> m;
for (std::size_t i = 0; i < data.size(); i++) {
m.Get(i) = this->Get(i) ^ other.Get(i);
}
return m;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::operator^(const Basic_Block<T>& other) const {
return Xor(other);
}
template <typename T>
void Basic_Block<T>::XorInplace(const Basic_Block<T>& other) {
for (std::size_t i = 0; i < data.size(); i++) {
this->Get(i) ^= other.Get(i);
}
return;
}
template <typename T>
Basic_Block<T>& Basic_Block<T>::operator^=(const Basic_Block<T>& other) {
XorInplace(other);
return *this;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::Add(const Basic_Block<T>& other) const {
Basic_Block<T> m;
for (std::size_t i = 0; i < data.size(); i++) {
m.Get(i) = this->Get(i) + other.Get(i);
}
return m;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::operator+(const Basic_Block<T>& other) const {
return Add(other);
}
template <typename T>
void Basic_Block<T>::AddInplace(const Basic_Block<T>& other) {
for (std::size_t i = 0; i < data.size(); i++) {
this->Get(i) += other.Get(i);
}
return;
}
template <typename T>
Basic_Block<T>& Basic_Block<T>::operator+=(const Basic_Block<T>& other) {
AddInplace(other);
return *this;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::Sub(const Basic_Block<T>& other) const {
Basic_Block<T> m;
for (std::size_t i = 0; i < data.size(); i++) {
m.Get(i) = this->Get(i) - other.Get(i);
}
return m;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::operator-(const Basic_Block<T>& other) const {
return Sub(other);
}
template <typename T>
void Basic_Block<T>::SubInplace(const Basic_Block<T>& other) {
for (std::size_t i = 0; i < data.size(); i++) {
this->Get(i) -= other.Get(i);
}
return;
}
template <typename T>
Basic_Block<T>& Basic_Block<T>::operator-=(const Basic_Block<T>& other) {
SubInplace(other);
return *this;
}
template <typename T>
void Basic_Block<T>::ShiftRowsUpInplace() {
Basic_Block<T> tmp = *this;
Get(MAT_INDEX(0, 0)) = tmp.Get(MAT_INDEX(1, 0));
Get(MAT_INDEX(0, 1)) = tmp.Get(MAT_INDEX(1, 1));
Get(MAT_INDEX(0, 2)) = tmp.Get(MAT_INDEX(1, 2));
Get(MAT_INDEX(0, 3)) = tmp.Get(MAT_INDEX(1, 3));
Get(MAT_INDEX(1, 0)) = tmp.Get(MAT_INDEX(2, 0));
Get(MAT_INDEX(1, 1)) = tmp.Get(MAT_INDEX(2, 1));
Get(MAT_INDEX(1, 2)) = tmp.Get(MAT_INDEX(2, 2));
Get(MAT_INDEX(1, 3)) = tmp.Get(MAT_INDEX(2, 3));
Get(MAT_INDEX(2, 0)) = tmp.Get(MAT_INDEX(3, 0));
Get(MAT_INDEX(2, 1)) = tmp.Get(MAT_INDEX(3, 1));
Get(MAT_INDEX(2, 2)) = tmp.Get(MAT_INDEX(3, 2));
Get(MAT_INDEX(2, 3)) = tmp.Get(MAT_INDEX(3, 3));
Get(MAT_INDEX(3, 0)) = tmp.Get(MAT_INDEX(0, 0));
Get(MAT_INDEX(3, 1)) = tmp.Get(MAT_INDEX(0, 1));
Get(MAT_INDEX(3, 2)) = tmp.Get(MAT_INDEX(0, 2));
Get(MAT_INDEX(3, 3)) = tmp.Get(MAT_INDEX(0, 3));
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftRowsUp() const {
Basic_Block<T> b;
b.Get(MAT_INDEX(0, 0)) = Get(MAT_INDEX(1, 0));
b.Get(MAT_INDEX(0, 1)) = Get(MAT_INDEX(1, 1));
b.Get(MAT_INDEX(0, 2)) = Get(MAT_INDEX(1, 2));
b.Get(MAT_INDEX(0, 3)) = Get(MAT_INDEX(1, 3));
b.Get(MAT_INDEX(1, 0)) = Get(MAT_INDEX(2, 0));
b.Get(MAT_INDEX(1, 1)) = Get(MAT_INDEX(2, 1));
b.Get(MAT_INDEX(1, 2)) = Get(MAT_INDEX(2, 2));
b.Get(MAT_INDEX(1, 3)) = Get(MAT_INDEX(2, 3));
b.Get(MAT_INDEX(2, 0)) = Get(MAT_INDEX(3, 0));
b.Get(MAT_INDEX(2, 1)) = Get(MAT_INDEX(3, 1));
b.Get(MAT_INDEX(2, 2)) = Get(MAT_INDEX(3, 2));
b.Get(MAT_INDEX(2, 3)) = Get(MAT_INDEX(3, 3));
b.Get(MAT_INDEX(3, 0)) = Get(MAT_INDEX(0, 0));
b.Get(MAT_INDEX(3, 1)) = Get(MAT_INDEX(0, 1));
b.Get(MAT_INDEX(3, 2)) = Get(MAT_INDEX(0, 2));
b.Get(MAT_INDEX(3, 3)) = Get(MAT_INDEX(0, 3));
return b;
}
template <typename T>
void Basic_Block<T>::ShiftRowsDownInplace() {
Basic_Block<T> tmp = *this;
Get(MAT_INDEX(0, 0)) = tmp.Get(MAT_INDEX(3, 0));
Get(MAT_INDEX(0, 1)) = tmp.Get(MAT_INDEX(3, 1));
Get(MAT_INDEX(0, 2)) = tmp.Get(MAT_INDEX(3, 2));
Get(MAT_INDEX(0, 3)) = tmp.Get(MAT_INDEX(3, 3));
Get(MAT_INDEX(1, 0)) = tmp.Get(MAT_INDEX(0, 0));
Get(MAT_INDEX(1, 1)) = tmp.Get(MAT_INDEX(0, 1));
Get(MAT_INDEX(1, 2)) = tmp.Get(MAT_INDEX(0, 2));
Get(MAT_INDEX(1, 3)) = tmp.Get(MAT_INDEX(0, 3));
Get(MAT_INDEX(2, 0)) = tmp.Get(MAT_INDEX(1, 0));
Get(MAT_INDEX(2, 1)) = tmp.Get(MAT_INDEX(1, 1));
Get(MAT_INDEX(2, 2)) = tmp.Get(MAT_INDEX(1, 2));
Get(MAT_INDEX(2, 3)) = tmp.Get(MAT_INDEX(1, 3));
Get(MAT_INDEX(3, 0)) = tmp.Get(MAT_INDEX(2, 0));
Get(MAT_INDEX(3, 1)) = tmp.Get(MAT_INDEX(2, 1));
Get(MAT_INDEX(3, 2)) = tmp.Get(MAT_INDEX(2, 2));
Get(MAT_INDEX(3, 3)) = tmp.Get(MAT_INDEX(2, 3));
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftRowsDown() const {
Basic_Block<T> b;
b.Get(MAT_INDEX(0, 0)) = Get(MAT_INDEX(3, 0));
b.Get(MAT_INDEX(0, 1)) = Get(MAT_INDEX(3, 1));
b.Get(MAT_INDEX(0, 2)) = Get(MAT_INDEX(3, 2));
b.Get(MAT_INDEX(0, 3)) = Get(MAT_INDEX(3, 3));
b.Get(MAT_INDEX(1, 0)) = Get(MAT_INDEX(0, 0));
b.Get(MAT_INDEX(1, 1)) = Get(MAT_INDEX(0, 1));
b.Get(MAT_INDEX(1, 2)) = Get(MAT_INDEX(0, 2));
b.Get(MAT_INDEX(1, 3)) = Get(MAT_INDEX(0, 3));
b.Get(MAT_INDEX(2, 0)) = Get(MAT_INDEX(1, 0));
b.Get(MAT_INDEX(2, 1)) = Get(MAT_INDEX(1, 1));
b.Get(MAT_INDEX(2, 2)) = Get(MAT_INDEX(1, 2));
b.Get(MAT_INDEX(2, 3)) = Get(MAT_INDEX(1, 3));
b.Get(MAT_INDEX(3, 0)) = Get(MAT_INDEX(2, 0));
b.Get(MAT_INDEX(3, 1)) = Get(MAT_INDEX(2, 1));
b.Get(MAT_INDEX(3, 2)) = Get(MAT_INDEX(2, 2));
b.Get(MAT_INDEX(3, 3)) = Get(MAT_INDEX(2, 3));
return b;
}
template <typename T>
void Basic_Block<T>::ShiftColumnsLeftInplace() {
Basic_Block<T> tmp = *this;
Get(MAT_INDEX(0, 0)) = tmp.Get(MAT_INDEX(0, 1));
Get(MAT_INDEX(1, 0)) = tmp.Get(MAT_INDEX(1, 1));
Get(MAT_INDEX(2, 0)) = tmp.Get(MAT_INDEX(2, 1));
Get(MAT_INDEX(3, 0)) = tmp.Get(MAT_INDEX(3, 1));
Get(MAT_INDEX(0, 1)) = tmp.Get(MAT_INDEX(0, 2));
Get(MAT_INDEX(1, 1)) = tmp.Get(MAT_INDEX(1, 2));
Get(MAT_INDEX(2, 1)) = tmp.Get(MAT_INDEX(2, 2));
Get(MAT_INDEX(3, 1)) = tmp.Get(MAT_INDEX(3, 2));
Get(MAT_INDEX(0, 2)) = tmp.Get(MAT_INDEX(0, 3));
Get(MAT_INDEX(1, 2)) = tmp.Get(MAT_INDEX(1, 3));
Get(MAT_INDEX(2, 2)) = tmp.Get(MAT_INDEX(2, 3));
Get(MAT_INDEX(3, 2)) = tmp.Get(MAT_INDEX(3, 3));
Get(MAT_INDEX(0, 3)) = tmp.Get(MAT_INDEX(0, 0));
Get(MAT_INDEX(1, 3)) = tmp.Get(MAT_INDEX(1, 0));
Get(MAT_INDEX(2, 3)) = tmp.Get(MAT_INDEX(2, 0));
Get(MAT_INDEX(3, 3)) = tmp.Get(MAT_INDEX(3, 0));
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftColumnsLeft() const {
Basic_Block<T> b;
b.Get(MAT_INDEX(0, 0)) = Get(MAT_INDEX(0, 1));
b.Get(MAT_INDEX(1, 0)) = Get(MAT_INDEX(1, 1));
b.Get(MAT_INDEX(2, 0)) = Get(MAT_INDEX(2, 1));
b.Get(MAT_INDEX(3, 0)) = Get(MAT_INDEX(3, 1));
b.Get(MAT_INDEX(0, 1)) = Get(MAT_INDEX(0, 2));
b.Get(MAT_INDEX(1, 1)) = Get(MAT_INDEX(1, 2));
b.Get(MAT_INDEX(2, 1)) = Get(MAT_INDEX(2, 2));
b.Get(MAT_INDEX(3, 1)) = Get(MAT_INDEX(3, 2));
b.Get(MAT_INDEX(0, 2)) = Get(MAT_INDEX(0, 3));
b.Get(MAT_INDEX(1, 2)) = Get(MAT_INDEX(1, 3));
b.Get(MAT_INDEX(2, 2)) = Get(MAT_INDEX(2, 3));
b.Get(MAT_INDEX(3, 2)) = Get(MAT_INDEX(3, 3));
b.Get(MAT_INDEX(0, 3)) = Get(MAT_INDEX(0, 0));
b.Get(MAT_INDEX(1, 3)) = Get(MAT_INDEX(1, 0));
b.Get(MAT_INDEX(2, 3)) = Get(MAT_INDEX(2, 0));
b.Get(MAT_INDEX(3, 3)) = Get(MAT_INDEX(3, 0));
return b;
}
template <typename T>
void Basic_Block<T>::ShiftColumnsRightInplace() {
Basic_Block<T> tmp = *this;
Get(MAT_INDEX(0, 1)) = tmp.Get(MAT_INDEX(0, 0));
Get(MAT_INDEX(1, 1)) = tmp.Get(MAT_INDEX(1, 0));
Get(MAT_INDEX(2, 1)) = tmp.Get(MAT_INDEX(2, 0));
Get(MAT_INDEX(3, 1)) = tmp.Get(MAT_INDEX(3, 0));
Get(MAT_INDEX(0, 2)) = tmp.Get(MAT_INDEX(0, 1));
Get(MAT_INDEX(1, 2)) = tmp.Get(MAT_INDEX(1, 1));
Get(MAT_INDEX(2, 2)) = tmp.Get(MAT_INDEX(2, 1));
Get(MAT_INDEX(3, 2)) = tmp.Get(MAT_INDEX(3, 1));
Get(MAT_INDEX(0, 3)) = tmp.Get(MAT_INDEX(0, 2));
Get(MAT_INDEX(1, 3)) = tmp.Get(MAT_INDEX(1, 2));
Get(MAT_INDEX(2, 3)) = tmp.Get(MAT_INDEX(2, 2));
Get(MAT_INDEX(3, 3)) = tmp.Get(MAT_INDEX(3, 2));
Get(MAT_INDEX(0, 0)) = tmp.Get(MAT_INDEX(0, 3));
Get(MAT_INDEX(1, 0)) = tmp.Get(MAT_INDEX(1, 3));
Get(MAT_INDEX(2, 0)) = tmp.Get(MAT_INDEX(2, 3));
Get(MAT_INDEX(3, 0)) = tmp.Get(MAT_INDEX(3, 3));
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftColumnsRight() const {
Basic_Block<T> b;
b.Get(MAT_INDEX(0, 1)) = Get(MAT_INDEX(0, 0));
b.Get(MAT_INDEX(1, 1)) = Get(MAT_INDEX(1, 0));
b.Get(MAT_INDEX(2, 1)) = Get(MAT_INDEX(2, 0));
b.Get(MAT_INDEX(3, 1)) = Get(MAT_INDEX(3, 0));
b.Get(MAT_INDEX(0, 2)) = Get(MAT_INDEX(0, 1));
b.Get(MAT_INDEX(1, 2)) = Get(MAT_INDEX(1, 1));
b.Get(MAT_INDEX(2, 2)) = Get(MAT_INDEX(2, 1));
b.Get(MAT_INDEX(3, 2)) = Get(MAT_INDEX(3, 1));
b.Get(MAT_INDEX(0, 3)) = Get(MAT_INDEX(0, 2));
b.Get(MAT_INDEX(1, 3)) = Get(MAT_INDEX(1, 2));
b.Get(MAT_INDEX(2, 3)) = Get(MAT_INDEX(2, 2));
b.Get(MAT_INDEX(3, 3)) = Get(MAT_INDEX(3, 2));
b.Get(MAT_INDEX(0, 0)) = Get(MAT_INDEX(0, 3));
b.Get(MAT_INDEX(1, 0)) = Get(MAT_INDEX(1, 3));
b.Get(MAT_INDEX(2, 0)) = Get(MAT_INDEX(2, 3));
b.Get(MAT_INDEX(3, 0)) = Get(MAT_INDEX(3, 3));
return b;
}
template <typename T>
void Basic_Block<T>::ShiftCellsLeftInplace() {
Basic_Block<T> tmp = *this;
Get(15) = tmp.Get(0);
for (std::size_t i = 0; i < 15; i++) {
Get(i) = tmp.Get(i+1);
}
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftCellsLeft() const {
Basic_Block<T> b;
b.Get(15) = Get(0);
for (std::size_t i = 0; i < 15; i++) {
b.Get(i) = Get(i+1);
}
return b;
}
template <typename T>
void Basic_Block<T>::ShiftCellsRightInplace() {
Basic_Block<T> tmp = *this;
Get(0) = tmp.Get(15);
for (std::size_t i = 1; i < 16; i++) {
Get(i) = tmp.Get(i-1);
}
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftCellsRight() const {
Basic_Block<T> b;
b.Get(0) = Get(15);
for (std::size_t i = 1; i < 16; i++) {
b.Get(i) = Get(i-1);
}
return b;
}
template <typename T>
Basic_Block<T>& Basic_Block<T>::operator=(const Basic_Block<T>& other) {
data = other.data;
return *this;
}
template <typename T>
bool Basic_Block<T>::GetBit(const std::size_t index) const {
// Fetch index of integer the bit is located in
const std::size_t intIndex = index / CHUNK_SIZE_BITS;
// Fetch bit index relative to that int
const std::size_t relBitIndex = index - (intIndex * CHUNK_SIZE_BITS);
// Pre-calculate the bitmask to use
const std::size_t bitmask = 1 << (CHUNK_SIZE_BITS - relBitIndex - 1);
return data[intIndex] & bitmask;
}
template <typename T>
void Basic_Block<T>::SetBit(const std::size_t index, const bool state) {
// Fetch index of integer the bit is located in
const std::size_t intIndex = index / CHUNK_SIZE_BITS;
// Fetch bit index relative to that int
const std::size_t relBitIndex = index - (intIndex * CHUNK_SIZE_BITS);
// Pre-calculate the bitmask to use
const std::size_t bitmask = 1 << (CHUNK_SIZE_BITS - relBitIndex - 1);
// Set the bit
if (state) {
data[intIndex] |= bitmask;
}
// Clear the bit
else {
data[intIndex] &= ~bitmask;
}
return;
}
template <typename T>
void Basic_Block<T>::FlipBit(const std::size_t index) {
// Fetch index of integer the bit is located in
const std::size_t intIndex = index / CHUNK_SIZE_BITS;
// Fetch bit index relative to that int
const std::size_t relBitIndex = index - (intIndex * CHUNK_SIZE_BITS);
// Pre-calculate the bitmask to use
const std::size_t bitmask = 1 << (CHUNK_SIZE_BITS - relBitIndex - 1);
data[intIndex] ^= bitmask;
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftBitsLeft() const {
Basic_Block<T> b;
// First, copy this block over
b = *this;
// Then, shift all integers individually
for (std::size_t i = 0; i < data.size(); i++) {
b.data[i] <<= 1;
}
// Current state: the LSB is zero everywhere. We have to carry
// it over manually from the previous state.
// Carry over the MSB of data[i] to LSB of data[i-1]
constexpr std::size_t bitmaskMsb = 1 << (CHUNK_SIZE_BITS - 1);
constexpr std::size_t bitmaskLsb = 1;
for (int i = 0; i < data.size(); i++) {
const bool msb = data[i] & bitmaskMsb;
// Set the lsb
if (msb) {
b.data[Mod(i-1, data.size())] |= bitmaskLsb;
}
// Clear the lsb
else {
b.data[Mod(i-1, data.size())] &= ~bitmaskLsb;
}
}
return b;
}
template <typename T>
void Basic_Block<T>::ShiftBitsLeftInplace() {
Basic_Block<T> tmp = *this;
// Then, shift all integers individually
for (std::size_t i = 0; i < data.size(); i++) {
data[i] <<= 1;
}
// Current state: the LSB is zero everywhere. We have to carry
// it over manually from the previous state.
// Carry over the MSB of data[i] to LSB of data[i-1]
constexpr std::size_t bitmaskMsb = 1 << (CHUNK_SIZE_BITS - 1);
constexpr std::size_t bitmaskLsb = 1;
for (int i = 0; i < data.size(); i++) {
const bool msb = tmp.data[i] & bitmaskMsb;
// Set the lsb
if (msb) {
data[Mod(i-1, data.size())] |= bitmaskLsb;
}
// Clear the lsb
else {
data[Mod(i-1, data.size())] &= ~bitmaskLsb;
}
}
return;
}
template <typename T>
Basic_Block<T> Basic_Block<T>::ShiftBitsRight() const {
Basic_Block<T> b;
// First, copy this block over
b = *this;
// Then, shift all integers individually
for (std::size_t i = 0; i < data.size(); i++) {
b.data[i] >>= 1;
}
// Current state: the LSB is zero everywhere. We have to carry
// it over manually from the previous state.
// Carry over the LSB of data[i] to MSB of data[i+1]
constexpr std::size_t bitmaskMsb = 1 << (CHUNK_SIZE_BITS - 1);
constexpr std::size_t bitmaskLsb = 1;
for (int i = 0; i < data.size(); i++) {
const bool lsb = data[i] & bitmaskLsb;
// Set the msb
if (lsb) {
b.data[Mod(i+1, data.size())] |= bitmaskMsb;
}
// Clear the msb
else {
b.data[Mod(i+1, data.size())] &= ~bitmaskMsb;
}
}
return b;
}
template <typename T>
void Basic_Block<T>::ShiftBitsRightInplace() {
Basic_Block<T> tmp = *this;
// Then, shift all integers individually
for (std::size_t i = 0; i < data.size(); i++) {
data[i] >>= 1;
}
// Current state: the LSB is zero everywhere. We have to carry
// it over manually from the previous state.
// Carry over the LSB of data[i] to MSB of data[i+1]
constexpr std::size_t bitmaskMsb = 1 << (CHUNK_SIZE_BITS - 1);
constexpr std::size_t bitmaskLsb = 1;
for (int i = 0; i < data.size(); i++) {
const bool lsb = tmp.data[i] & bitmaskLsb;
// Set the msb
if (lsb) {
data[Mod(i+1, data.size())] |= bitmaskMsb;
}
// Clear the msb
else {
data[Mod(i+1, data.size())] &= ~bitmaskMsb;
}
}
return;
}
template <typename T>
T& Basic_Block<T>::Get(const std::uint8_t row, const std::uint8_t column){
return data[MAT_INDEX(row, column)];
}
template <typename T>
const T& Basic_Block<T>::Get(const std::uint8_t row, const std::uint8_t column) const {
return data[MAT_INDEX(row, column)];
}
template <typename T>
T& Basic_Block<T>::Get(const std::uint8_t index) {
return data[index];
}
template <typename T>
const T& Basic_Block<T>::Get(const std::uint8_t index) const {
return data[index];
}
template <typename T>
T& Basic_Block<T>::operator[](const std::uint8_t index) {
return data[index];
}
template <typename T>
const T& Basic_Block<T>::operator[](const std::uint8_t index) const {
return data[index];
}
template <typename T>
T* Basic_Block<T>::Data() noexcept {
return data.data();
}
template <typename T>
const T* Basic_Block<T>::Data() const noexcept {
return data.data();
}
template <typename T>
bool Basic_Block<T>::operator==(const Basic_Block<T>& other) const {
return data == other.data;
}
template <typename T>
bool Basic_Block<T>::operator!=(const Basic_Block<T>& other) const {
return data != other.data;
}
#if defined _WIN32 || defined _WIN64
#pragma optimize("", off )
#elif defined __GNUG__
#pragma GCC push_options
#pragma GCC optimize ("O0")
#endif
template <typename T>
Basic_Block<T>::~Basic_Block() {
Reset();
}
template <typename T>
void Basic_Block<T>::Reset() {
memset(data.data(), 0, CHUNK_SIZE*data.size());
return;
}
#if defined _WIN32 || defined _WIN64
#pragma optimize("", on )
#elif defined __GNUG__
#pragma GCC pop_options
#endif
// Instantiate templates
template class Basic_Block<std::uint32_t>;
template class Basic_Block<std::uint16_t>;
}
#undef MAT_INDEX

View File

@ -1,135 +0,0 @@
#include <iostream>
#include <vector>
#include "GCrypt/Cipher.h"
#include "GCrypt/Util.h"
#include "GCrypt/InitializationVector.h"
namespace Leonetienne::GCrypt {
Cipher::Cipher(const Block& key)
:
key { key },
initializationVector(InitializationVector(key)) {
return;
}
Cipher::Cipher(const std::string& password)
:
key { PasswordToKey(password) },
initializationVector(InitializationVector(key)) {
return;
}
Cipher::~Cipher() {
// Clear key memory
ZeroKeyMemory();
return;
}
void Cipher::SetKey(const Block& key) {
ZeroKeyMemory();
this->key = key;
return;
}
void Cipher::SetPassword(const std::string& password) {
ZeroKeyMemory();
key = PasswordToKey(password);
return;
}
Flexblock Cipher::Encipher(const Flexblock& data, bool printProgress) const {
// Split cleartext into blocks
std::vector<Block> blocks;
for (std::size_t i = 0; i < data.size(); i += BLOCK_SIZE) {
blocks.push_back(Block(
PadStringToLength(data.substr(i, BLOCK_SIZE), BLOCK_SIZE, '0', false))
);
}
// Encrypt individual blocks using cipher block chaining
Feistel feistel(key);
for (std::size_t i = 0; i < blocks.size(); i++) {
// Print reports if desired. If we have > 1000 blocks, print one report every 100 blocks. Otherwise for every 10th block.
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] : 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
std::stringstream ss;
for (Block& b : blocks) {
ss << b;
}
// Return it
return ss.str();
}
Flexblock Cipher::Decipher(const Flexblock& data, bool printProgress) const {
// Split ciphertext into blocks
std::vector<Block> blocks;
for (std::size_t i = 0; i < data.size(); i += BLOCK_SIZE) {
blocks.push_back(Block(
PadStringToLength(data.substr(i, BLOCK_SIZE), BLOCK_SIZE, '0', false))
);
}
// Decrypt individual blocks
Feistel feistel(key);
// We can't do this in-loop for decryption, because we are decrypting the blocks in-place.
Block lastBlock = initializationVector;
for (std::size_t i = 0; i < blocks.size(); i++) {
// Print reports if desired. If we have > 1000 blocks, print one report every 100 blocks. Otherwise for every 10th block.
if ((i % ((blocks.size() > 1000) ? 100 : 10) == 0) && (printProgress)) {
std::cout << "Decrypting... (Block " << i << " / " << blocks.size() << " - " << ((float)i*100/ blocks.size()) << "%)" << std::endl;
}
Block tmpCopy = blocks[i];
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);
}
// Concatenate ciphertext blocks back into a flexblock
std::stringstream ss;
for (Block& b : blocks) {
ss << b;
}
// Return it
return ss.str();
}
// These pragmas only work for MSVC and g++, as far as i know. Beware!!!
#if defined _WIN32 || defined _WIN64
#pragma optimize("", off )
#elif defined __GNUG__
#pragma GCC push_options
#pragma GCC optimize ("O0")
#endif
void Cipher::ZeroKeyMemory() {
key.reset();
return;
}
#if defined _WIN32 || defined _WIN64
#pragma optimize("", on )
#elif defined __GNUG__
#pragma GCC pop_options
#endif
}

View File

@ -2,242 +2,256 @@
#include "GCrypt/Feistel.h"
#include "GCrypt/Util.h"
#include "GCrypt/Config.h"
#include "GCrypt/SBoxLookup.h"
namespace Leonetienne::GCrypt {
Feistel::Feistel(const Block& key) {
SetKey(key);
return;
Feistel::Feistel() {
}
Feistel::Feistel(const Key& key) {
SetKey(key);
}
Feistel::~Feistel() {
ZeroKeyMemory();
return;
ZeroKeyMemory();
}
void Feistel::SetKey(const Block& key) {
GenerateRoundKeys(key);
return;
void Feistel::SetKey(const Key& key) {
GenerateRoundKeys(key);
isInitialized = true;
}
Block Feistel::Encipher(const Block& data) {
return Run(data, false);
return Run(data, false);
}
Block Feistel::Decipher(const Block& data) {
return Run(data, true);
return Run(data, true);
}
Block Feistel::Run(const Block& data, bool reverseKeys) {
const auto splitData = FeistelSplit(data);
Halfblock l = splitData.first;
Halfblock r = splitData.second;
Block Feistel::Run(const Block& data, bool modeEncrypt) {
if (!isInitialized) {
throw std::runtime_error("Attempted to digest data on uninitialized GCipher!");
}
Halfblock tmp;
const auto splitData = FeistelSplit(data);
Halfblock l = splitData.first;
Halfblock r = splitData.second;
for (std::size_t i = 0; i < N_ROUNDS; i++) {
// Calculate key index
std::size_t keyIndex;
if (reverseKeys) {
keyIndex = N_ROUNDS - i - 1;
}
else {
keyIndex = i;
}
Halfblock tmp;
// Do a feistel round
tmp = r;
r = l ^ F(r, roundKeys[keyIndex]);
l = tmp;
for (std::size_t i = 0; i < N_ROUNDS; i++) {
// Encryption
if (modeEncrypt) {
const std::size_t keyIndex = i;
// Do a feistel round
tmp = r;
r = l ^ F(r, roundKeys[keyIndex]);
l = tmp;
// Jumble it up a bit more
l.ShiftRowsUpInplace();
l.ShiftCellsRightInplace();
l.ShiftBitsLeftInplace();
l.ShiftColumnsLeftInplace();
// Seal all these operations with a key
l += ReductionFunction(roundKeys[keyIndex]);
}
// Block has finished de*ciphering.
// Let's generate a new set of round keys.
GenerateRoundKeys((Block)roundKeys.back());
// Decryption
else {
// Decryption needs keys in reverse order
const std::size_t keyIndex = N_ROUNDS - i - 1;
return FeistelCombine(r, l);
// Unjumble the jumble
r -= ReductionFunction(roundKeys[keyIndex]);
r.ShiftColumnsRightInplace();
r.ShiftBitsRightInplace();
r.ShiftCellsLeftInplace();
r.ShiftRowsDownInplace();
// Do a feistel round
tmp = r;
r = l ^ F(r, roundKeys[keyIndex]);
l = tmp;
}
}
// Block has finished de*ciphering.
// Let's generate a new set of round keys.
GenerateRoundKeys(roundKeys.back());
return FeistelCombine(r, l);
}
Halfblock Feistel::F(Halfblock m, const Block& key) {
// Made-up F function
Halfblock Feistel::F(Halfblock m, const Key& key) {
// Expand to full bitwidth
Block m_expanded = ExpansionFunction(m);
// Made-up F function:
// Expand to full bitwidth
Block m_expanded = ExpansionFunction(m);
// Shift to left by 1
m_expanded = Shiftl(m_expanded, 1);
// Mix up the block a bit
m_expanded.ShiftCellsRightInplace();
m_expanded.ShiftRowsUpInplace();
// Xor with key
m_expanded ^= key;
// Matrix-mult with key (this is irreversible)
m_expanded *= key;
// Non-linearly apply subsitution boxes
std::stringstream ss;
const std::string m_str = m_expanded.to_string();
// Now do a bitshift
m_expanded.ShiftBitsLeftInplace();
for (std::size_t i = 0; i < BLOCK_SIZE; i += 4) {
ss << SBox(m_str.substr(i, 4));
}
// Apply the sbox
SBox(m_expanded);
m_expanded = Block(ss.str());
// Reduce back to a halfblock
Halfblock hb = ReductionFunction(m_expanded);
// Return the compressed version
return CompressionFunction(m_expanded);
// To jumble it up a last time,
// matrix-multiply it with the input halfblock
hb *= m;
return hb;
}
std::pair<Halfblock, Halfblock> Feistel::FeistelSplit(const Block& block) {
const std::string bits = block.to_string();
Halfblock l;
Halfblock r;
Halfblock l(bits.substr(0, bits.size() / 2));
Halfblock r(bits.substr(bits.size() / 2));
memcpy(l.Data(), block.Data(), Halfblock::BLOCK_SIZE);
memcpy(r.Data(), block.Data() + 8, Halfblock::BLOCK_SIZE);
// +8, because 8 is HALF the number of elements in the array. We only want to copy HALF a full-sized block.
return std::make_pair(l, r);
return std::make_pair(l, r);
}
Block Feistel::FeistelCombine(const Halfblock& l, const Halfblock& r) {
return Block(l.to_string() + r.to_string());
Block b;
memcpy(b.Data(), l.Data(), Halfblock::BLOCK_SIZE);
memcpy(b.Data() + 8, r.Data(), Halfblock::BLOCK_SIZE);
// +8, because 8 is HALF the number of elements in the array. We only want to copy HALF a full-sized block.
return b;
}
Block Feistel::ExpansionFunction(const Halfblock& block) {
std::stringstream ss;
const std::string bits = block.to_string();
Block Feistel::ExpansionFunction(const Halfblock& hb) {
Block b;
std::unordered_map<std::string, std::string> expansionMap;
expansionMap["00"] = "1101";
expansionMap["01"] = "1000";
expansionMap["10"] = "0010";
expansionMap["11"] = "0111";
// Copy the bits over
for (std::size_t i = 0; i < 16; i++) {
b[i] = hb[i];
}
// We have to double the bits!
for (std::size_t i = 0; i < HALFBLOCK_SIZE; i += 2) {
const std::string sub = bits.substr(i, 2);
ss << expansionMap[sub];
}
// Multiply the block a few tims with a bitshifted version
// This is irriversible, too
for (std::size_t i = 0; i < 3; i++) {
b *= b.ShiftBitsRight();
}
return Block(ss.str());
return b;
}
Halfblock Feistel::CompressionFunction(const Block& block) {
std::stringstream ss;
const std::string bits = block.to_string();
Halfblock Feistel::ReductionFunction(const Block& block) {
std::unordered_map<std::string, std::string> compressionMap;
compressionMap["0000"] = "10";
compressionMap["0001"] = "01";
compressionMap["0010"] = "10";
compressionMap["0011"] = "10";
compressionMap["0100"] = "11";
compressionMap["0101"] = "01";
compressionMap["0110"] = "00";
compressionMap["0111"] = "11";
compressionMap["1000"] = "01";
compressionMap["1001"] = "00";
compressionMap["1010"] = "11";
compressionMap["1011"] = "00";
compressionMap["1100"] = "11";
compressionMap["1101"] = "10";
compressionMap["1110"] = "00";
compressionMap["1111"] = "01";
// Just apply a modulo operation, remapping a 32bit integer
// onto 16bit space (default configuration).
// Without saying, modulo is irreversible.
Halfblock hb;
for (std::size_t i = 0; i < 16; i++) {
hb[i] = block[i] % (1 << (Halfblock::CHUNK_SIZE_BITS - 1));
}
// We have to half the bits!
for (std::size_t i = 0; i < BLOCK_SIZE; i += 4) {
const std::string sub = bits.substr(i, 4);
ss << compressionMap[sub];
}
return Halfblock(ss.str());
return hb;
}
void Feistel::SBox(Block& block) {
std::uint8_t* curByte = (std::uint8_t*)(void*)block.Data();
// Iterate over all bytes in the block
for (std::size_t i = 0; i < Block::BLOCK_SIZE; i++) {
curByte++;
// Subsitute byte
*curByte = sboxLookup[*curByte];
}
return;
}
/*
std::string Feistel::SBox(const std::string& in) {
static std::unordered_map<std::string, std::string> subMap;
static bool mapInitialized = false;
if (!mapInitialized) {
subMap["0000"] = "1100";
subMap["0001"] = "1000";
subMap["0010"] = "0001";
subMap["0011"] = "0111";
subMap["0100"] = "1011";
subMap["0101"] = "0011";
subMap["0110"] = "1101";
subMap["0111"] = "1111";
subMap["1000"] = "0000";
subMap["1001"] = "1010";
subMap["1010"] = "0100";
subMap["1011"] = "1001";
subMap["1100"] = "0010";
subMap["1101"] = "1110";
subMap["1110"] = "0101";
subMap["1111"] = "0110";
mapInitialized = true;
}
static std::unordered_map<std::string, std::string> subMap;
static bool mapInitialized = false;
if (!mapInitialized) {
subMap["0000"] = "1100";
subMap["0001"] = "1000";
subMap["0010"] = "0001";
subMap["0011"] = "0111";
subMap["0100"] = "1011";
subMap["0101"] = "0011";
subMap["0110"] = "1101";
subMap["0111"] = "1111";
subMap["1000"] = "0000";
subMap["1001"] = "1010";
subMap["1010"] = "0100";
subMap["1011"] = "1001";
subMap["1100"] = "0010";
subMap["1101"] = "1110";
subMap["1110"] = "0101";
subMap["1111"] = "0110";
mapInitialized = true;
}
return subMap[in];
return subMap[in];
}
*/
void Feistel::GenerateRoundKeys(const Key& seedKey) {
// Clear initial key memory
ZeroKeyMemory();
roundKeys = Keyset();
// Derive all round keys with simple matrix operations
roundKeys[0] = seedKey;
for (std::size_t i = 1; i < roundKeys.size(); i++) {
// Initialize new round key with last round key
const Key& lastKey = roundKeys[i - 1];
roundKeys[i] = lastKey;
// Stir it good
roundKeys[i].ShiftRowsUpInplace();
// Bitshift and matrix-mult 3 times
// (each time jumbles it up pretty good)
// This is irreversible
roundKeys[i].ShiftBitsRightInplace();
roundKeys[i] *= lastKey;
roundKeys[i].ShiftBitsRightInplace();
roundKeys[i] *= lastKey;
roundKeys[i].ShiftBitsRightInplace();
roundKeys[i] *= lastKey;
// Lastly, do apply some cell shifting, and other mutations
roundKeys[i].ShiftCellsRightInplace();
roundKeys[i] += lastKey;
roundKeys[i].ShiftColumnsRightInplace();
roundKeys[i] ^= lastKey;
}
return;
}
void Feistel::GenerateRoundKeys(const Block& seedKey) {
// Clear initial key memory
ZeroKeyMemory();
roundKeys = Keyset();
void Feistel::operator=(const Feistel& other) {
roundKeys = other.roundKeys;
isInitialized = other.isInitialized;
// 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++) {
// 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; // I know this is reversible, but it helps to diffuse future round keys.
roundKeys[i] = FeistelCombine(halfkey1, halfkey2);
}
return;
return;
}
// These pragmas only work for MSVC and g++, as far as i know. Beware!!!
@ -248,11 +262,11 @@ namespace Leonetienne::GCrypt {
#pragma GCC optimize ("O0")
#endif
void Feistel::ZeroKeyMemory() {
for (Block& key : roundKeys) {
key.reset();
}
for (Key& key : roundKeys) {
key.Reset();
}
return;
return;
}
#if defined _WIN32 || defined _WIN64
#pragma optimize("", on )

91
GCryptLib/src/GCipher.cpp Normal file
View File

@ -0,0 +1,91 @@
#include <iostream>
#include <vector>
#include <stdexcept>
#include "GCrypt/GCipher.h"
#include "GCrypt/Util.h"
#include "GCrypt/InitializationVector.h"
namespace Leonetienne::GCrypt {
GCipher::GCipher() {
}
GCipher::GCipher(const Key& key, const DIRECTION direction) :
direction { direction },
lastBlock(InitializationVector(key)), // Initialize our lastBlock with some deterministic initial value, based on the key
feistel(key)
{
isInitialized = true;
return;
}
void GCipher::Initialize(const Key& key, const DIRECTION direction) {
feistel = Feistel(key);
lastBlock = InitializationVector(key);
this->direction = direction;
isInitialized = true;
return;
}
Block GCipher::Digest(const Block& input) {
if (!isInitialized) {
throw std::runtime_error("Attempted to digest data on uninitialized GCipher!");
}
switch (direction) {
case DIRECTION::ENCIPHER: {
// Rename our input to cleartext
const Block& cleartext = input;
// First, xor our cleartext with the last block, and then encipher it
Block ciphertext = feistel.Encipher(cleartext ^ lastBlock);
// Now set our lastBlock to the ciphertext of this block
lastBlock = ciphertext;
// Now return the ciphertext
return ciphertext;
}
case DIRECTION::DECIPHER: {
// Rename our input into ciphertext
const Block& ciphertext = input;
// First, decipher our ciphertext, and then xor it with our last block
Block cleartext = feistel.Decipher(ciphertext) ^ lastBlock;
// Now set our lastBLock to the ciphertext of this block
lastBlock = ciphertext;
// Now return the cleartext
return cleartext;
}
}
throw std::runtime_error("Unreachable branch reached.");
}
void GCipher::SetKey(const Key& key) {
if (!isInitialized) {
throw std::runtime_error("Attempted to set key on uninitialized GCipher!");
}
feistel.SetKey(key);
return;
}
void GCipher::operator=(const GCipher& other) {
direction = other.direction;
feistel = other.feistel;
lastBlock = other.lastBlock;
isInitialized = other.isInitialized;
return;
}
}

View File

@ -1,88 +0,0 @@
#include "GCrypt/GCryptWrapper.h"
#include "GCrypt/Cipher.h"
#include "GCrypt/Util.h"
namespace Leonetienne::GCrypt {
std::string GCryptWrapper::EncryptString(const std::string& cleartext, const std::string& password) {
// Instanciate our cipher and supply a key
const Block key = PasswordToKey(password);
Cipher cipher(key);
// Recode the ascii-string to bits
const Flexblock cleartext_bits = StringToBits(cleartext);
// Encrypt our cleartext bits
const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits);
// Recode the ciphertext bits to a hex-string
const std::string ciphertext = BitsToHexstring(ciphertext_bits);
// Return it
return ciphertext;
}
std::string GCryptWrapper::DecryptString(const std::string& ciphertext, const std::string& password) {
// Instanciate our cipher and supply a key
const Block key = PasswordToKey(password);
Cipher cipher(key);
// Recode the hex-string to bits
const Flexblock ciphertext_bits = HexstringToBits(ciphertext);
// Decrypt the ciphertext bits
const std::string cleartext_bits = cipher.Decipher(ciphertext_bits);
// Recode the cleartext bits to an ascii-string
const std::string cleartext = BitsToString(cleartext_bits);
// Return it
return cleartext;
}
bool GCryptWrapper::EncryptFile(const std::string& filename_in, const std::string& filename_out, const std::string& password, bool printProgressReport) {
try {
// Read the file to bits
const Flexblock cleartext_bits = ReadFileToBits(filename_in);
// Instanciate our cipher and supply a key
const Block key = PasswordToKey(password);
Cipher cipher(key);
// Encrypt our cleartext bits
const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits, printProgressReport);
// Write our ciphertext bits to file
WriteBitsToFile(filename_out, ciphertext_bits);
return true;
}
catch (std::runtime_error&) {
return false;
}
}
bool GCryptWrapper::DecryptFile(const std::string& filename_in, const std::string& filename_out, const std::string& password, bool printProgressReport) {
try {
// Read the file to bits
const Flexblock ciphertext_bits = ReadFileToBits(filename_in);
// Instanciate our cipher and supply a key
const Block key = PasswordToKey(password);
Cipher cipher(key);
// Decrypt the ciphertext bits
const Flexblock cleartext_bits = cipher.Decipher(ciphertext_bits, printProgressReport);
// Write our cleartext bits to file
WriteBitsToFile(filename_out, cleartext_bits);
return true;
}
catch (std::runtime_error&) {
return false;
}
}
}

97
GCryptLib/src/GHash.cpp Normal file
View File

@ -0,0 +1,97 @@
#include "GCrypt/GHash.h"
#include "GCrypt/Util.h"
#include "GCrypt/InitializationVector.h"
#include <vector>
namespace Leonetienne::GCrypt {
GHash::GHash() {
// Initialize our cipher with a static, but randomly distributed key.
Block ivSeed;
ivSeed.FromByteString("3J7IipfQTDJbO8jtasz9PgWui6faPaEMOuVuAqyhB1S2CRcLw5caawewgDUEG1WN");
block = InitializationVector(ivSeed);
Key key;
key.FromByteString("nsoCZfvdqpRkeVTt9wzvPR3TT26peOW9E2kTHh3pdPCq2M7BpskvUljJHSrobUTI");
cipher = GCipher(
// The key really does not matter, as it gets changed
// each time before digesting anything.
key,
GCipher::DIRECTION::ENCIPHER
);
return;
}
void GHash::Digest(const Block& data) {
// Set the cipher key to the current data to be hashed
cipher.SetKey(data);
// Encipher the current block, and matrix-mult it with the current hashsum
block ^= cipher.Digest(data);
return;
}
const Block& GHash::GetHashsum() const {
return block;
}
Block GHash::CalculateHashsum(const std::vector<Block>& data, std::size_t n_bytes) {
// 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;
}
// Create hasher instance
GHash hasher;
// Digest all blocks
for (const Block& block : data) {
hasher.Digest(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;
Block lengthBlock;
lengthBlock.FromTextString(ss.str());
// Digest the length block
hasher.Digest(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;
return;
}
}

116
GCryptLib/src/GPrng.cpp Normal file
View File

@ -0,0 +1,116 @@
#include "GCrypt/GPrng.h"
#include <cassert>
namespace Leonetienne::GCrypt {
GPrng::GPrng(const Block& seed) {
this->seed = seed;
hasher.Digest(seed);
}
GPrng::GPrng() {
}
void GPrng::Seed(const Block& seed) {
hasher = GHash();
this->seed = seed;
hasher.Digest(seed);
return;
}
bool GPrng::GetBit() {
// If we have no more bits to go, create new ones
if (nextBit >= Block::BLOCK_SIZE_BITS) {
AdvanceBlock();
}
// Return the next bit.
return hasher.GetHashsum().GetBit(nextBit++);
}
void GPrng::AdvanceBlock() {
// To prevent an attacker from being able
// to predict block n, by knowing block n-1, and block n-2,
// we will advance the hash function by block n-1 XOR seed.
// This way it is impossible for an attacker to know the
// state of the hash function, unless the seed is known.
// Advance the block (Add the current hashsum XOR seed to the hasher)
hasher.Digest(Block(hasher.GetHashsum() ^ seed));
// Reset the pointer
nextBit = 0;
return;
}
Block GPrng::GetBlock() {
// Tactic on efficiently generating a new block:
// 1) Fetch complete current hashsum (it might have been partially given out already)
// 2) Bitshift it, and matrix-mult it with the seed (that is irreversible)
// That should be a one-way function, and create a new unique block.
// Performance improvement over the previous method:
// (generating 100.000 blocks):
// 12 seconds -> 0.12 seconds
// Fetch our current block
Block hashsum = hasher.GetHashsum();
// Derive/'hash' it to hashsum'
hashsum *= seed;
hashsum.ShiftBitsLeftInplace();
hashsum *= seed;
// Advance the block, so that the following block will be a new block
AdvanceBlock();
// Return our hashsum
return hashsum;
}
std::uint32_t GPrng::operator()() {
// Tactic:
// A block intrinsically consists of 16 32-bit uints.
// We'll just skip all the bits until the next whole integer,
// fetch this complete int, and then advance our pointer
// by 32 bits.
// Performance improvement over the previous method:
// (generating 1.000.000 integers):
// 5.26 seconds -> 3.75 seconds
// Advance our pointer to the next whole uint32
// Do we even have >= 32 bits left in our block?
if (nextBit > Block::BLOCK_SIZE_BITS - Block::CHUNK_SIZE_BITS) {
// No: Create a new block
AdvanceBlock();
}
// We don't have to do this, if our pointer is already at
// the beginning of a whole uint32
if (nextBit % Block::CHUNK_SIZE_BITS != 0) {
nextBit = ((nextBit / Block::CHUNK_SIZE_BITS) + 1) * Block::CHUNK_SIZE_BITS;
}
// Fetch our integer from the block
const std::uint32_t randint =
hasher.GetHashsum().Get(nextBit / Block::CHUNK_SIZE_BITS);
// Advance our pointer
nextBit += Block::CHUNK_SIZE_BITS;
// New state of the pointer:
// It ow may be more now than the size of a block, but that
// gets checked at the begin of a function.
// Not at the end, like here.
// Return our integer
return randint;
}
}

158
GCryptLib/src/GWrapper.cpp Normal file
View File

@ -0,0 +1,158 @@
#include "GCrypt/GWrapper.h"
#include "GCrypt/GCipher.h"
#include "GCrypt/Util.h"
#include <vector>
namespace Leonetienne::GCrypt {
std::string GWrapper::EncryptString(
const std::string& cleartext,
const Key& key)
{
// Recode the ascii-string to bits
const std::vector<Block> cleartext_blocks = StringToBitblocks(cleartext);
// Create cipher instance
GCipher cipher(key, GCipher::DIRECTION::ENCIPHER);
// 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 ss.str();
}
std::string GWrapper::DecryptString(
const std::string& ciphertext,
const Key& key)
{
// 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.");
}
// 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));
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 ss.str();
}
bool GWrapper::EncryptFile(
const std::string& filename_in,
const std::string& filename_out,
const Key& key,
bool printProgressReport)
{
try {
// Read the file to blocks
const std::vector<Block> cleartext_blocks = ReadFileToBlocks(filename_in);
// Encrypt our cleartext blocks
std::vector<Block> ciphertext_blocks;
ciphertext_blocks.reserve(cleartext_blocks.size());
// 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;
}
catch (std::runtime_error&) {
return false;
}
}
bool GWrapper::DecryptFile(
const std::string& filename_in,
const std::string& filename_out,
const Key& key,
bool printProgressReport)
{
try {
// Read the file to blocks
const std::vector<Block> ciphertext_blocks = ReadFileToBlocks(filename_in);
// Decrypt our cleartext blocks
std::vector<Block> cleartext_blocks;
cleartext_blocks.reserve(ciphertext_blocks.size());
// 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;
}
catch (std::runtime_error&) {
return false;
}
}
std::vector<Block> GWrapper::CipherBlocks(
const std::vector<Block>& data,
const Key& key,
const GCipher::DIRECTION direction)
{
// Create cipher instance
GCipher cipher(key, direction);
std::vector<Block> digested;
digested.reserve(data.size());
// Digest all our blocks
for (const Block& block : data) {
Block digestedBlock = cipher.Digest(block);
digested.emplace_back(digestedBlock);
}
// Return it
return digested;
}
}

75
GCryptLib/src/Key.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "GCrypt/Key.h"
#include "GCrypt/GHash.h"
#include "GCrypt/Util.h"
#include <random>
#include <cassert>
#include <sstream>
#include <fstream>
namespace Leonetienne::GCrypt {
Key Key::FromPassword(const std::string& password) {
return GHash::HashString(password);
}
Key Key::Random() {
// Create our truly-random rng
std::random_device rng;
constexpr std::size_t bitsPerCall = sizeof(std::random_device::result_type) * 8;
// Create a new key, and assign 16 random values
Key key;
for (std::size_t i = 0; i < 16; i++) {
key[i] = rng();
}
// Return it
return key;
}
Key Key::LoadFromFile(const std::string& path) {
// Read this many chars
const std::size_t maxChars = Key::BLOCK_SIZE;
// Open ifilestream for keyfile
std::ifstream ifs(path, std::ios::in | std::ios::binary);
// Is the file open now? Or were there any issues...
if (!ifs.good()) {
throw std::runtime_error(std::string("Unable to open ifilestream for keyfile \"") + path + "\"! Aborting...");
}
// Create a new key, and zero it
Key key;
key.Reset();
// Read into it
ifs.read((char*)(void*)key.Data(), Key::BLOCK_SIZE);
// Return it
return key;
}
void Key::WriteToFile(const std::string& path) const {
// Create an ofilestream
std::ofstream ofs(path, std::ios::out | std::ios::binary);
// Write the key
ofs.write((char*)(void*)Data(), Key::BLOCK_SIZE);
// Close the file handle
ofs.close();
return;
}
Key::Key() : Block() {
}
Key::Key(const Block& b) : Block(b) {
}
Key::Key(const Key& k) : Block(k) {
}
}

142
GCryptLib/src/Util.cpp Normal file
View File

@ -0,0 +1,142 @@
#include "GCrypt/Util.h"
#include "GCrypt/GHash.h"
#include <vector>
namespace Leonetienne::GCrypt {
std::string PadStringToLength(const std::string& str, const std::size_t len, const char pad, const bool padLeft) {
// Fast-reject: Already above padded length
if (str.length() >= len) {
return str;
}
std::stringstream ss;
// Pad left:
if (padLeft) {
for (std::size_t i = 0; i < len - str.size(); i++) {
ss << pad;
}
ss << str;
}
// Pad right:
else {
ss << str;
for (std::size_t i = 0; i < len - str.size(); i++) {
ss << pad;
}
}
return ss.str();
}
std::string BitblocksToBytes(const std::vector<Block>& blocks) {
std::stringstream ss;
for (const Block& block : blocks) {
ss << block.ToByteString();
}
return ss.str();
}
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::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
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_block = ifs.gcount();
bytes_read += n_bytes_read_block;
if (n_bytes_read_block > 0) {
// Append the block to our vector
blocks.emplace_back(block);
}
}
// Close the filehandle
ifs.close();
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
){
// 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& str) {
// Create our block vector, and reserve exactly
// how many blocks are required to store this string
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 < str.length(); i += Block::BLOCK_SIZE) {
Block block;
block.FromTextString(str.substr(i, Block::BLOCK_SIZE));
blocks.emplace_back(block);
}
return blocks;
}
}

883
GCryptLib/test/Block.cpp Normal file
View File

@ -0,0 +1,883 @@
#include <GCrypt/Block.h>
#include <GCrypt/Key.h>
#include "Catch2.h"
#include <cstdlib>
#include <time.h>
#include <sstream>
#include <iostream>
using namespace Leonetienne::GCrypt;
// Tests that writing and retrieving data from a block works
TEST_CASE(__FILE__"/Write-read", "[Block]") {
// Setup
Block block;
// Exercise
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
// Verify
for (std::size_t i = 0; i < 16; i++) {
REQUIRE(block.Get(i) == i * 1024);
}
}
// Tests that the copy constructor works
TEST_CASE(__FILE__"/CCtor", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
// Exercise
Block block2(block);
// Verify
for (std::size_t i = 0; i < 16; i++) {
REQUIRE(block2.Get(i) == i * 1024);
}
}
// Tests that operator= works
TEST_CASE(__FILE__"/operator=", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
// Exercise
Block block2;
block2 = block;
// Verify
for (std::size_t i = 0; i < 16; i++) {
REQUIRE(block2.Get(i) == i * 1024);
}
}
// Tests that converting to, and from, binary strings works
TEST_CASE(__FILE__"/BinaryStringConversion", "[Block]") {
// Setup
srand(time(0));
std::stringstream ss;
for (std::size_t i = 0; i < 512; i++) {
ss << (rand()%2 == 0 ? '1' : '0');
}
// Exercise
Block block(ss.str());
// Verify
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 *=
TEST_CASE(__FILE__"/operator*&=", "[Block]") {
// Setup
Block block1;
for (std::size_t i = 0; i < 16; i++) {
block1.Get(i) = i * 1024;
}
Block block2;
for (std::size_t i = 0; i < 16; i++) {
block2.Get(i) = i * 1024 * 2;
}
// Exercise
Block block3 = block1 * block2;
block1 *= block2;
// Verify
REQUIRE(block1 == block3);
}
// Tests that operator^ (xor) works
TEST_CASE(__FILE__"/xor", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
Block xorRH;
for (std::size_t i = 0; i < 16; i++) {
xorRH.Get(i) = i * 5099;
}
// Exercise
Block result = block ^ xorRH;
Block manualResult;
for (std::size_t i = 0; i < 16; i++) {
manualResult.Get(i) = (i * 1024) ^ (i * 5099);
}
// Verify
REQUIRE(result == manualResult);
}
// Tests that operator^ is the same as ^=
TEST_CASE(__FILE__"/operator^&=", "[Block]") {
// Setup
Block block1;
for (std::size_t i = 0; i < 16; i++) {
block1.Get(i) = i * 1024;
}
Block block2;
for (std::size_t i = 0; i < 16; i++) {
block2.Get(i) = i * 5099 * 2;
}
// Exercise
Block block3 = block1 ^ block2;
block1 ^= block2;
// Verify
REQUIRE(block1 == block3);
}
// Tests that operator+ (add) works
TEST_CASE(__FILE__"/add", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
Block addRH;
for (std::size_t i = 0; i < 16; i++) {
addRH.Get(i) = i * 5099;
}
// Exercise
Block result = block + addRH;
Block manualResult;
for (std::size_t i = 0; i < 16; i++) {
manualResult.Get(i) = (i * 1024) + (i * 5099);
}
// Verify
REQUIRE(result == manualResult);
}
// Tests that operator+ is the same as +=
TEST_CASE(__FILE__"/operator+&=", "[Block]") {
// Setup
Block block1;
for (std::size_t i = 0; i < 16; i++) {
block1.Get(i) = i * 1024;
}
Block block2;
for (std::size_t i = 0; i < 16; i++) {
block2.Get(i) = i * 5099 * 2;
}
// Exercise
Block block3 = block1 + block2;
block1 += block2;
// Verify
REQUIRE(block1 == block3);
}
// Tests that operator- (subtract) works
TEST_CASE(__FILE__"/subtract", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
Block subRH;
for (std::size_t i = 0; i < 16; i++) {
subRH.Get(i) = i * 5099;
}
// Exercise
Block result = block - subRH;
Block manualResult;
for (std::size_t i = 0; i < 16; i++) {
manualResult.Get(i) = (i * 1024) - (i * 5099);
}
// Verify
REQUIRE(result == manualResult);
}
// Tests that operator- is the same as -=
TEST_CASE(__FILE__"/operator-&=", "[Block]") {
// Setup
Block block1;
for (std::size_t i = 0; i < 16; i++) {
block1.Get(i) = i * 1024;
}
Block block2;
for (std::size_t i = 0; i < 16; i++) {
block2.Get(i) = i * 5099 * 2;
}
// Exercise
Block block3 = block1 - block2;
block1 -= block2;
// Verify
REQUIRE(block1 == block3);
}
// Tests that subtraction undoes addition, and vica versa
TEST_CASE(__FILE__"/subtraction-undoes-addition", "[Block]") {
// Setup
const Block a = Key::FromPassword("Halleluja");
const Block b = Key::FromPassword("Ananas");
// Exercise
const Block a_plus_b = a + b;
const Block a_plus_b_minus_b = a_plus_b - b;
// Verify
REQUIRE(a == a_plus_b_minus_b);
}
// Tests that operator== works correctly
TEST_CASE(__FILE__"/operator==", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
SECTION("Expected true") {
Block sameBlock;
for (std::size_t i = 0; i < 16; i++) {
sameBlock.Get(i) = i * 1024;
}
REQUIRE(block == sameBlock);
}
SECTION("Expected false") {
Block otherBlock;
for (std::size_t i = 0; i < 16; i++) {
otherBlock.Get(i) = i * 1024 + 1;
}
REQUIRE_FALSE(block == otherBlock);
}
}
// Tests that operator!= works correctly
TEST_CASE(__FILE__"/operator!=", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i * 1024;
}
SECTION("Expected false") {
Block sameBlock;
for (std::size_t i = 0; i < 16; i++) {
sameBlock.Get(i) = i * 1024;
}
REQUIRE_FALSE(block != sameBlock);
}
SECTION("Expected true") {
Block otherBlock;
for (std::size_t i = 0; i < 16; i++) {
otherBlock.Get(i) = i * 1024 + 1;
}
REQUIRE(block != otherBlock);
}
}
// Tests that getting the data via the matrix accessor works
TEST_CASE(__FILE__"/matrix-accessor", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i;
}
// Exercise
REQUIRE(block.Get(0,0) == 0);
REQUIRE(block.Get(1,0) == 1);
REQUIRE(block.Get(2,0) == 2);
REQUIRE(block.Get(3,0) == 3);
REQUIRE(block.Get(0,1) == 4);
REQUIRE(block.Get(1,1) == 5);
REQUIRE(block.Get(2,1) == 6);
REQUIRE(block.Get(3,1) == 7);
REQUIRE(block.Get(0,2) == 8);
REQUIRE(block.Get(1,2) == 9);
REQUIRE(block.Get(2,2) == 10);
REQUIRE(block.Get(3,2) == 11);
REQUIRE(block.Get(0,3) == 12);
REQUIRE(block.Get(1,3) == 13);
REQUIRE(block.Get(2,3) == 14);
REQUIRE(block.Get(3,3) == 15);
}
// Tests that the reset method works
TEST_CASE(__FILE__"/reset", "[Block]") {
// Setup
Block block;
for (std::size_t i = 0; i < 16; i++) {
block.Get(i) = i + 33;
}
// Exercise
block.Reset();
// Verify
for (std::size_t i = 0; i < 16; i++) {
REQUIRE(block[i] == 0);
}
}
// Tests that shift rows up works
TEST_CASE(__FILE__"/shift-rows-up", "[Block]") {
// Setup
Block a;
a.Get(0,0) = 10; a.Get(0,1) = 11; a.Get(0,2) = 12; a.Get(0,3) = 13;
a.Get(1,0) = 20; a.Get(1,1) = 21; a.Get(1,2) = 22; a.Get(1,3) = 23;
a.Get(2,0) = 30; a.Get(2,1) = 31; a.Get(2,2) = 32; a.Get(2,3) = 33;
a.Get(3,0) = 40; a.Get(3,1) = 41; a.Get(3,2) = 42; a.Get(3,3) = 43;
Block e; /* expected */
e.Get(0,0) = 20; e.Get(0,1) = 21; e.Get(0,2) = 22; e.Get(0,3) = 23;
e.Get(1,0) = 30; e.Get(1,1) = 31; e.Get(1,2) = 32; e.Get(1,3) = 33;
e.Get(2,0) = 40; e.Get(2,1) = 41; e.Get(2,2) = 42; e.Get(2,3) = 43;
e.Get(3,0) = 10; e.Get(3,1) = 11; e.Get(3,2) = 12; e.Get(3,3) = 13;
// Exercise
a.ShiftRowsUpInplace();
// Verify
REQUIRE(a == e);
}
// Tests that ShiftRowsUpInplace() does the exact same thing as ShiftRowsUp()
TEST_CASE(__FILE__"/shift-rows-up-same-as-inplace", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise and verify
a.ShiftRowsUpInplace();
REQUIRE(a == initial_a.ShiftRowsUp());
}
// Tests that shift rows down works
TEST_CASE(__FILE__"/shift-rows-down", "[Block]") {
// Setup
Block a;
a.Get(0,0) = 10; a.Get(0,1) = 11; a.Get(0,2) = 12; a.Get(0,3) = 13;
a.Get(1,0) = 20; a.Get(1,1) = 21; a.Get(1,2) = 22; a.Get(1,3) = 23;
a.Get(2,0) = 30; a.Get(2,1) = 31; a.Get(2,2) = 32; a.Get(2,3) = 33;
a.Get(3,0) = 40; a.Get(3,1) = 41; a.Get(3,2) = 42; a.Get(3,3) = 43;
Block e; /* expected */
e.Get(0,0) = 40; e.Get(0,1) = 41; e.Get(0,2) = 42; e.Get(0,3) = 43;
e.Get(1,0) = 10; e.Get(1,1) = 11; e.Get(1,2) = 12; e.Get(1,3) = 13;
e.Get(2,0) = 20; e.Get(2,1) = 21; e.Get(2,2) = 22; e.Get(2,3) = 23;
e.Get(3,0) = 30; e.Get(3,1) = 31; e.Get(3,2) = 32; e.Get(3,3) = 33;
// Exercise
a.ShiftRowsDownInplace();
// Verify
REQUIRE(a == e);
}
// Tests that ShiftRowsDownInplace() does the exact same thing as ShiftRowsDown()
TEST_CASE(__FILE__"/shift-rows-down-same-as-inplace", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise and verify
a.ShiftRowsDownInplace();
REQUIRE(a == initial_a.ShiftRowsDown());
}
// Tests that shift columns left works
TEST_CASE(__FILE__"/shift-columns-left", "[Block]") {
// Setup
Block a;
a.Get(0,0) = 10; a.Get(0,1) = 11; a.Get(0,2) = 12; a.Get(0,3) = 13;
a.Get(1,0) = 20; a.Get(1,1) = 21; a.Get(1,2) = 22; a.Get(1,3) = 23;
a.Get(2,0) = 30; a.Get(2,1) = 31; a.Get(2,2) = 32; a.Get(2,3) = 33;
a.Get(3,0) = 40; a.Get(3,1) = 41; a.Get(3,2) = 42; a.Get(3,3) = 43;
Block e; /* expected */
e.Get(0,0) = 11; e.Get(0,1) = 12; e.Get(0,2) = 13; e.Get(0,3) = 10;
e.Get(1,0) = 21; e.Get(1,1) = 22; e.Get(1,2) = 23; e.Get(1,3) = 20;
e.Get(2,0) = 31; e.Get(2,1) = 32; e.Get(2,2) = 33; e.Get(2,3) = 30;
e.Get(3,0) = 41; e.Get(3,1) = 42; e.Get(3,2) = 43; e.Get(3,3) = 40;
// Exercise
a.ShiftColumnsLeftInplace();
// Verify
REQUIRE(a == e);
}
// Tests that ShiftColumnsLeftInplace()() does the exact same thing as ShiftColumnsLeft()
TEST_CASE(__FILE__"/shift-columns-left-same-as-inplace", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise and verify
a.ShiftColumnsLeftInplace();
REQUIRE(a == initial_a.ShiftColumnsLeft());
}
// Tests that shift columns right works
TEST_CASE(__FILE__"/shift-columns-right", "[Block]") {
// Setup
Block a;
a.Get(0,0) = 10; a.Get(0,1) = 11; a.Get(0,2) = 12; a.Get(0,3) = 13;
a.Get(1,0) = 20; a.Get(1,1) = 21; a.Get(1,2) = 22; a.Get(1,3) = 23;
a.Get(2,0) = 30; a.Get(2,1) = 31; a.Get(2,2) = 32; a.Get(2,3) = 33;
a.Get(3,0) = 40; a.Get(3,1) = 41; a.Get(3,2) = 42; a.Get(3,3) = 43;
Block e; /* expected */
e.Get(0,0) = 13; e.Get(0,1) = 10; e.Get(0,2) = 11; e.Get(0,3) = 12;
e.Get(1,0) = 23; e.Get(1,1) = 20; e.Get(1,2) = 21; e.Get(1,3) = 22;
e.Get(2,0) = 33; e.Get(2,1) = 30; e.Get(2,2) = 31; e.Get(2,3) = 32;
e.Get(3,0) = 43; e.Get(3,1) = 40; e.Get(3,2) = 41; e.Get(3,3) = 42;
// Exercise
a.ShiftColumnsRightInplace();
// Verify
REQUIRE(a == e);
}
// Tests that ShiftColumnsRightInplace()() does the exact same thing as ShiftColumnsRight()
TEST_CASE(__FILE__"/shift-columns-right-same-as-inplace", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise and verify
a.ShiftColumnsRightInplace();
REQUIRE(a == initial_a.ShiftColumnsRight());
}
// Tests that shift cells left works
TEST_CASE(__FILE__"/shift-cells-left", "[Block]") {
// Setup
Block a;
for (std::size_t i = 0; i < 16; i++) {
a.Get(i) = i;
}
Block expected;
for (std::size_t i = 0; i < 15; i++) {
expected.Get(i) = i+1;
}
expected.Get(15) = 0;
// Exercise
a.ShiftCellsLeftInplace();
// Verify
REQUIRE(a == expected);
}
// Tests that ShiftCellsLeftInplace()() does the exact same thing as ShiftCellsLeft()
TEST_CASE(__FILE__"/shift-cells-left-same-as-inplace", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise and verify
a.ShiftCellsLeftInplace();
REQUIRE(a == initial_a.ShiftCellsLeft());
}
// Tests that shift cells right works
TEST_CASE(__FILE__"/shift-cells-right", "[Block]") {
// Setup
Block a;
for (std::size_t i = 0; i < 16; i++) {
a.Get(i) = i;
}
Block expected;
for (std::size_t i = 1; i < 16; i++) {
expected.Get(i) = i-1;
}
expected.Get(0) = 15;
// Exercise
a.ShiftCellsRightInplace();
// Verify
REQUIRE(a == expected);
}
// Tests that ShiftCellsRightInplace()() does the exact same thing as ShiftCellsRight()
TEST_CASE(__FILE__"/shift-cells-right-same-as-inplace", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise and verify
a.ShiftCellsRightInplace();
REQUIRE(a == initial_a.ShiftCellsRight());
}
// Tests that shifting down undoes shifting up, and vica versa
TEST_CASE(__FILE__"/shift-down-undoes-shift-up", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise
a.ShiftRowsUpInplace();
a.ShiftRowsDownInplace();
// Verify
REQUIRE(a == initial_a);
}
// Tests that shifting left undoes shifting right, and vica versa
TEST_CASE(__FILE__"/shift-left-undoes-shift-right", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise
a.ShiftColumnsRightInplace();
a.ShiftColumnsLeftInplace();
// Verify
REQUIRE(a == initial_a);
}
// Tests that shifting cells left undoes shifting cells right, and vica versa
TEST_CASE(__FILE__"/cellshift-left-undoes-cellshift-right", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise
a.ShiftCellsRightInplace();
a.ShiftCellsLeftInplace();
// Verify
REQUIRE(a == initial_a);
}
// Tests that multiple, combined shifts and additions can be undone
TEST_CASE(__FILE__"/multiple-combined-shifts-and-additions-can-be-undone", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
Block key = Key::FromPassword("Papaya");
const Block initial_a = a;
// Exercise (mix-up)
for (std::size_t i = 0; i < 64; i++) {
a.ShiftRowsUpInplace();
a.ShiftColumnsLeftInplace();
a += key;
a.ShiftCellsRightInplace();
}
// Exercise (un-mix)
for (std::size_t i = 0; i < 64; i++) {
a.ShiftCellsLeftInplace();
a -= key;
a.ShiftColumnsRightInplace();
a.ShiftRowsDownInplace();
}
// Verify
REQUIRE(a == initial_a);
}
// Tests that the get-bit method works
TEST_CASE(__FILE__"/get-bit", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
// Exercise
std::stringstream ss;
for (std::size_t i = 0; i < 512; i++) {
ss << a.GetBit(i);
}
// Verify
REQUIRE(ss.str() == a.ToBinaryString());
}
// Tests that the set-bit to-false method works
TEST_CASE(__FILE__"/set-bit-to-false", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
// Exercise
a.SetBit(5, 0);
a.SetBit(15, 0);
a.SetBit(105, 0);
a.SetBit(205, 0);
// Verify
REQUIRE(a.GetBit(5) == false);
REQUIRE(a.GetBit(15) == false);
REQUIRE(a.GetBit(105) == false);
REQUIRE(a.GetBit(205) == false);
}
// Tests that the set-bit to-true method works
TEST_CASE(__FILE__"/set-bit-to-true", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
// Exercise
a.SetBit(5, 1);
a.SetBit(15, 1);
a.SetBit(105, 1);
a.SetBit(205, 1);
// Verify
REQUIRE(a.GetBit(5) == true);
REQUIRE(a.GetBit(15) == true);
REQUIRE(a.GetBit(105) == true);
REQUIRE(a.GetBit(205) == true);
}
// Tests that the flip-bit method works
TEST_CASE(__FILE__"/flip-bit", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
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';
compare[205] = compare[205] == '1' ? '0' : '1';
// Exercise
a.FlipBit(5);
a.FlipBit(15);
a.FlipBit(105);
a.FlipBit(205);
// Verify
REQUIRE(a.ToBinaryString() == compare);
}
// Tests that bitshifts (to the left) work
TEST_CASE(__FILE__"/bitshift-left", "[Block]") {
// Setup
srand(time(0));
std::stringstream ss;
for (std::size_t i = 0; i < 512; i++) {
ss << (rand()%2 == 0 ? '1' : '0');
}
const std::string originalBits = ss.str();
ss.str("");
// Shift string manually
std::string shiftedBits = originalBits;
shiftedBits.erase(0, 1);
ss << shiftedBits << originalBits[0];
shiftedBits = ss.str();
// Create block of original bits
Block block(originalBits);
// Exercise
block = block.ShiftBitsLeft();
// Verify
REQUIRE(block.ToBinaryString().length() == shiftedBits.length());
REQUIRE(block.ToBinaryString() == shiftedBits);
}
// Tests that inplace-bitshifts to the left do the exact same as copy bitshifts
TEST_CASE(__FILE__"/bitshift-left-inplace", "[Block]") {
// Setup
srand(time(0));
std::stringstream ss;
for (std::size_t i = 0; i < 512; i++) {
ss << (rand()%2 == 0 ? '1' : '0');
}
Block a(ss.str());
// Exercise
Block b = a.ShiftBitsLeft();
a.ShiftBitsLeftInplace();
// Verify
REQUIRE(a == b);
}
// Tests that bitshifts (to the right) work
TEST_CASE(__FILE__"/bitshift-right", "[Block]") {
// Setup
srand(time(0));
std::stringstream ss;
for (std::size_t i = 0; i < 512; i++) {
ss << (rand()%2 == 0 ? '1' : '0');
}
const std::string originalBits = ss.str();
ss.str("");
// Shift string manually
std::string shiftedBits = originalBits;
shiftedBits.erase(shiftedBits.length() - 1);
ss << originalBits[originalBits.length()-1] << shiftedBits;
shiftedBits = ss.str();
// Create block of original bits
Block block(originalBits);
// Exercise
block = block.ShiftBitsRight();
// Verify
REQUIRE(block.ToBinaryString().length() == shiftedBits.length());
REQUIRE(block.ToBinaryString() == shiftedBits);
}
// Tests that inplace-bitshifts to the right do the exact same as copy bitshifts
TEST_CASE(__FILE__"/bitshift-right-inplace", "[Block]") {
// Setup
srand(time(0));
std::stringstream ss;
for (std::size_t i = 0; i < 512; i++) {
ss << (rand()%2 == 0 ? '1' : '0');
}
Block a(ss.str());
// Exercise
Block b = a.ShiftBitsRight();
a.ShiftBitsRightInplace();
// Verify
REQUIRE(a == b);
}
// Tests that bitshifting undoes itself
TEST_CASE(__FILE__"/bitshifting-undoes-itself", "[Block]") {
// Setup
Block a = Key::FromPassword("Halleluja");
const Block initial_a = a;
// Exercise (mix-up)
for (std::size_t i = 0; i < 100; i++) {
a.ShiftBitsLeftInplace();
}
// Exercise (un-mix)
for (std::size_t i = 0; i < 100; i++) {
a.ShiftBitsRightInplace();
}
// Verify
REQUIRE(a == initial_a);
}

View File

@ -1,4 +1,4 @@
#include <GCrypt/Cipher.h>
#include <GCrypt/GWrapper.h>
#include <GCrypt/Util.h>
#include "Catch2.h"
@ -7,14 +7,13 @@ using namespace Leonetienne::GCrypt;
// THESE TESTS ASSUME BLOCK_SIZE == 512
// Tests that encrypting a message of exactly BLOCK_SIZE yields the exact message back
TEST_CASE(__FILE__"/SingleBlock_NoPadding", "[Encryption/Decryption consistency]") {
TEST_CASE(__FILE__"/SingleBlock", "[Encryption/Decryption consistency]") {
// Instanciate our cipher and supply a key
const Block key = PasswordToKey("1234");
const Cipher cipher(key);
const Key key = Key::FromPassword("1234");
// Recode the ascii-string to bits
const Flexblock cleartext_bits =
const std::string cleartext_bits =
"1011110011010110000010110001111000111010111101001010100100011101"
"0101110101010010100000110100001000011000111010001001110101111111"
"1110110101100101110001010101011110001010000010111110011011010111"
@ -24,199 +23,65 @@ TEST_CASE(__FILE__"/SingleBlock_NoPadding", "[Encryption/Decryption consistency]
"1101100100000100010000001011100010010001101111100100101100010001"
"0000011110010110111010110110111110011110011010001100100111110101";
std::vector<Block> cleartext_blocks({Block(cleartext_bits)});
// Encrypt our cleartext bits
const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits);
const std::vector<Block> ciphertext_blocks = GWrapper::CipherBlocks(cleartext_blocks, key, GCipher::DIRECTION::ENCIPHER);
// Decipher it again
const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits);
const std::vector<Block> decrypted_blocks = GWrapper::CipherBlocks(ciphertext_blocks, key, GCipher::DIRECTION::DECIPHER);
// Assert that the decrypted text equals the plaintext
REQUIRE(
cleartext_bits ==
decryptedBits
cleartext_blocks ==
decrypted_blocks
);
}
// Tests that encrypting a message of less than BLOCK_SIZE yields the exact message plus zero-padding back
TEST_CASE(__FILE__"/SingleBlock_Padding", "[Encryption/Decryption consistency]") {
// Instanciate our cipher and supply a key
const Block key = PasswordToKey("1234");
const Cipher cipher(key);
// Recode the ascii-string to bits
const Flexblock cleartext_bits =
"1011110011010110000010110001111000111010111101001010100100011101"
"0101110101010010100000110100001000011000111010001001110101111111"
"1110110101100101110001010101011110001010000010111110011011010111"
"1100110100111000000011100101010100110010001110010011000010111001"
"0000010000010000011001111010011110111001000000000110101000110001"
"0110111110110110100000010100000011010001000011100100111001001011"
"1101100100000100";
const Flexblock cleartext_bits_EXPECTED_RESULT =
"1011110011010110000010110001111000111010111101001010100100011101"
"0101110101010010100000110100001000011000111010001001110101111111"
"1110110101100101110001010101011110001010000010111110011011010111"
"1100110100111000000011100101010100110010001110010011000010111001"
"0000010000010000011001111010011110111001000000000110101000110001"
"0110111110110110100000010100000011010001000011100100111001001011"
"1101100100000100000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000";
// Encrypt our cleartext bits
const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits);
// Decipher it again
const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits);
// Assert that the decrypted text equals the plaintext
REQUIRE(
cleartext_bits_EXPECTED_RESULT ==
decryptedBits
);
}
// Tests that a decrypted ciphertext equals its plaintrext version, using a cleartext that requires A LOT of blocks
// Tests that a decrypted ciphertext equals its plaintext version, using a cleartext that requires A LOT of blocks
TEST_CASE(__FILE__"MultiBlock_NoPadding/", "[Encryption/Decryption consistency]") {
// Instanciate our cipher and supply a key
const Block key = PasswordToKey("1234");
const Cipher cipher(key);
const Key key = Key::FromPassword("1234");
// Recode the ascii-string to bits
const Flexblock cleartext_bits =
"1011110011010110000010110001111000111010111101001010100100011101"
"0101110101010010100000110100001000011000111010001001110101111111"
"1110110101100101110001010101011110001010000010111110011011010111"
"1100110100111000000011100101010100110010001110010011000010111001"
"0000010000010000011001111010011110111001000000000110101000110001"
"0110111110110110100000010100000011010001000011100100111001001011"
"1101100100000100010000001011100010010001101111100100101100010001"
"0000011110010110111010110110111110011110011010001100100111110101"
"1000010010000000000100101011110001000101101101100000010011111011"
"1011111010110100100111100111110011100001111101111110000110001100"
"0001000111000111101110000111011011101010100010100101100111010100"
"0101111110110010110000111111011001101110101101100100100011000100"
"1000110010101001000100001001101000011111101011111100100000100101"
"1100001100111001011111001101000111011101011101000110010110110110"
"0111001010011010010000010110000110010101101100101110111100100011"
"0010111110011100010100000101100101110101101011110100100000110110"
"1001101110101001001111111000010100011100100000101000111101101111"
"0101111011110001101010111010001000111010101111001101100100100100"
"1110110111001100011010110000101000011001011100101100111101110000"
"1010101111011110000111011011011110000111010110110111111010101010"
"0111100101111001010111101000001010100000111010111100111011111001"
"0110111000000110100011011100101101010101101000010010011111100100"
"0010111000001011101110000110010011101001111010100111110111110101"
"1110111000000000101011000100101010000110110111101010011001111010"
"1101011110001110000011010111001100001100101000000101000101000010"
"0101000011011111010010110010000010101100001110011000110111110111"
"1110010101011110111001100010110101101011100111100011101010001011"
"0101110010100110101100111100010000111101111100000111000110110110"
"1001100111000000011010100000011101011000010010011010001011110000"
"1100100111111001001000011100110000011110001100000000010000001001"
"1110000000110010000010011010100011011011000000000111110000110111"
"0101110011001101010110010100011001110110000110010001100110011111";
std::stringstream ss;
const std::string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Read 10 blocks worth of characters
srand(time(0));
for (std::size_t i = 0; i < 512*10; i++) {
ss << charset[rand() % charset.length()];
}
const std::string cleartext_str = ss.str();
std::vector<Block> cleartext_blocks = StringToBitblocks(cleartext_str);
// Encrypt our cleartext bits
const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits);
std::vector<Block> ciphertext_blocks;
ciphertext_blocks.reserve(cleartext_blocks.size());
{
GCipher cipher(key, GCipher::DIRECTION::ENCIPHER);
for (const Block& clearBlock : cleartext_blocks) {
ciphertext_blocks.emplace_back(cipher.Digest(clearBlock));
}
}
// Decipher it again
const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits);
std::vector<Block> deciphered_blocks;
deciphered_blocks.reserve(ciphertext_blocks.size());
{
GCipher cipher(key, GCipher::DIRECTION::DECIPHER);
for (const Block& ciphBlock : ciphertext_blocks) {
deciphered_blocks.emplace_back(cipher.Digest(ciphBlock));
}
}
// Assert that the decrypted text equals the plaintext
REQUIRE(
cleartext_bits ==
decryptedBits
);
}
// Tests that a decrypted ciphertext equals its plaintrext version, using a cleartext that requires A LOT of blocks
TEST_CASE(__FILE__"MultiBlock_Padding/", "[Encryption/Decryption consistency]") {
// Instanciate our cipher and supply a key
const Block key = PasswordToKey("1234");
const Cipher cipher(key);
// Recode the ascii-string to bits
const Flexblock cleartext_bits =
"1011110011010110000010110001111000111010111101001010100100011101"
"0101110101010010100000110100001000011000111010001001110101111111"
"1110110101100101110001010101011110001010000010111110011011010111"
"1100110100111000000011100101010100110010001110010011000010111001"
"0000010000010000011001111010011110111001000000000110101000110001"
"0110111110110110100000010100000011010001000011100100111001001011"
"1101100100000100010000001011100010010001101111100100101100010001"
"0000011110010110111010110110111110011110011010001100100111110101"
"1000010010000000000100101011110001000101101101100000010011111011"
"1011111010110100100111100111110011100001111101111110000110001100"
"0001000111000111101110000111011011101010100010100101100111010100"
"0101111110110010110000111111011001101110101101100100100011000100"
"1000110010101001000100001001101000011111101011111100100000100101"
"1100001100111001011111001101000111011101011101000110010110110110"
"0111001010011010010000010110000110010101101100101110111100100011"
"0010111110011100010100000101100101110101101011110100100000110110"
"1001101110101001001111111000010100011100100000101000111101101111"
"0101111011110001101010111010001000111010101111001101100100100100"
"1110110111001100011010110000101000011001011100101100111101110000"
"1010101111011110000111011011011110000111010110110111111010101010"
"0111100101111001010111101000001010100000111010111100111011111001"
"0110111000000110100011011100101101010101101000010010011111100100"
"0010111000001011101110000110010011101001111010100111110111110101"
"1110111000000000101011000100101010000110110111101010011001111010"
"1101011110001110000011010111001100001100101000000101000101000010"
"0101000011011111010010110010000010101100001110011000110111110111"
"1110010101011110111001100010110101101011100111100011101010001011"
"0101110010100110101100111100010000111101111100000111000110110110"
"1001100111000000011010100000011101011000010010011010001011110000"
"1100100111111001001000011100110000011110001100000000010000001001"
"11100000001100100000100110101000110110110000000001111100001";
const Flexblock cleartext_bits_EXPECTED_RESULT =
"1011110011010110000010110001111000111010111101001010100100011101"
"0101110101010010100000110100001000011000111010001001110101111111"
"1110110101100101110001010101011110001010000010111110011011010111"
"1100110100111000000011100101010100110010001110010011000010111001"
"0000010000010000011001111010011110111001000000000110101000110001"
"0110111110110110100000010100000011010001000011100100111001001011"
"1101100100000100010000001011100010010001101111100100101100010001"
"0000011110010110111010110110111110011110011010001100100111110101"
"1000010010000000000100101011110001000101101101100000010011111011"
"1011111010110100100111100111110011100001111101111110000110001100"
"0001000111000111101110000111011011101010100010100101100111010100"
"0101111110110010110000111111011001101110101101100100100011000100"
"1000110010101001000100001001101000011111101011111100100000100101"
"1100001100111001011111001101000111011101011101000110010110110110"
"0111001010011010010000010110000110010101101100101110111100100011"
"0010111110011100010100000101100101110101101011110100100000110110"
"1001101110101001001111111000010100011100100000101000111101101111"
"0101111011110001101010111010001000111010101111001101100100100100"
"1110110111001100011010110000101000011001011100101100111101110000"
"1010101111011110000111011011011110000111010110110111111010101010"
"0111100101111001010111101000001010100000111010111100111011111001"
"0110111000000110100011011100101101010101101000010010011111100100"
"0010111000001011101110000110010011101001111010100111110111110101"
"1110111000000000101011000100101010000110110111101010011001111010"
"1101011110001110000011010111001100001100101000000101000101000010"
"0101000011011111010010110010000010101100001110011000110111110111"
"1110010101011110111001100010110101101011100111100011101010001011"
"0101110010100110101100111100010000111101111100000111000110110110"
"1001100111000000011010100000011101011000010010011010001011110000"
"1100100111111001001000011100110000011110001100000000010000001001"
"1110000000110010000010011010100011011011000000000111110000100000"
"0000000000000000000000000000000000000000000000000000000000000000";
// Encrypt our cleartext bits
const Flexblock ciphertext_bits = cipher.Encipher(cleartext_bits);
// Decipher it again
const Flexblock decryptedBits = cipher.Decipher(ciphertext_bits);
// Assert that the decrypted text equals the plaintext
REQUIRE(
cleartext_bits_EXPECTED_RESULT ==
decryptedBits
cleartext_blocks ==
deciphered_blocks
);
}

View File

@ -1,58 +1,86 @@
#include <GCrypt/GCryptWrapper.h>
#include <GCrypt/Flexblock.h>
#include <GCrypt/GWrapper.h>
#include <GCrypt/Util.h>
#include "Catch2.h"
using namespace Leonetienne::GCrypt;
// Tests that encrypting and decrypting strings using the wrapper works.
// Tests that encrypting and decrypting short strings using the wrapper works.
// This test will start from scratch after encryption, to ensure EVERYTHING has to be re-calculated.
TEST_CASE(__FILE__"/Encrypting and decrypting strings works", "[Wrapper]") {
TEST_CASE(__FILE__"/Encrypting and decrypting strings works, Single block", "[Wrapper]") {
// Setup
const std::string plaintext = "Hello, World!";
const std::string password = "Der Affe will Zucker";
// Setup
const std::string plaintext = "Hello, World!";
const Key key = Key::FromPassword("Der Affe will Zucker");
std::string ciphertext;
std::string decrypted;
std::string ciphertext;
std::string decrypted;
// Encryption
ciphertext = GCryptWrapper::EncryptString(plaintext, password);
// Encryption
ciphertext = GWrapper::EncryptString(plaintext, key);
// Decryption
decrypted = GCryptWrapper::DecryptString(ciphertext, password);
// Decryption
decrypted = GWrapper::DecryptString(ciphertext, key);
// Assertion
REQUIRE(plaintext == decrypted);
// Assertion
REQUIRE(plaintext == decrypted);
}
// Tests that encrypting and decrypting very long strings using the wrapper works.
// This test will start from scratch after encryption, to ensure EVERYTHING has to be re-calculated.
TEST_CASE(__FILE__"/Encrypting and decrypting strings works, Many blocks block", "[Wrapper]") {
// Setup
// Read an not-multiple-of-blocksize amount of random chars, that's very large (about 200kb long string)
srand(time(0));
std::stringstream ss;
const std::string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (std::size_t i = 0; i < 198273; i++) {
ss << charset[rand() % charset.length()];
}
const std::string plaintext = ss.str();
const Key key = Key::FromPassword("Der Affe will Zucker");
std::string ciphertext;
std::string decrypted;
// Encryption
ciphertext = GWrapper::EncryptString(plaintext, key);
// Decryption
decrypted = GWrapper::DecryptString(ciphertext, key);
// Assertion
REQUIRE(plaintext == decrypted);
}
// Tests that encrypting and decrypting files using the wrapper works.
// This test will start from scratch after encryption, to ensure EVERYTHING has to be re-calculated.
TEST_CASE(__FILE__"/Encrypting and decrypting files works", "[Wrapper]") {
// Setup
const std::string testfile_dir = "testAssets/";
// Setup
const std::string testfile_dir = "testAssets/";
const std::string filename_plain = testfile_dir + "testfile.png";
const std::string filename_encrypted = testfile_dir + "testfile.png.crypt";
const std::string filename_decrypted = testfile_dir + "testfile.png.clear.png";
const std::string password = "Der Affe will Zucker";
const std::string filename_plain = testfile_dir + "testfile.png";
const std::string filename_encrypted = testfile_dir + "testfile.png.crypt";
const std::string filename_decrypted = testfile_dir + "testfile.png.clear.png";
const Key key = Key::FromPassword("Der Affe will Zucker");
// Encryption
GCryptWrapper::EncryptFile(filename_plain, filename_encrypted, password);
// Encryption
GWrapper::EncryptFile(filename_plain, filename_encrypted, key);
// Decryption
GCryptWrapper::DecryptFile(filename_encrypted, filename_decrypted, password);
// Decryption
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);
// Read in both the base, and the decrypted file
const std::vector<Block> plainfile = ReadFileToBlocks(filename_plain);
const std::vector<Block> 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
);
// Assertion (If this fails, maybe check if the image is even readable by an image viewer)
REQUIRE(plainfile.size() == decryptfile.size());
REQUIRE(plainfile == decryptfile);
}

View File

@ -1,4 +1,5 @@
#include <GCrypt/Util.h>
#include <GCrypt/Block.h>
#include <GCrypt/Config.h>
#include <unordered_map>
#include <sstream>
@ -55,39 +56,39 @@ inline std::string Base10_2_X(const unsigned long long int i, const std::string
// Already validated range: Password 0 - 1.000.000
TEST_CASE(__FILE__"/Password to key transformation collision resistance", "[Key extrapolation]") {
// To test resistence set this to a high number around a million.
// This will take a LONG while to execute though (about 2.5hrs on my machine), hence why it's set so low.
constexpr std::size_t NUM_RUN_TESTS = 1000;
// To test resistence set this to a high number around a million.
// This will take a LONG while to execute though (about 2.5hrs on my machine), hence why it's set so low.
constexpr std::size_t NUM_RUN_TESTS = 10;
std::unordered_map<std::bitset<BLOCK_SIZE>, std::string> keys; // <key, password>
std::unordered_map<std::string, std::string> keys; // <key, password>
// Try NUM_RUN_TESTS passwords
const std::string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Try NUM_RUN_TESTS passwords
const std::string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (std::size_t i = 0; i < NUM_RUN_TESTS; i++) {
// Get password
const std::string password = Base10_2_X(i, charset, 0);
for (std::size_t i = 0; i < NUM_RUN_TESTS; i++) {
// Get password
const std::string password = Base10_2_X(i, charset, 0);
// Generate key
const std::bitset<BLOCK_SIZE> newKey = PasswordToKey(password).Get();
// Generate key
const std::string newKeyBits = Key::FromPassword(password).ToBinaryString();
// Check if this block is already in our map
if (keys.find(newKey) != keys.cend()) {
std::cout << "Collision found between password \""
<< password
<< "\" and \""
<< keys[newKey]
<< "\". The key is \""
<< newKey
<< "\".";
// Check if this block is already in our map
if (keys.find(newKeyBits) != keys.cend()) {
std::cout << "Collision found between password \""
<< password
<< "\" and \""
<< keys[newKeyBits]
<< "\". The key is \""
<< newKeyBits
<< "\".";
FAIL();
}
FAIL();
}
// All good? Insert it into our map
keys[newKey] = password;
}
// All good? Insert it into our map
keys[newKeyBits] = password;
}
return;
return;
}

View File

@ -0,0 +1,51 @@
#!zsh
echo "Make sure to have run all visualization executables in ../build/ (cmake -B build)!"
echo "These generate the base images!"
# Copy all images over, but as pngs, and a bit larger
find ../build/ -maxdepth 1 -type f -name '*.bmp' |\
xargs -I {}\
convert "{}"\
-filter box\
-resize 256x\
"{}.png"
mv ../build/*.png .
# Create a few gifs
# Singleblock diffusion
convert -delay 10 -loop 0 -dispose previous \
"visualize-singleblock-diffusion-input.bmp.png"\
"visualize-singleblock-diffusion-input-flip.bmp.png"\
"visualize-singleblock-diffusion-input.gif"
convert -delay 10 -loop 0 -dispose previous \
"visualize-singleblock-diffusion-output.bmp.png"\
"visualize-singleblock-diffusion-output-flip.bmp.png"\
"visualize-singleblock-diffusion-output.gif"
# Multiblock diffusion
convert -delay 10 -loop 0 -dispose previous \
"visualize-multiblock-diffusion-input.bmp.png"\
"visualize-multiblock-diffusion-input-flip.bmp.png"\
"visualize-multiblock-diffusion-input.gif"
convert -delay 10 -loop 0 -dispose previous \
"visualize-multiblock-diffusion-output.bmp.png"\
"visualize-multiblock-diffusion-output-flip.bmp.png"\
"visualize-multiblock-diffusion-output.gif"
# Extreme input diffusion
convert -delay 10 -loop 0 -dispose previous \
"visualize-extreme-input-diffusion-input.bmp.png"\
"visualize-extreme-input-diffusion-input-flip.bmp.png"\
"visualize-extreme-input-diffusion-input.gif"
convert -delay 10 -loop 0 -dispose previous \
"visualize-extreme-input-diffusion-output.bmp.png"\
"visualize-extreme-input-diffusion-output-flip.bmp.png"\
"visualize-extreme-input-diffusion-output.gif"

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show More