Feistel Networks – Do They Have to use XOR?

If you have no idea what a Feistel network is, but like cryptography and/or random number generation algorithms, read this link first:
Fast & Lightweight Random “Shuffle” Functionality – FIXED!

As a quick refresher, to encrypt data with a Feistel network, you break the plain text data into a left and a right side and do N rounds of this operation:

Left[i+1]  = Right[i];
Right[i+1] = Left[i] ^ RoundFunction(Right[i], key);

Where RoundFunction is ideally some chaotic function that returns some pseudo-random-esque number based on the inputs. For instance, RoundFunction could be MD5 so that it returned the MD5 hash of the data and the key, where the key could be considered the salt of the hash. The better the round function, the better your encryption algorithm will be.

To decrypt data with a Feistel network, you break the data into a left and right side and do the same number of rounds of this operation:

Right[i] = Left[i+1];
Left[i] = Right[i+1] ^ RoundFunction(Left[i+1], key);

Ok, onto the question….

Does it Have to use XOR?

Recently a friend of mine was using Feistel networks for something pretty amazing (so amazing, I can’t even talk about it), but in doing so, he asked me an interesting question. He asked “do you think this HAS to be XOR here, where we combine the round functions result back into the data?”. Well, it turns out, it doesn’t!

The operation has to be a reversible operation though, and you have to do the reverse operation when decrypting that you did while encrypting.

For instance, when encrypting you could add the round function result in, but then when decrypting, you would have to subtract the round function result out.

Or, you could do bitwise rotation left when encrypting, and right when decrypting perhaps.

Basically, anything that has a reverse operation can be used.

You have to be careful though because you might be lured into the trap of thinking that this includes something like multiplication and division.

If you multiply when you encrypt, you might get an integer overflow and lose data that can’t be corrected by doing a divide. For instance, if you multiply 255*2 in an unsigned 8 bit number you get 254 as a result. If you divide 254 by 2 to “undo” the multiplication, you get 127 which is obviously not 255, so we’ve lost some data. In an unsigned 8 bit number, ((255*2)/2) = 127.

If you go the other way and divide on encryption, and multiply on decryption, that doesn’t work either. For instance, when you divide 3 by 2, you get 1 with integer math, and when you multiply by 2, you get 2. So, with integers… ((3/2)*2) = 2.

Confusing note: you ARE able to do irreversible operations within the round function though. Feel free to do a divide or whatever you want in there. If that is difficult to understand how that could possibly work, you aren’t alone. Step through the code a bit by hand with a simple round function and a low number of rounds and you might be able to understand better how it does what it does.

I’m really not sure if anyone else out there does this variation on the traditional Feistel networks or not, but it is pretty interesting to combine the RoundFunction result back into the data with something other than XOR.

Source Code

Here’s some simple C++ code below to play with if you want to mess around with this stuff.


static const unsigned int c_numRounds = 4;

void PrimeRandomNumberPump ()
// if you are curious about this, check out:
// http://blog.demofox.org/2013/06/18/wtf-rand/
for (unsigned int index = 0; index < 20; ++index) rand(); } unsigned char RoundFunction (unsigned char value, unsigned char key) { // Not a particularly effective round function, but the round function // isn't the point of this code. // If you want a better round function, try plugging in a hash function // or another chaotic function that has big changes in output for // small changes in input. Also, you could change c_numRounds to a // higher number if you want better encryption. return value + key | (value * key) + 3; } void Encrypt (unsigned char &left, unsigned char &right, unsigned char key) { for (unsigned int index = 0; index < c_numRounds; ++index) { // Feistel Network Encryption: // Left[i+1] = Right[i]; // Right[i+1] = Left[i] ^ RoundFunction(Right[i], key); // let's do addition to combine the value of the round function on // encryption, instead of doing xor. Xor is used in feistel networks // because xor is it's own inverse operation. unsigned char oldLeft = left; left = right; right = oldLeft + RoundFunction(right, key); } } void Decrypt (unsigned char &left, unsigned char &right, unsigned char key) { for (unsigned int index = 0; index < c_numRounds; ++index) { // Feistel Network Decryption: // Right[i] = Left[i+1]; // Left[i] = Right[i+1] ^ RoundFunction(Left[i+1], key); // let's do subtraction to combine the value of the round function on // decryption, instead of doing xor. Xor is used in feistel networks // because xor is it's own inverse operation. unsigned char oldRight = right; right = left; left = oldRight - RoundFunction(left, key); } } void DoTest (unsigned char plainText1, unsigned char plainText2, unsigned char key, int &tests, int &errors) { // encrypt the plaintext unsigned char cipherText1 = plainText1; unsigned char cipherText2 = plainText2; Encrypt(cipherText1, cipherText2, key); // decrypt the cipher text unsigned char decryptedData1 = cipherText1; unsigned char decryptedData2 = cipherText2; Decrypt(decryptedData1, decryptedData2, key); // if the decrypted data doesn't match the plaintext data, count it as an error // and show the details tests++; if (decryptedData1 != plainText1 || decryptedData2 != plainText2) { errors++; printf("plaintext = 0x%02X%02X\r\n", (unsigned int)plainText1, (unsigned int)plainText2); printf("ciphertext = 0x%02X%02X\r\n", (unsigned int)cipherText1, (unsigned int)cipherText2); printf("decrypteddata = 0x%02X%02X\r\n\r\n", (unsigned int)decryptedData1, (unsigned int)decryptedData2); } } void main (void) { // generate a key PrimeRandomNumberPump(); unsigned char key = (unsigned char)rand(); // run tests with the key int errors = 0; int tests = 0; for (unsigned int y = 0; y < 256; ++y) for (unsigned int x = 0; x < 256; ++x) DoTest((unsigned char)y, (unsigned char)x, key, tests, errors); // display the test results printf("%i tests ran, %i errors encountered. key = 0x%02X\r\n", tests, errors, key); } [/cpp]



About Demofox

I'm a game and engine programmer at Blizzard Entertainment and have been making games since 1990 (starting out with QBasic and TI-85 games) My shipped titles include: * Heroes of the Storm * StarCraft II: Heart of the Swarm & Legacy of the void * Insanely Twisted Shadow Planet (PC) * Gotham City Impostors (PC, 360, PS3) * Line Rider (PC, Wii, DS) I also like hiking, making music, learning cool new stuff and attempting the impossible.