How Alkemi Encrypts Data



Andromeda: The Encryption Object

Overview

A BRIEF WORD ON THE OUTPUT OF ALKEMI

Alkemi relies on a class called RNGCryptoServiceProvider from Microsoft to produce random data which, at various places, is added to the encrypted data. For instance before each section of the plaintext is submitted to the encryption class (Andromeda) a number of bytes of random data generated by RNGCryptoServiceProvider is added.

Here is how you can invoke it from Powershell:

$path = "T:" $bytes = 155476 [System.Security.Cryptography.RNGCryptoServiceProvider] $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider $rndbytes = New-Object byte[] $bytes $rng.GetBytes($rndbytes) [System.IO.File]::WriteAllBytes("$($path)\155476.bin", $rndbytes)

This will output a 155476 byte file named 155476.bin in the T:\ location. It will be random data of length 155476.

Here is the graph using HxD - Freeware Hex Editor and Disk Editor (see https://mh-nexus.de/en/) of this file showing the frequency of each byte value:


And here's the same sized file produced from Alkemi (and where the outputted data, which is in HEXASCII format is converted into binary values):


Of course there are some differences but by eye they seem very similar.

No one byte value dominates.

This leads me to conclude that the output of Alkemi seems close to random.

Of course it is not but I can't imagine how someone could separate the random from non-random data.

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 like 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!



Note about Randomness

Some Issues Determining How Random Is Enough

Here's the code to both of these applications, IsRandom.exe and RandNumGen.exe. The compiled apps are below.

I can generate a random data file of 2300 and it will pass the NIST tests. I can do it again and it will fail. My code (from Alkemi) does the same thing.

Download IsRandom.exe and Download RandNumGen.exe

Here's some code that generates random numbers using RNGCryptoServiceProvider:

using System; using System.Text; using System.Security.Cryptography; using System.IO; namespace RandNumGen { class Program { static void Main(string[] args) { if (args.Length != 3) { Console.WriteLine(@" Only 3 args: args[0]=size of random data as a positive integter; args[1] is file to output to; args[2] is ASCII or BINARY, like this: randnumgen 1000 bindata binary"); Environment.Exit(0); } if (args[2].ToUpper() != "ASCII" && args[2].ToUpper() != "BINARY") { Console.WriteLine("Only 3 args: args[0]=size of random data as a positive integter; args[1] is file to output to; args[2] is ASCII or BINARY."); Environment.Exit(0); } int size = 0; int.TryParse(args[0], out size); if (size==0 || size > 1000000 || Math.Abs(size) !=size) { Console.Write("args[0] must be a whole number greater than 0 and less than 1000000."); Environment.Exit(1); } string file = args[1]; byte[] random = new byte[size]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(random); if (args[2].ToUpper() == "ASCII") { StringBuilder hex = new StringBuilder(random.Length * 2); foreach (byte b in random) hex.AppendFormat("0x{0:x2}, ", b); try { using (TextWriter tx = File.CreateText(file)) { tx.Write(hex.ToString()); tx.Flush(); tx.Close(); } } catch (Exception ex) { Console.WriteLine("an exception occurred trying to create or write to the file " + file + ": " + ex.Message); Environment.Exit(1); } } else { try { using (BinaryWriter bw = new BinaryWriter( File.Open(file, FileMode.Create))) { bw.Write(random); bw.Flush(); bw.Close(); } } catch (Exception ex) { Console.WriteLine("an exception occurred trying to create or write to the file " + file + ": " + ex.Message); Environment.Exit(1); } } Console.WriteLine("DONE"); Environment.Exit(0); } } }



And here's the code for IsRandom.exe:

using System; using System.Text; using System.Security.Cryptography; using System.IO; namespace RandNumGen { class Program { static void Main(string[] args) { if (args.Length != 3) { Console.WriteLine(@" Only 3 args: args[0]=size of random data as a positive integter; args[1] is file to output to; args[2] is ASCII or BINARY, like this: randnumgen 1000 bindata binary"); Environment.Exit(0); } if (args[2].ToUpper() != "ASCII" && args[2].ToUpper() != "BINARY") { Console.WriteLine("Only 3 args: args[0]=size of random data as a positive integter; args[1] is file to output to; args[2] is ASCII or BINARY."); Environment.Exit(0); } int size = 0; int.TryParse(args[0], out size); if (size==0 || size > 1000000 || Math.Abs(size) !=size) { Console.Write("args[0] must be a whole number greater than 0 and less than 1000000."); Environment.Exit(1); } string file = args[1]; byte[] random = new byte[size]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(random); if (args[2].ToUpper() == "ASCII") { StringBuilder hex = new StringBuilder(random.Length * 2); foreach (byte b in random) hex.AppendFormat("0x{0:x2}, ", b); try { using (TextWriter tx = File.CreateText(file)) { tx.Write(hex.ToString()); tx.Flush(); tx.Close(); } } catch (Exception ex) { Console.WriteLine("an exception occurred trying to create or write to the file " + file + ": " + ex.Message); Environment.Exit(1); } } else { try { using (BinaryWriter bw = new BinaryWriter( File.Open(file, FileMode.Create))) { bw.Write(random); bw.Flush(); bw.Close(); } } catch (Exception ex) { Console.WriteLine("an exception occurred trying to create or write to the file " + file + ": " + ex.Message); Environment.Exit(1); } } Console.WriteLine("DONE"); Environment.Exit(0); } } }



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