From 4d083e6acf18f2d52030acf82691b335f89f3e67 Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Mon, 7 Feb 2022 01:50:00 +0100 Subject: [PATCH] Updated readme --- .../Password2Key_CollisionResistance.cpp | 110 ++++++++++++++++++ SimpleTests/SimpleTests.vcxproj | 1 + SimpleTests/SimpleTests.vcxproj.filters | 3 + readme.md | 35 +++++- 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 SimpleTests/Password2Key_CollisionResistance.cpp diff --git a/SimpleTests/Password2Key_CollisionResistance.cpp b/SimpleTests/Password2Key_CollisionResistance.cpp new file mode 100644 index 0000000..c9c32ca --- /dev/null +++ b/SimpleTests/Password2Key_CollisionResistance.cpp @@ -0,0 +1,110 @@ +#include "CppUnitTest.h" +#include "../GhettoCrypt/Util.h" +#include "../GhettoCrypt/Config.h" +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace GhettoCipher; + +// We can generate passwords by just translating a decimal number to base "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +inline std::string Base10_2_X(const unsigned long long int i, const std::string set, unsigned int padding) +{ + if (set.length() == 0) + return ""; // Return empty string, if set is empty. Play stupid games, win stupid prizes. + + std::stringstream ss; + + if (i != 0) + { + { + unsigned long long int buf = i; + while (buf != 0) + { + const unsigned long long int mod = buf % set.length(); + buf /= set.length(); + ss << set[(std::size_t)mod]; + } + } + { + const std::string buf = ss.str(); + ss.str(""); + for (long long int i = buf.length() - 1; i >= 0; i--) + ss << buf[(std::size_t)i]; + } + } + else + { + ss << set[0]; // If i is 0, just pass a null-value. The algorithm would hang otherwise. + } + + // Add as much null-values to the left as requested. + if (ss.str().length() < padding) + { + const std::size_t cachedLen = ss.str().length(); + const std::string cachedStr = ss.str(); + ss.str(""); + for (std::size_t i = 0; i < padding - cachedLen; i++) + ss << set[0]; + ss << cachedStr; + } + + return ss.str(); +} + +using convert_t = std::codecvt_utf8; + +namespace SimpleTests +{ + TEST_CLASS(Password2Key) + { + public: + + // Run a few thousand random passwords through the keygen and see if we'll find a collision. + // This test passing does NOT mean that it's resistant! Maybe good, maybe shit! But if it fails, it's definitely shit. + // Already validated range: Password 0 - 1.000.000 + TEST_METHOD(CollisionResistance) + { + // 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; + + std::unordered_map, std::string> keys; // + + // Try NUM_RUN_TESTS passwords + const std::string charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + std::wstring_convert strconverter; + + 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 newKey = PasswordToKey(password).Get(); + + // Check if this block is already in our map + if (keys.find(newKey) != keys.cend()) + { + std::wstringstream wss; + wss << "Collision found between password \"" + << strconverter.from_bytes(password) + << "\" and \"" + << strconverter.from_bytes(keys[newKey]) + << "\". The key is \"" + << newKey + << "\"."; + + Assert::Fail(wss.str().c_str()); + } + + // All good? Insert it into our map + keys[newKey] = password; + } + + return; + } + }; +} diff --git a/SimpleTests/SimpleTests.vcxproj b/SimpleTests/SimpleTests.vcxproj index 1318a25..ffb9d58 100644 --- a/SimpleTests/SimpleTests.vcxproj +++ b/SimpleTests/SimpleTests.vcxproj @@ -158,6 +158,7 @@ + diff --git a/SimpleTests/SimpleTests.vcxproj.filters b/SimpleTests/SimpleTests.vcxproj.filters index a66a1ff..3183e7f 100644 --- a/SimpleTests/SimpleTests.vcxproj.filters +++ b/SimpleTests/SimpleTests.vcxproj.filters @@ -21,5 +21,8 @@ Quelldateien + + Quelldateien + \ No newline at end of file diff --git a/readme.md b/readme.md index 73c3eae..a9eb803 100644 --- a/readme.md +++ b/readme.md @@ -4,16 +4,16 @@ ## What the hell is this? An educational project on implementing a block cipher using a feistel network. -To provide at least some security this is using some DES-inspired modes of operation like *cipher block chaining*. -This way this provides relatively good diffusion. +This block cipher employs a few modes of operation. Read more about them [here](#modes-of-operation). ## Features * It has very easy syntax * It's slow +* It absolutely tanks your ram when working with files +* Even leaves some key fragments in there✨ * It's probably super insecure -* It leaves your keys sprinkled in ram✨ * 512-bit keys \* -* But the syntax is pythonlike easy🙇 +* But the syntax is pythonlike easy🙇 It's pretty ghetto, you know? @@ -71,6 +71,33 @@ Without saying, this is more advanced and not as-easy as the methods supplied in --- \* 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://github.com/Leonetienne/GhettoCrypt/blob/master/GhettoCrypt/Config.h) or wherever it'll be put in the INCLUDE/*.cpp. `BLOCK_SIZE` is also the minimal output length! +## 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)*. +* [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 *Mi* being dependent on *E(M,Ki-1,0)i-1* 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, if 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 +* Ideally good collision resistance + +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, because *|P|, len\(p\) ∈ ℵ ≫ |C|*. + +How does it work? Basically, what happens is your password gets recoded to binary. It is then split into blocks of +size KEY_SIZE, they are ⨁ together, and this single block is then encrypted with itself as a key. + +The end result is the key corresponding to your password. +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.* + + ## LICENSE ``` BSD 2-Clause License