A Simple Crypto Method

With todays crypto techniques and open libraries, encryption is quite accessible. But I wanted to speak of a simple crypto method, for those who don’t want to store stuff in plain text and want an easy implementation.

Please be warned, this method should not be used for anything more important than basic non-private data, since it can be broken by an average attacker.

Code of the console app is in C#, using .Net Core 3.1 and written on Visual Studio. It can be adapted to any environment since it doesn’t depend on any crypto library.

Let’s look at the main code first, Program.cs:

using System;namespace SimpleCrypto
{
class Program
{
[Serializable]
public struct DataStruct
{
public int iTest;
public float fTest;
public string sTest;
public bool bTest;
}
static void Main(string[] args)
{
// console will use UTF8 encoding
Console.OutputEncoding = new System.Text.UTF8Encoding();
// create data manager and data structure
DataEncryptor dataEncryptor = new DataEncryptor();
DataStruct dataStruct = new DataStruct();
// fill data
dataStruct.iTest = 12;
dataStruct.fTest = 34.56f;
dataStruct.sTest = "Hello öçşığü!";
dataStruct.bTest = true;
// encrypt & save data
dataEncryptor.DataSave(dataStruct);
// load & decrypt data
dataStruct = (DataStruct)dataEncryptor.DataLoad();
// show data
Console.WriteLine("* Encrypted & decrypted data:");
Console.WriteLine("iTest=" + dataStruct.iTest);
Console.WriteLine("fTest=" + dataStruct.fTest);
Console.WriteLine("sTest=" + dataStruct.sTest);
Console.WriteLine("bTest=" + dataStruct.bTest);
Console.WriteLine();
}
}
}

We use a data structure to hold our data together. Serializable attribute is to tell the compiler we will serialize this type. While filling the structure we include some non-ascii characters in string to test UTF8 behavior as well. And that’s also why we instruct the console to use UTF8 encoding.

See the program output:

Program Output
Program Output
Program Output

Program outputs the original data after encrypting, saving, loading and decrypting it. Also see the content of the save file, DataFile.bin:

Hex View of the Save File
Hex View of the Save File
Hex View of the Save File

Now let’s see how this method works 👀 Before posting the code of DataEncryptor class, I’ll explain the steps it performs. Plain object is serialized to binary, and then encrypted. Encrypted object is decrypted, and then deserialized to object. Serialization is necessary to convert the data object to a byte stream, and is performed by using BinaryFormatter class.

Encryption has below 2 steps:

  • XOR encipher
  • Rotate left by 3 bits

Decryption has below 2 steps:

  • Rotate right by 3 bits
  • XOR decipher

XOR stands for eXclusive OR and is a bitwise operation which takes 2 bits as input and gives 1 bit as output. It can be phrased as “are these bits different?”, 1 for yes and 0 for no. Below is the XOR truth table:

XOR Truth Table
XOR Truth Table
XOR Truth Table

XOR operation is used quite a lot in cryptology, because it provides 2-way operability. For example on the XOR truth table, if you XOR B and Out you get A, or if you XOR A and Out you get B. This reversibility ensures there will be no data loss. Think of A as plain text, B as encryption key, then Out will be cipher text. One with the encryption key (B) can use it on cipher text (Out) to get plain text (A), using XOR operation’s reversibility property.

Bitwise rotations are used just to spoil some possible byte-patterns, like ASCII encodings. Rotating 5, 6 or 7 bits are the same as rotating 1, 2 or 3 bits in the opposite direction. Rotating 4 bits is the same as swapping 2 halves of the byte. So 3 bits is chosen as amount of rotation.

Last but definitely not least, use a random generator to create the key. For this example, I used a randomly generated key of size 64 bytes. Longer keys will make patterns harder to find. 🧐

That’s it. Remember, this method is far from being strong, but just for basic non-plain storage and a bit of fun ^^

DataEncryptor.cs is below to be examined:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace SimpleCrypto
{
class DataEncryptor
{
const string sCryptKey = "hl23fzRwH1dJ$0EfkpV&kVM#4e?NLsV551j#XPLPm$%@HM!*PulZUH-&DgY-J-%K";
const string sDataFile = "DataFile.bin";
// save and load: object <-> binary <-> cryptedpublic void DataSave(object data)
{
using MemoryStream memo = new MemoryStream();
using FileStream file = File.Create(sDataFile);
BinaryFormatter bifo = new BinaryFormatter();
bifo.Serialize(memo, data);
Encrypt(memo, file);
}
public object DataLoad()
{
using MemoryStream memo = new MemoryStream();
using FileStream file = File.Open(sDataFile, FileMode.Open);
BinaryFormatter bifo = new BinaryFormatter();
Decrypt(file, memo);
return bifo.Deserialize(memo);
}
void Encrypt(Stream Source, Stream Target)
{
int s;
Source.Position = 0;
for (int c = 0; c < Source.Length; c++)
{
// read byte from source
s = Source.ReadByte();
// XOR encipher
s ^= sCryptKey[c % sCryptKey.Length];
// rotate left by 3 bits
s = (s << 3) | (s >> (8 - 3));
// write byte to target
Target.WriteByte((byte)s);
}
// reset the position within the stream for the caller
Target.Position = 0;
}
void Decrypt(Stream Source, Stream Target)
{
int s;
Source.Position = 0;
for (int c = 0; c < Source.Length; c++)
{
// read byte from source
s = Source.ReadByte();
// rotate right by 3 bits
s = (s >> 3) | (s << (8 - 3));
// XOR decipher
s ^= sCryptKey[c % sCryptKey.Length];
// write byte to target
Target.WriteByte((byte)s);
}
// reset the position within the stream for the caller
Target.Position = 0;
}
}
}