Last active
April 11, 2021 15:55
-
-
Save xenomancer/c459520c7127b2ff11e1ddff6d41493b to your computer and use it in GitHub Desktop.
A standalone alternative to a quick encryption/decryption part of a project. Based on simple Feistel network with some modification to perform as a pseudo-unbalanced network with more than two blocks allowed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright Craig D. Mansfield 2021, zero rights reserved. | |
// TO the extent that it is possible under law, this is released to the public domain. | |
// Do with this as you please. Consider this beerware. | |
// If you like what I wrote and we ever meet up, you can buy me a beer. | |
// This is a header only c++ implementation. | |
// Assuming compiler uses c++14 spec or later. | |
#pragma once | |
#include <stdlib> | |
#include <stdint> | |
#include <vector> | |
#include <functional> | |
#include <exception> | |
#include <stdexcept> | |
#include <random> | |
#include <stdio> | |
using namespace std; | |
//typedef unsigned long long size_t; | |
//using vecc_t = std::vector<unsigned char>; | |
using hash_func = function<vector<unsigned char>(const vector<unsigned char>)>; | |
#pragma region rotations | |
inline void rotate_left( | |
vector<unsigned char>& aData, | |
size_t aCount | |
) { | |
size_t aN = aData.size(); | |
size_t xCount = ((aCount % aN) + aN) % aN; | |
if (xCount == 0) return; | |
vector<unsigned char> tmp(aN); | |
size_t x; | |
for (int i = 0; i < aN; i++) { | |
x = (i + aN - xCount) % aN; | |
tmp[i] = aData[x]; | |
} | |
for (int i = 0; i < aN; i++) { | |
aData[i] = tmp[i]; | |
} | |
} | |
inline void rotate_right( | |
vector<unsigned char>& aData, | |
size_t aCount | |
) { | |
size_t aN = aData.size(); | |
size_t xCount = ((aCount % aN) + aN) % aN; | |
if (xCount == 0) return; | |
vector<unsigned char> tmp(aN); | |
size_t x; | |
for (int i = 0; i < aN; i++) { | |
x = (i + aN + xCount) % aN; | |
tmp[i] = aData[x]; | |
} | |
for (int i = 0; i < aN; i++) { | |
aData[i] = tmp[i]; | |
} | |
} | |
#pragma endregion | |
#pragma region key_gen | |
inline vector<vector<unsigned char>> key_gen( | |
const vector<unsigned char>& aSeed, | |
size_t aNChunkSize, | |
size_t aNRounds, | |
hash_func aHashFunc | |
) { | |
vector<unsigned char> tmpKey; | |
vector<unsigned char> aHashOut; | |
vector<vector<unsigned char>> res(aNRounds); | |
tmpKey = aSeed; | |
for (int i = 0; i < aNRounds; i++) { | |
aHashOut = aHashFunc(tmpKey); | |
size_t aNHashSize = aHashOut.size(); | |
tmpKey = vector<unsigned char>(aNChunkSize); | |
for (int j = 0; j < aNChunkSize; j++) { | |
tmpKey[j] = aHashOut[j % aNHashSize]; | |
} | |
res[i] = tmpKey; | |
} | |
return res; | |
} | |
#pragma endregion | |
#pragma region combine | |
inline void combine( | |
vector<unsigned char>& aData, | |
const vector<unsigned char>& aKey, | |
size_t aXStart, | |
size_t aYStart, | |
hash_func aHashFunc | |
) { | |
size_t aN = aKey.size(); | |
size_t aNHashSize; | |
vector<unsigned char> aHashIn(aN); | |
vector<unsigned char> aHashOut; | |
// pre-mix x-data and key | |
for (int i = 0; i < aN; i++) { | |
aHashIn[i] = aData[aXStart + i] ^ aKey[i]; | |
} | |
// hash pre-mixed data | |
aHashOut = aHashFunc(aHashIn); | |
aNHashSize = aHashOut.size(); | |
// store post-mix of y-data and hashed pre-mix data to the y-data | |
for (int i = 0; i < aN; i++) { | |
aData[aYStart + i] = aData[aYStart + i] ^ aHashOut[i % aNHashSize]; | |
} | |
} | |
#pragma endregion | |
#pragma region transform | |
inline void transform( | |
vector<unsigned char>& aData, | |
const vector<unsigned char>& aKey, | |
size_t aOffset, | |
hash_func aHashFunc | |
) { | |
size_t aNData = aData.size(); | |
size_t aNChunkSize = aKey.size(); | |
size_t aNChunks = aNData / aNChunkSize; | |
for (int i = 0; i < aNChunks; i++) { | |
if ((i % 2) == 1) { | |
combine(aData, aKey, aOffset + i * aNChunkSize, aOffset + (i - 1) * aNChunkSize, aHashFunc); | |
} | |
} | |
} | |
#pragma endregion | |
#pragma region encrypt | |
inline void encrypt( | |
vector<unsigned char>& aData, | |
vector<vector<unsigned char>>& aKeys, | |
hash_func aHashFunc | |
) { | |
size_t aNData = aData.size(); | |
size_t aNRounds = aKeys.size(); | |
if (aNData == 0) return; | |
if (aNRounds == 0) return; | |
size_t aNChunkSize = aKeys[0].size(); | |
size_t aNChunks = aNData / aNChunkSize; | |
size_t aNOffset = aNData - aNChunks * aNChunkSize; | |
size_t aOffset = 0; | |
vector<unsigned char> tmpKey; | |
for (int i = 0; i < aNRounds; i++) { | |
// get round key | |
tmpKey = aKeys[i]; | |
// perform first half of round | |
aOffset = 0; | |
transform(aData, tmpKey, aOffset, aHashFunc); | |
rotate_left(aData, aNChunkSize); | |
// perform second half of round | |
aOffset = aNOffset; | |
transform(aData, tmpKey, aOffset, aHashFunc); | |
rotate_left(aData, aNChunkSize); | |
} | |
} | |
inline void encrypt( | |
vector<unsigned char>& aData, | |
vector<unsigned char>& aSeedKey, | |
size_t aNChunkSize, | |
size_t aNRounds, | |
hash_func aHashFuncData, | |
hash_func aHashFuncKeys | |
) { | |
size_t aNData = aData.size(); | |
size_t aNSeed = aSeedKey.size(); | |
if (aNChunkSize == 0) return; // no change to data needed; | |
if (aNChunkSize > (aNData / 2)) throw exception("Chunk size must be less than or equal to half of the size of the data."); | |
if (aNRounds == 0) return; // no change to data needed | |
vector<vector<unsigned char>> aKeys = key_gen(aSeedKey, aNChunkSize, aNRounds, aHashFuncKeys); // get keys | |
// encrypt | |
encrypt(aData, aKeys, aHashFuncData); | |
} | |
inline void encrypt( | |
vector<unsigned char>& aData, | |
vector<unsigned char>& aSeedKey, | |
size_t aNChunkSize, | |
size_t aNRounds, | |
hash_func aHashFunc | |
) { | |
encrypt(aData, aSeedKey, aNChunkSize, aNRounds, aHashFunc, aHashFunc); | |
} | |
#pragma endregion | |
#pragma region decrypt | |
inline void decrypt( | |
vector<unsigned char>& aData, | |
vector<vector<unsigned char>>& aKeys, | |
hash_func aHashFunc | |
) { | |
size_t aNData = aData.size(); | |
size_t aNRounds = aKeys.size(); | |
if (aNData == 0) return; | |
if (aNRounds == 0) return; | |
size_t aNChunkSize = aKeys[0].size(); | |
size_t aNChunks = aNData / aNChunkSize; | |
size_t aNOffset = aNData - aNChunks * aNChunkSize; | |
size_t aOffset = 0; | |
vector<unsigned char> tmpKey; | |
for (int i = 0; i < aNRounds; i++) { | |
// get round key | |
tmpKey = aKeys[aNRounds - 1 - i]; | |
// perform first half of round | |
aOffset = aNOffset; | |
rotate_right(aData, aNChunkSize); | |
transform(aData, tmpKey, aOffset, aHashFunc); | |
// perform second half of round | |
aOffset = 0; | |
rotate_right(aData, aNChunkSize); | |
transform(aData, tmpKey, aOffset, aHashFunc); | |
} | |
} | |
inline void decrypt( | |
vector<unsigned char>& aData, | |
vector<unsigned char>& aSeedKey, | |
size_t aNChunkSize, | |
size_t aNRounds, | |
hash_func aHashFuncData, | |
hash_func aHashFuncKeys | |
) { | |
size_t aNData = aData.size(); | |
size_t aNSeed = aSeedKey.size(); | |
if (aNChunkSize == 0) return; // no change to data needed; | |
if (aNChunkSize > (aNData / 2)) throw exception("Chunk size must be less than or equal to half of the size of the data."); | |
if (aNRounds == 0) return; // no change to data needed | |
vector<vector<unsigned char>> aKeys = key_gen(aSeedKey, aNChunkSize, aNRounds, aHashFuncKeys); // get keys | |
// decrypt | |
decrypt(aData, aKeys, aHashFuncData); | |
} | |
inline void decrypt( | |
vector<unsigned char>& aData, | |
vector<unsigned char>& aSeedKey, | |
size_t aNChunkSize, | |
size_t aNRounds, | |
hash_func aHashFunc | |
) { | |
decrypt(aData, aSeedKey, aNChunkSize, aNRounds, aHashFunc, aHashFunc); | |
} | |
#pragma endregion | |
#pragma region demo | |
inline void demo() { | |
// size parameters | |
size_t aNData = 1000; // size of data in bytes | |
size_t aNRounds = 100; // number of rounds | |
size_t aNChunkSize = aNData / 3; // size of a data chunk | |
size_t aNDataBits = aNData * CHAR_BIT; // number of bits in data | |
cout << "Data size = " << aNData << endl; | |
cout << "Chunk size = " << aNChunkSize << endl; | |
cout << "Num rounds = " << aNRounds << endl; | |
// a truly terrible "hash" function | |
hash_func aHF = [](const vector<unsigned char>& aIn){ | |
size_t aN = aIn.size(); | |
vector<unsigned char> lsft(aIn); | |
vector<unsigned char> rsft(aIn); | |
vector<unsigned char> res(aIn); | |
// this is explicitly bad | |
// this will only demonstrate the encryption/decryption well with random data | |
// this will very likely not protect your data | |
// never use this to protect real data! | |
// never!!! | |
// force a data mismatch | |
rotate_left(lsft, 1); | |
rotate_right(rsft, 1); | |
// get the difference in the data | |
for (int i = 0; i < aN; i++) { | |
res[i] = (rsft[i] ^ lsft[i]); | |
} | |
// not even if you are in a hurry! | |
// don't use this! | |
// return the result | |
return res; | |
}; | |
// data storage | |
vector<vector<unsigned char>> keys; // the seed key | |
vector<unsigned char> key(aNChunkSize); // a set of keys | |
vector<unsigned char> msg(aNData); // the original unencrypted message | |
vector<unsigned char> enc; // the encrypted message | |
vector<unsigned char> dec; // the decrypted message | |
vector<unsigned char> dff(aNData); // difference between msg and enc (should be a mess) | |
vector<unsigned char> chk(aNData); // difference between msg and dec (should be all zeros) | |
size_t dff_bits; // diff bits in dff (should be a lot) | |
size_t chk_bits; // diff bits in chk (should be zero) | |
// for random number generation | |
random_device rd; | |
mt19937_64 gen = mt19937_64(rd()); | |
uniform_int_distribution<size_t> dis; | |
// get a random seed key | |
for (int i = 0; i < aNChunkSize; i++) { | |
key[i] = dis(gen); | |
} | |
cout << "generated a random seed key" << endl; | |
// generate the keys | |
keys = key_gen(key, aNChunkSize, aNRounds, aHF); | |
cout << "generated keys from seed key" << endl; | |
// get a random message | |
for (int i = 0; i < aNData; i++) { | |
msg[i] = dis(gen); | |
} | |
cout << "generated a random message" << endl << endl; | |
// encrypt the message using the generated keys | |
enc = vector<unsigned char>(msg); // get a copy of the message | |
encrypt(enc, keys, aHF); // encrypt the message | |
cout << "message encrypted" << endl << endl; | |
// decrypt the message using the generated keys | |
dec = vector<unsigned char>(enc); // get a copy of the encrypted message | |
decrypt(dec, keys, aHF); // decrypt the message | |
cout << "message decrypted" << endl << endl; | |
// get the diffs data | |
for (int i = 0; i < aNData; i++) { | |
dff[i] = (msg[i] > enc[i]) ? (msg[i] - enc[i]) : (enc[i] - msg[i]); | |
chk[i] = (msg[i] > dec[i]) ? (msg[i] - dec[i]) : (dec[i] - msg[i]); | |
} | |
// get the diff bits | |
dff_bits = 0; | |
chk_bits = 0; | |
for (int i = 0; i < aNData; i++) { | |
unsigned char tmp_dff = dff[i]; | |
unsigned char tmp_chk = chk[i]; | |
for (int j = 0; j < CHAR_BIT; j++) { | |
// avoiding entire issue of popcount existing or not existing, etc. | |
if (((tmp_dff >> j) & 1) == 1) dff_bits++; | |
if (((tmp_chk >> j) & 1) == 1) chk_bits++; | |
} | |
} | |
cout << "dff_bits = " << dff_bits << endl; | |
cout << "chk_bits = " << chk_bits << endl; | |
// wait for user to end test in console | |
cin.get(); | |
} | |
#pragma endregion | |
// EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment