01.06.2022

GCrypt

Tags: C++ / Code it yourself / Cryptography

Or... by it's full name, GhettoCrypt
is an educational project I've started with the intention of learning more about feistel ciphers.
See repository

Also be sure to check out the library's documentation here.

Don't care about the C++ library?

Skip right to the command line tool implementation!


GCrypt is a feistel block cipher implemented in C++ with the functional goal of providing a pythonic API. It comes with a wrapper class making it even easier to use, and can be easily installed as a git submodule.

Besides simple en- and decryption, it also supports calculating hashsums, and provides an extensive pseudo random number generator (prng).

Features

  • It has very easy syntax
  • It's slow
  • It's probably super insecure
  • But the syntax is pythonlike easy🙇
  • 512-bit keys

What are the actual advantages?

  • Easily included into any project
  • No dependencies
  • 1 Line to use
  • 100% cross plattform

What could it be used for?

  • Data obfuscation
  • If the only other option would be no encryption at all

Usage examples

Working with strings

using namespace Leonetienne::GCrypt;

// Get some string
const std::string input = "I am a super secret message!";
std::cout << input << std::endl;

// Encrypt
const std::string encrypted = GWrapper::EncryptString(input, Key::FromPassword("password1"));
std::cout << encrypted << std::endl;

// Decrypt
const std::string decrypted = GWrapper::DecryptString(encrypted, Key::FromPassword("password1"));
std::cout << decrypted << std::endl;

Working with files

using namespace Leonetienne::GCrypt;

// Encrypt
GWrapper::EncryptFile("main.cpp", "main.cpp.crypt", Key::FromPassword("password1"));

// Decrypt
GWrapper::DecryptFile("main.cpp.crypt", "main.cpp.clear", Key::FromPassword("password1"));

Using keyfiles

using namespace Leonetienne::GCrypt;

// Create a random key
const Key newKey = Key::Random(); // Will create a key from actual randomness (like, hardware events)

// Use the key
GWrapper::EncryptFile("main.cpp", "main.cpp.crypt", newKey);

// Save the key to a keyfile
newKey.WriteToFile("/var/stuff/mykeyfile");

// ...

// Load the key
const Key loadedKey = Key::LoadFromFile("/var/stuff/mykeyfile");

The deets 🍝

Modes of operation

  • [CBC] This block cipher makes use of cipher block chaining. Nothing special.
  • [IV] The initialization vector is indeed a bit of special sauce, as it depends on your key instead of being static. It is generated by running the feistel network on E(m=seed, k=seed), which is a one-way function, because m=k.
  • [RRKM] Never heard of a mode like this, so i've named it RollingRoundKeyMode. This basically means that the round key extrapolation is carried out continously over EVERY round on EVERY block. So in addition to Mi being dependent on E(Mi-1,Ki-1,0) due to CBC, so is now Ki dependent on Ki-1,r with r being the maximum number of extrapolated keys within a call of E(). This is handled within the feistel network class, as an instance lifecycle sees all blocks. Just in case you want to take a peek.
  • [I-don't-even-know-any-more] Inspired by Rijndael, it does some monkey tricks with reversible matrix-mutators after each feistel round.

Password to key transformation

How does GCrypt transform a password to a key?.. Well, it uses the included hash function GHash.

Hashing with GHash

GHash is a streaming hash function based on the GCipher.
For all intents and purposes, it does the following: You have a Block b, which is initialized with a static random distribution. Once you give the GHash instance a data block to digest, it will use the GCipher to encrypt it, with itself as a key, and xor that onto b. (bi = bi-1 ⊕ E(key=bnew, data=bnew))

The lastest b represents the current result of the hash function.

GHash also supports a do-it-all wrapper method that takes a vector of blocks, and returns a hashsum for it. This wrapper function adds an additional block including the length of the input, if provided. This wrapper function is used to transform Passwords to Keys.

GPrng...?

Whilst we're at it, why not implement a pseudo-random number generator based on GHash aswell. So here it is, GPrng.
GPrng is really nothing special. I just wanted to implement it, mainly to visualize the GCiphers entropy.

GPrng basically does the following: It creates a GHash instance, which initially digests the prng's seed. This produces a hash result, which is one block in size. This block gets eaten up, as pseudo-randomness is used. Once there are no bits left, the GHash instance will digest the result of this block ⊕ the initial seed. The xor operation ensures that an observer will never know the internal state of the GHash instance. This is important, as to ensure an observer won't be able to predict future output.

Speaking of... Visualizations!

Single-block diffusion

"Hello :3" in binary, and it's ciphertext:

"Hello :3" in binary     Ciphertext 1

Now, let's flip a single bit in the input:

One bit flipped, and again the corresponding ciphertext:
One bit flipped     Ciphertext for flipped bit

Let's gif them together, to better see the difference:
Input     Ciphertext

As shown, flipping even a single bit, affects the entire ciphertext.

What about input longer than a single block?

Input, and ciphertext:
Input     Ciphertext

Notice how the ciphertext doesn't change until the block containing the bitflip is reached? This is a limitation of cipher block chaining.

What about extreme inputs?

How non-transparent is the cipher with extreme inputs? Even with a super problematic key?:

Input, key, and ciphertext:
Input     Key     Ciphertext

Notice how even cleartexts that are completely uniform, with a key that is almost just zeores, will still produce ambiguous ciphertexts.

What about the PRNG's distribution?

Check it out, here are the distributions of a few different getter-methods, some in black/white, some in grayscale, some in color.

Blackwhite - GetBit(), Grayscale - GetRandom(), and Grayscale - operator():
Input     Key     Ciphertext

Color - GetRandom(), Color - operator(), and Color - GetBlock():
Input     Key     Ciphertext

Worth a look: GCrypt-CLI

GCrypt is also available as a command line tool:

See repository

The realized featureset includes:

  • Taking a password as a parameter
  • Asking for a password on stdin
  • Creating and using Keyfiles
  • A variety of ciphertext formats
  • Encryption & Decryption (files, streams, parameters)
  • Hashing (files, streams, parameters)
  • Optional buffering (not digesting an input block-by-block, but all at once)
  • Printing progress (it is still slow after all)

A few usage examples

I want to encrypt text!

$ gcrypt -e --keyask --intext "hello, world!"
efbebc429c8370bf84f00b0d8ccbaf7858b3b87d71ff58cb1cfefa8fb0c68094c0865565873aa8a5254ede59be46e81a4d4917e679b18cb290dbd6669cb6207a

Now decrypt it

$ gcrypt -d --keyask --intext "efbebc429c8370bf84f00b0d8ccbaf7858b3b87d71ff58cb1cfefa8fb0c68094c0865565873aa8a5254ede59be46e81a4d4917e679b18cb290dbd6669cb6207a"
hello, world!

Creating keyfiles

$ gcrypt --generate-key --ofile "my-keyfile.bin"

This will generate a random 512-bit keyfile from hardware events and other random sources, if available.
To see how this randomness gets sourced, see std::random_device.

Encrypting files

$ gcrypt -e --keyask --infile "cat.jpg" --ofile "cat.jpg.crypt"

File cat.jpg.crypt will be created.

Decrypting files

$ gcrypt -d --keyask --infile "cat.jpg.crypt" --ofile "decrypted_cat.jpg"

File decrypted_cat.jpg will be created. You can now open it again. Its contents match cat.jpg.

⚠️ Since this is a block cipher, decrypted files may be tailpadded with a few nullbytes.

Encrypting large files takes time. How's the progress?

$ gcrypt -e --keyask --infile "cat.jpg" --buffer-input --progress

Something along the lines of Encrypting... (Block 200 / 1148 - 17.4216%) will be regularly, but not too often, printed to stderr. Obviously, to print progress, we have to know the size of the input. Hence, it has to be buffered.

Any cipher can also compute hashsums

$ gcrypt -h --intext "hello, world!"
a96f42c9d97e46b9e1ed7de5182770170d4ef9b7b8264f3fbd89b38dc60c1fe06232653f5856013307fc020fb1d35f2bea26bc0f373c5ac35a722c6b03d8254d

$ gcrypt -h --infile "cat.jpg"
fe6bdfb6ec39771c4fdcdc40e52397bcd67fbfef0ad5a15ebbd8b9e4c2a815848b3984eda5ef6f727e9e420c23500c90c42ab80ac5659048be8969357741e3e5

The hashsum will always be of size BLOCK_SIZE. That is 512 bits.

Streaming the output of file en/decryption.

Easily! If you do not supply any output or input, stdout and stdin will be used instead!

# mpv is a media player, as an example
$ gcrypt -d --key "123" --infile "music.mp3.crypt" | mpv -

My favourite feature: esoteric data formats

Base 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

Yes, you can send these... adventorous texts to your friends, and they can actually decipher them back to the original message :). Almost going a bit into the steganography territory here.

These weird number bases do not impact security at all. This is because they are just that: Number bases, to represent a bunch of bytes, that is our ciphertext.

*I've added linebreaks to the ciphertexts, to make them more readable. They are not actually part of the output.

These just bring a bit more fun into the big world of cryptography :).

A more elaborate documentation is available in the projects readme.