Readme and visualizations
BIN
GCryptLib/visualizations/input-big-flip.bmp.png
Normal file
After Width: | Height: | Size: 938 B |
BIN
GCryptLib/visualizations/input-big.bmp.png
Normal file
After Width: | Height: | Size: 938 B |
BIN
GCryptLib/visualizations/input-big.gif
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
GCryptLib/visualizations/input-flip.bmp.png
Normal file
After Width: | Height: | Size: 355 B |
BIN
GCryptLib/visualizations/input.bmp.png
Normal file
After Width: | Height: | Size: 358 B |
BIN
GCryptLib/visualizations/input.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
GCryptLib/visualizations/output-big-flip.bmp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
GCryptLib/visualizations/output-big.bmp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
GCryptLib/visualizations/output-big.gif
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
GCryptLib/visualizations/output-flip.bmp.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
GCryptLib/visualizations/output.bmp.png
Normal file
After Width: | Height: | Size: 443 B |
BIN
GCryptLib/visualizations/output.gif
Normal file
After Width: | Height: | Size: 3.6 KiB |
108
readme.md
@ -49,11 +49,11 @@ const std::string input = "I am a super secret message!";
|
||||
std::cout << input << std::endl;
|
||||
|
||||
// Encrypt
|
||||
const std::string encrypted = GCryptWrapper::EncryptString(input, "password1");
|
||||
const std::string encrypted = GWrapper::EncryptString(input, Key::FromPassword("password1"));
|
||||
std::cout << encrypted << std::endl;
|
||||
|
||||
// Decrypt
|
||||
const std::string decrypted = GCryptWrapper::DecryptString(encrypted, "password1");
|
||||
const std::string decrypted = GWrapper::DecryptString(encrypted, Key::FromPassword("password1"));
|
||||
std::cout << decrypted << std::endl;
|
||||
```
|
||||
|
||||
@ -62,18 +62,38 @@ std::cout << decrypted << std::endl;
|
||||
using namespace Leonetienne::GCrypt;
|
||||
|
||||
// Encrypt
|
||||
GCryptWrapper::EncryptFile("main.cpp", "main.cpp.crypt", "password1");
|
||||
GCryptWrapper::EncryptFile("main.cpp", "main.cpp.crypt", Key::FromPassword("password1"));
|
||||
|
||||
// Decrypt
|
||||
GCryptWrapper::DecryptFile("main.cpp.crypt", "main.cpp.clear", "password1");
|
||||
GCryptWrapper::DecryptFile("main.cpp.crypt", "main.cpp.clear", Key::FromPassword("password1"));
|
||||
```
|
||||
|
||||
If you want to do more complex stuff, use the cipher-class [`GCrypt::Cipher`](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/include/GCrypt/Cipher.h) aswell as the conversion methods in [Util.h](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/include/GCrypt/Util.h). This way you can cipher on bitlevel. Examples on how to do this are in [GCryptWrapper.cpp](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/src/GCryptWrapper.cpp).
|
||||
This way you could, for example, decrypt an ecrypted file directly into memory. Or use a full-length key instead of a password.
|
||||
### Prefer keyfiles instead?
|
||||
```cpp
|
||||
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
|
||||
GCryptWrapper::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");
|
||||
```
|
||||
|
||||
If you want to do more complex stuff, use the cipher-class [`GCrypt::GCipher`](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/include/GCrypt/GCipher.h) aswell as the conversion methods in [Util.h](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/include/GCrypt/Util.h).
|
||||
This way you can cipher on bitlevel. Examples on how to do this are in [GWrapper.cpp](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/src/GWrapper.cpp).
|
||||
This way you could, for example, decrypt an ecrypted file directly into memory.
|
||||
Without saying, this is more advanced and not as-easy as the methods supplied in the wrapper.
|
||||
|
||||
---
|
||||
<sup>\*</sup> A key is always of size `BLOCK_SIZE`. The default block size is 512 (bit), but you can easily change it in [Config.h](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/include/GCrypt/Config.h) or wherever it'll be put in the INCLUDE/*.cpp. `BLOCK_SIZE` is also the minimal output length!
|
||||
<sup>\*</sup> A key is always of size `BLOCK_SIZE`. The default block size is 512 (bit), but you can easily change it in [Config.h](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/master/GCryptLib/include/GCrypt/Config.h) or wherever it'll be put in the INCLUDE/\*.cpp. `BLOCK_SIZE` is also the minimal output length!
|
||||
|
||||
## The deets 🍝
|
||||
|
||||
@ -83,23 +103,69 @@ Without saying, this is more advanced and not as-easy as the methods supplied in
|
||||
* [RRKM] Never heard of a mode like this, so i've named it **R**olling**R**ound**K**ey**M**ode. This basically means that the round key extrapolation is carried out continously over EVERY round on EVERY block. So in addition to *M<sub>i</sub>* being dependent on *E(M<sub>i-1</sub>,K<sub>i-1,0</sub>)* due to CBC, so is now *K<sub>i</sub>* dependent on *K<sub>i-1,r</sub>* 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.
|
||||
|
||||
### Password to key
|
||||
How does *GC* transform a password to a key?
|
||||
First up, we have to establish what requirements this transformation must fulfill:
|
||||
* A full key. Not just *len(passwd)\*8* bits and the rest zero-padded.
|
||||
* Even if *len(passwd)\*8 > KEY_SIZE*, every bit of the password should affect the key
|
||||
* Diffusion
|
||||
* Ideally good collision resistance
|
||||
How does GCrypt transform a password to a key?..
|
||||
Well, it uses the included hash function [GHash](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/feature/relaunch/GCryptLib/include/GCrypt/GHash.h).
|
||||
|
||||
Let's be honest, I'm not a cryptographer, I have no idea how collision resistant this is.
|
||||
This means, it has to be considered *insecure*!
|
||||
I have tried a few passwords brute-forcibly, experimentally (about 1mil) and have not been able to produce a collision.
|
||||
Obviously there have to be collisions, since *|P|, len\(p\) ∈ ℵ ≫ |C|, len(c)*.
|
||||
### 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*.
|
||||
(*b<sub>i</sub> = b</sub>i-1</sub> ⊕ E(key=b, data=k)*)
|
||||
|
||||
How does it work? Basically, what happens is your password gets recoded to binary. It is then split into blocks of
|
||||
size KEY_SIZE, and they are combined using *c<sub>i+1</sub> = c<sub>i</sub> ⨁ E(c=block<sub>i</sub>, k=block<sub>i</sub>)*. *c<sub>0</sub>* is a static initialization vector. The final *c* is they key corresponding to a password.
|
||||
The lastest *b* represents the current result of the hash function.
|
||||
|
||||
This is a one-way operation. Since the key used for this operation is the cleartext itself, you cannot undo it without already
|
||||
knowing the password(=cleartext) to begin with. *You could make a hashfunction out of this.*
|
||||
GHash also supports a do-it-all wrapper method that takes a Flexblock (A block of arbitrary length), and returns a hashsum for it.
|
||||
This wrapper function adds an additional block including the length of the input. 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](https://gitea.leonetienne.de/leonetienne/GCrypt/src/branch/feature/relaunch/GCryptLib/include/GCrypt/GPrng.h).
|
||||
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 digested the prngs 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 ⊕ 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!
|
||||
<div>
|
||||
<span style="display: inline-block;">
|
||||
`"Hello :3"` in binary:
|
||||

|
||||
</span>
|
||||
|
||||
<span style="display: inline-block;">
|
||||
It's ciphertext:
|
||||

|
||||
</span>
|
||||
</div>
|
||||
|
||||
Now, let's flip a single bit in the input:
|
||||
|
||||
One bit flipped:
|
||||

|
||||
|
||||
It's ciphertext:
|
||||

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

|
||||
|
||||
Ciphertext:
|
||||

|
||||
|
||||
|
||||
What about input longer a single block?
|
||||
|
||||
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.
|
||||
|
||||
## Noteworthy:
|
||||
* This is no fixed algorithm. Newer versions may very well be unable to decrypt ciphertexts encrypted with earlier versions.
|
||||
|