Andromeda: The Encryption Object

The Yull class handles IO, setup, and managing the read and writing of data and submitting that data to the encryption object. The encryption object is the Andromeda class.

Before encryption (or decryption) begins in the Andromeda class, Yull instantiates the Andromeda object and instructs it to setup the jump table. Andromeda has 54 encryption procedures. Andromeda constructs an array of 256 delegates. (A C# delegate is like a C++ function pointer.) Its construction might look like this:

 for (int i = 0; i <= 255; i++)
{

if (i < 7) encrypt[i] = new Encrypt(Encrypt1);
if (7 <= i && i < 14) encrypt[i] = new Encrypt(Encrypt33);
if (14 <= i && i < 20) encrypt[i] = new Encrypt(Encrypt26);
if (20 <= i && i < 25) encrypt[i] = new Encrypt(Encrypt2);
if (25 <= i && i < 30) encrypt[i] = new Encrypt(Encrypt32);
if (30 <= i && i < 35) encrypt[i] = new Encrypt(Encrypt25);
if (35 <= i && i < 40) encrypt[i] = new Encrypt(Encrypt36);
if (40 <= i && i < 47) encrypt[i] = new Encrypt(Encrypt16);
if (47 <= i && i < 53) encrypt[i] = new Encrypt(Encrypt3);
if (53 <= i && i < 60) encrypt[i] = new Encrypt(Encrypt37);
if (60 <= i && i < 65) encrypt[i] = new Encrypt(Encrypt4);
if (65 <= i && i < 70) encrypt[i] = new Encrypt(Encrypt43);
if (70 <= i && i < 75) encrypt[i] = new Encrypt(Encrypt24);
if (75 <= i && i < 80) encrypt[i] = new Encrypt(Encrypt38);
and so forth. The decryption array is set up in a similar fashion. All the Encrypt functions are prototyped liked this: function (ref byte[] inB, int i)

After the array is set up, the code starts:


      for (i = 0; i < Key.Length; i++)
      {
         encrypt[Key[i]](ref inB, i);
      }

The key length is the same as the read buffer.

When this for loop finishes, Andromeda returns control back to the Yull object, which then continues the rounds loop.


	for (int i = 0; i < iRounds; i++) //iRounds is different with each read
	{
		androm.Key = new byte[iKeyLength]; //androm is the Andromeda object
		System.Buffer.BlockCopy(Keys[outerRead][i], 0, androm.Key, 0, iKeyLength);
		androm.EncryptStart();
	}

And when the rounds loop ends Yull cycles back to read the next part of the file and process starts again. Before starting the encryption loop, Yull has created a multidimensional array of keys, sufficient for the entire encryption process:

secretKeys = new byte[Rounds.Length][][]; //yes, secretKeys...a bit hokey int j = 0; for (int i = 0; i < secretKeys.Length; i++) //length is the number of buff reads { secretKeys[i] = new byte[Rounds[i]][]; //new byte[iRounds][]; for (j = 0; j < Rounds[i]; j++) { encryptKey(ref secretKey); secretKeys[i][j] = new byte[iKeyLength]; System.Buffer.BlockCopy(secretKey, 0, secretKeys[i][j], 0, iKeyLength); } }

Getting back to the encryption loop, when it completes, the data is written out and the process continues until the last block is read by Yull. Nearly all (or all) encryption relies heavily on a few instructions on the chip: XOR, NOT, ROL and ROR. These are important because they are reversible. If you XOR (eXclusive OR). a value with another value and then it you will get the original value back.

Nearly all (or all) encryption relies heavily on a few instructions on the chip: XOR, NOT, ROL and ROR. These are important because they are reversible. If you XOR (eXclusive OR) a value with another value and then repeat it you will get the original value back:

NOT (NOT 10) = 10

ROL and ROR (bitwise rotation) are a bit more subtle and are related to how data is represented in bytes. RoR and RoL mean Rotate Right and Rotate Left.

If you call ROL or ROL eight times on the same value you will get that value back. In any case, this is not a primer on encryption. Yull/Andromeda uses these but also extends them in a unique way:

void rotateArrayLeftAsBits(ref byte[] inB) { if (inB.Length == 0) return; counter++; //just like ROL but on a byte stream; treats byte stream as a bit stream byte[] outB = new byte[inB.Length]; byte[] bits = new byte[inB.Length]; int i; for (i = 0; i < inB.Length; i++) { if ((inB[i] & 0x80) == 0x80) bits[i] = 1; //save the high bit for later outB[i] = (byte)(inB[i] << 1); //SHIFT LEFT ONE } //now add in the saved high bits for (i = 1; i < inB.Length; i++) outB[i - 1] |= bits[i]; outB[inB.Length - 1] |= bits[0]; //copy back into inB System.Buffer.BlockCopy(outB, 0, inB, 0, inB.Length); } }

Instead of shifting the bits around a byte with ROL or ROR, wihch are bound by the byte boundaries, that is to say, the instructions only operate within a byte, rotateArrayLeftAsBits and RightAsBits do the same but on an array of bytes.

As mentioned above, Andromeda uses 54 encryption functions. Some are fairly atomic:

Decrypt46(ref byte[] inB, int k) { negateArray(ref inB); } Encrypt27(ref byte[] inB, int k) { for (int j = 0; j < inB.Length; j++) { inB[j] = (byte)(inB[j] ^ Key[j]); } }

Some are more complex, building on other functions:

Encrypt17(ref style='color:blue'>byte[] inB, int i) { inB[i] = (byte)((inB[i] << 7) | inB[i] >> 1); rotateArrayLeftAsBits(ref inB); inB[i] = (byte)(inB[i] ^ Key[i]); }

Other routines extract data from the read buffer (perhaps) in various ways. Some create a byte array based on the values from the Key, others from a "matrix" like extraction:

Figure 7 A visual representation of a matrix

Where the black rectangle represents a possible "matrix," that is, extracted data, which is clearly not completely sequential. It will start at some offset into the read buffer, run so long, then go to the next "line" (not a real line but some designated amount; this graphic only helps visualize the process.)

Some byte extractions take data from the inB parameter in a more disjointed fashion:

Figure 8 Non-sequential data extracted

The extracted data is submitted to the range of encryption functions since the array holding it is just another byte[] (an array of bytes).

Since all the encrypt functions are prototyped the same with the first parameter being a reference to a byte array they do not care if the byte array is the entire read array or some portion of it. The encryption functions are agnostic as to the source of the data, they can operate on these subarrays as if there were the entire read buffer, performing an encryption function (or several) and then returning the data back to the array the inB parameter points to.

So, for instance, the block matrix extract function can extract some data, pass that to the non-sequential extraction function, and pass the resulting data to a function which rotates the bits around the array, passsing that back up the

Some operate on parts of bytes:

     byte [] getBits(ref byte[] inB, byte Mask)
     {
          byte[] bitArray = new byte[inB.Length];
          for (int i = 0; i < inB.Length; i++)
               bitArray[i] = (byte)(inB[i] & Mask);
          return bitArray;
     }

This function extracts masked data from the input buffer (which could be the entire read buffer or another subset of it) and returns an array the length of inB which has been ANDed with the Mask value. The calling routine will submit this array to some of the other encryption functions. For instance this one:

     void Encrypt23(ref byte[] inB, int k)
        {
            byte[] values = getBits(ref inB, (byte)k);
            if (i < 130 || i >= 135) encrypt[i](ref values, k); //avoid Encrypt23..stackoverflow
            returnBits(ref values, ref inB, (byte)k);
        }

The function calls the masked extraction function getBits( ) and then passes the returned array to whatever encrypt function is at Key[k] as long as it is out of the range for Encrypt23. After that, :

     void Encrypt1(ref byte[] inB, int i)
        {
            if (i < inB.Length - 1)
            {
                reverseBytes(ref inB, Key[i], Key[i + 1]);
            }
        }
When that function returns, the encrypted array values is returned to the original array in the parameter.

All the reads are finished all the data arrays are zeroed out before they are deleted, including the key.

The entire Andromeda source code is found at the end of this document.

How To Brute Force Decrypt a Yull-Encrypted File

Now that you know more or less how Yull operates, here's the steps one would have to perform to decrypt a 1000 byte file encrypted by Yull.

Assume you have the file, my source code, and the key.

You know the file is read out of disk order. You have to start with a read size. Say, start at 30. Now you have 1000/30 or 34 reads. Now you have 34! (factorial; 1x2x3x4x5x6...x34) permutations to try. This is about 2.952 X 1038 or 2.952 followed by 38 zeroes. Next consider the rounds: There are 116 possible values over 34 reads. In other words, each read will be encrypted (or decrypted) a number of times between 4 and 120. This is 11634 which is way too large to write. Next you have to consider the dummy data amount. That can vary between 20 and 100. So this is all those numbers times 80. This is just for our first attempt, a read buffer size of 30. Next you have to try 31 bytes and so forth up to 400 (or more with the Console app, this case 1000, the file size). The number of combinations are (I think) are:

Sum for 30 to 1000 ((readSize)! * 116NoOfReads * 8080 * 90600) or something like this. And that's with the key.

The Yull class handles IO, setup, and managing the read and writing of data and submitting that data to the encryption object. The encryption object is the Andromeda class.

Before encryption (or decryption) begins in the Andromeda class, Yull instantiates the Andromeda object and instructs it to setup the jump table. Andromeda has 60 encryption procedures. Andromeda constructs an array of 256 delegates. (A C# delegate is like a C++ function pointer.) Its construction might look like this:

	for (int i = 0; i <= 255; i++)
	{
		if (i < 7) encrypt[i] = new Encrypt(Encrypt1);
		if (7 <= i && i < 14) encrypt[i] = new Encrypt(Encrypt33);
		if (14 <= i && i < 20) encrypt[i] = new Encrypt(Encrypt26);
		if (20 <= i && i < 25) encrypt[i] = new Encrypt(Encrypt21);
		if (25 <= i && i < 30) encrypt[i] = new Encrypt(Encrypt60);

and so forth. The decryption array is set up in a similar fashion.

All the Encrypt functions are prototyped liked this:

function (ref byte[] inB, int i) 


After the array is set up, the code starts:


	for (i = 0; i < Key.Length; i++)
	{
		encrypt[Key[i]](ref inB, i);
	}


The key length is the same as the read buffer. If the key is shorter than the read buffer, it is lengthened to the size of the read buffer; if it is longer, it is truncated. When this for loop finishes, Andromeda returns control back to the Yull object, which then continues the rounds loop.


	for (int i = 0; i < iRounds; i++) //iRounds is different with each read
	{
		androm.Key = new byte[iKeyLength];
		//androm is the Andromeda object
		System.Buffer.BlockCopy(Keys[outerRead][i], 0, androm.Key, 0, iKeyLength);
		androm.EncryptStart();
	}


And when the rounds loop ends Yull cycles back to read the next part of the file and process starts again.
Before starting the encryption loop, Yull create a multidimensional array of keys, sufficient for the entire encryption process:


	sKeys = new byte[Rounds.Length][][];
	int j = 0;
	for (int i = 0; i < sKeys.Length; i++)
	{
		sKeys[i] = new byte[Rounds[i]][];
		for (j = 0; j < Rounds[i]; j++)
		{
			encryptKey(ref sKey);
			sKeys[i][j] = new byte[iKeyLength];
			System.Buffer.BlockCopy(sKey, 0, sKeys[i][j], 0,
			iKeyLength);
		}
	}

Getting back to the encryption loop, when it completes, the data is written out and the process continues until the last block is read by Yull.

Nearly all (or all) encryption relies heavily on a few instructions on the chip: XOR, NOT, ROL and ROR. These are important because they are reversible. If you XOR (eXclusive OR). a value with another value and then repeat it you will get the original value back:

NOT (NOT 10) = 10

RoR and RoR (bitwise rotation) are a bit more subtle and are related to how data is represented in bytes. RoR and RoL mean Rotate Right and Rotate Left. (image above and below from Wikipedia)

If you call RoL or RoL eight times on the same value you will get that value back. In any case, this is not a primer on encryption. Yull/Andromeda uses these but also extends them in a unique way:


void rotateArrayLeftAsBits(ref byte[] inB)
{
    if (inB.Length == 0) return;
	try

      byte[] outB = new byte[inB.Length];
	  byte[] bits = new byte[inB.Length];
      int i;
      for (i = 0; i < inB.Length; i++)
      {
          if ((inB[i] & 0x80) == 0x80) bits[i] = 1;
          outB[i] = (byte)(inB[i] << 1);
      }
      for (i = 1; i < inB.Length; i++)
	      outB[i - 1] |= bits[i]; outB[inB.Length - 1] |= bits[0];
      System.Buffer.BlockCopy(outB, 0, inB, 0, inB.Length);
}

While the ROL or ROR functions are bound by the byte boundaries, rotateArrayLeftAsBits (and rotateArrayRightAsBits) do the same but on an array of bytes.

As mentioned above, Andromeda uses 60 encryption functions. Some are fairly atomic:


void Decrypt46(ref byte[] inB, int k)
{
   negateArray(ref inB);
}

void Encrypt27(ref byte[] inB, int k)
{
   for (int j = 0; j < inB.Length; j++)
   {
      inB[j] = (byte)(inB[j] ^ Key[j]);
   }
}

Some are more complex, building on other functions:

void Encrypt2(ref byte[] inB, int i)
{
   inB[i] = (byte)((inB[i] << 7) | inB[i] >> 1);
   rotateArrayLeftAsBits(ref inB);
   inB[i] = (byte)(inB[i] ^ Key[i]);
}

Other routines extract data from the read buffer (perhaps) in various ways. Some create a byte array based on the values from the Key, others from a "matrix" like extraction:

Figure 6 A visual representation of a matrix

Here the black rectangle represents a possible "matrix," that is, extracted data, which is clearly not completely sequential. It will start at some offset into the read buffer, run so long, then go to the next "line" (not a real line but some designated amount; this graphic only helps visualize the process.)

Some byte extractions take data from the inB parameter in a more disjointed fashion:

Figure 7 Non-sequential data extracted

The extracted data is submitted to the range of encryption functions since the array holding it is just another buffer, like any other.

Since all the encrypt functions are prototyped the same with the first parameter being a reference to a byte array they do not care if the byte array is the entire read array or some portion of it. The encryption functions are agnostic as to the source of the data, they can operate on these subarrays as if there were the entire read buffer, performing an encryption function (or several) and then returning the data back to the array the inBufer parameter points to.

Some operate on parts of bytes:

	byte[] getBits(ref byte[] inB, byte Mask)
	{
		byte[] bitArray = new byte[inB.Length];
		for (int i = 0; i < inB.Length; i++)
			bitArray[i] = (byte)(inB[i] & Mask);
		return bitArray;
	}

This function extracts masked data from the input buffer (which could be the entire read buffer or another subset of it) and returns an array the length of inB which has been anded with the Mask value. The calling routine will submit this array to some of the other encryption functions. For instance this one:


void Encrypt53(ref byte[] inB, int k) { byte[] values = getBits(ref inB, (byte)k); encrypt[k](ref values, k); returnBits(ref values, ref inB, (byte)k); }

Which uses the int value k, which is probably (though not necessarily) the value Key[k].

Then it calls the masked extraction function getBits( ) and then passes the returned array to whatever function is at encrypt[k], the kth position in the array of delegates. When that function returns, the encrypted array is returned to the original array in the parameter.

Everything on this website is Copyright © 2015-2016 Ronald Gans / Yull Encryption Company, LLC.
All Rights Reserved.