How Alkemi Encrypts Data

Andromeda: The Encryption Object

Brief Overview

Before encryption (or decryption) begins in the Andromeda class, Yull performs several actions. It determines how many reads there will be; it encrypts the key; determines the read buffer size and how much random data to add. Then it instantiates the Andromeda object and instructs it to setup the jump table. Andromeda has 62 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.

androm.Key = modifyKey(alg.ComputeHash(Encoding.UTF8.GetBytes(userKey)), ii.Value); byte[] modifyKey(byte[] userKey, int Value) { for (int i = 0; i < Value; i++) androm.rotateArrayLeft(ref userKey); return userKey; } makeBuffersTheSame(ref androm.inBuff, ref androm.Key); for (int k = 0; k < iRounds; k++) { 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 creates a new key:

androm.Key = modifyKey(alg.ComputeHash(Encoding.UTF8.GetBytes(userKey)), ii.Value);
byte[] modifyKey(byte[] userKey, int Value) { for (int i = 0; i < Value; i++) androm.rotateArrayLeft(ref userKey); return userKey; }

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, which 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 62 encryption functions. Some are fairly atomic:

Decrypt46(ref byte[] inB, int k) { negateArray(ref inB); } void negateArray(ref byte[] inB) { counter++; for (int i = 0; i < inB.Length; i++) inB[i] = (byte)~inB[i]; } 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 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 stack of calling routines.

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.

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

How To Brute Force Decrypt a Yull-Encrypted File

Now that you know more or less how Yull/Andromeda 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, but not 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. Good luck!

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