The "Cyraz" encryption algorithm was presented on SOPT community.
This algorithm makes a cypher using a private key given in byte[]
format with any arbitrary length. It will then "swap" bytes on the data depedending on the key value. Additionally it will append a byte to end of the cipher, that byte is a hash of the key. Therefore this algorithm only increases message size by 1 byte.
The original algorithm is only available on pastebin. This implementation only handles data in the format byte[]
. My job was to make it handle Streams
as well, so the algorithm would be capable of handling files that do not fit in memory. When encrypting the data with stream interface I needed it to work with the array interface as well. To make that work I added an additional footer (2 bytes) that tells me if the data has been encrypted using Streams or not.
We are aware this cypher is easly breakable so please consider this to be a "toy" cypher
public class Cryraz {
private byte[] key = new byte[0];
private const int bufferLength = 4096;
//footer to include when encrypting data as a stream
private static readonly byte[] streamFooter = new byte[]{255, 255};
/// <summary>
/// Represents the standart operation for encrypting or decrypting types.
/// </summary>
public enum Operation {
/// <summary>
/// Byte decrypting algorithin.
/// </summary>
Decrypt,
/// <summary>
/// Byte encrypting algorithin.
/// </summary>
Encrypt
}
/// <summary>
/// Creates a new <seealso cref="Cryraz"/> instance with specified key array.
/// </summary>
/// <param name="key">The new instance byte-based key.</param>
public Cryraz(byte[] key){
if(key == null){
throw new ArgumentNullException(nameof(key));
}
this.key = key;
}
/// <summary>
/// Updates the key. You cannot get it directly.
/// </summary>
public byte[] Key { set { key = value; } }
internal static byte performKeyHash(byte[] key) {
int x = 0;
foreach (byte b in key) {
x += b;
x *= 1 + (b % 2);
}
return (byte)(x / key.Length);
}
internal static byte computePos(byte[] inputArray, int pos) {
int length = inputArray.Length;
if (pos <= length - 1) return inputArray[pos];
int divisor = pos / length;
pos -= length * divisor;
return inputArray[pos];
}
/// <summary>
/// Validates if the file can be decrytped with the current key
/// </summary>
/// <param name="data">The encrypted data</param>
/// <returns>True if the file was encrypted with streams</returns>
private bool Verify(byte[] data){
if (data == null)
throw new ArgumentNullException("entryByteData", "Input data cannot be nothing.");
var length = data.Length;
var isStreamEncrypted = data.Length >= streamFooter.Length + 1;
for(int i = 1; i <= streamFooter.Length; ++i){
isStreamEncrypted &= data[length - i] == streamFooter[streamFooter.Length - i];
}
var checkSum = isStreamEncrypted ? data[length - streamFooter.Length - 1] : data[length - 1];
byte hash_x = performKeyHash(key);
if (hash_x != checkSum)
throw new System.Security.SecurityException("Invalid key for this data.");
return isStreamEncrypted;
}
private Stream DecryptData(Stream reader, bool createFile, bool verify){
var isStream = !verify;
if(verify){
reader.Position = reader.Length - streamFooter.Length - 1;
var footer = new byte[streamFooter.Length + 1];
reader.Read(footer, 0, footer.Length);
isStream = Verify(footer);
reader.Position = 0;
}
if(!isStream){
var memStream = new MemoryStream();
reader.CopyTo(memStream);
var data = memStream.ToArray();
return DecryptData(ref data, createFile);
}
var writer = createFile
? (Stream)File.Create(Path.GetTempFileName())
: (Stream)new MemoryStream();
var buffer = new byte[bufferLength];
int bytes;
while((bytes = reader.Read(buffer, 0, buffer.Length)) > 0){
Array.Resize(ref buffer, bytes);
performData(ref buffer, Operation.Decrypt, 0);
writer.Write(buffer, 0, bytes);
}
if(verify){
writer.SetLength(writer.Length - streamFooter.Length - 1);
}
writer.Flush();
writer.Position = 0;
return writer;
}
public Stream DecryptData(Stream stream, bool createFile = false) {
return DecryptData(stream, createFile, true);
}
/// <summary>
/// Decrypts an given single dimension byte-array with this class key.
/// </summary>
/// <param name="entryByteData">The input byte-array to decrypting.</param>
/// <returns>A temporary file path containing the decrypted data if createFile is true.
/// Null if createFile is false</returns>
public Stream DecryptData(ref byte[] data, bool createFile = false) {
var isStreamEncrypted = Verify(data);
if(!isStreamEncrypted){
performData(ref data, Operation.Decrypt, 0);
data[data.Length - 1] = 0;
Array.Resize(ref data, data.Length - 1);
var writer = createFile
? (Stream)File.Create(Path.GetTempFileName())
: (Stream)new MemoryStream(data);
return writer;
}else{
Array.Resize(ref data, data.Length - streamFooter.Length - 1);
var stream = DecryptData(new MemoryStream(data), createFile, false);
stream.Read(data, 0, data.Length);
stream.Position = 0;
return stream;
}
}
/// <summary>
/// Encrypts an given single dimension byte-array with this class key.
/// </summary>
/// <param name="entryByteData">The input byte-array to encrypting.</param>
public void EncryptData(ref byte[] entryByteData, bool createFile = false) {
if (entryByteData == null)
throw new ArgumentNullException("entryByteData", "Input data cannot be nothing.");
performData(ref entryByteData, Operation.Encrypt, 0);
Array.Resize(ref entryByteData, entryByteData.Length + 1);
byte hash_x = performKeyHash(key);
entryByteData[entryByteData.Length - 1] = hash_x;
}
public Stream EncryptData(Stream stream, bool createFile = false) {
if (stream == null)
throw new ArgumentNullException("stream", "Input data cannot be nothing.");
var writer = createFile
? (Stream)File.Create(Path.GetTempFileName())
: (Stream)new MemoryStream();
byte hash = performKeyHash(key);
var buffer = new byte[bufferLength];
int bytes;
while((bytes = stream.Read(buffer, 0, buffer.Length)) > 0){
Array.Resize(ref buffer, bytes);
performData(ref buffer, Operation.Encrypt, 0);
writer.Write(buffer, 0, bytes);
}
writer.WriteByte(hash);
writer.Write(streamFooter, 0, streamFooter.Length);
writer.Position = 0;
return writer;
}
internal void performData(ref byte[] entryByteData, Operation op, int keyOffset) {
for (int i = 0; i <= entryByteData.Length - 1; i++) {
int pos = computePos(key, keyOffset + i);
int a = entryByteData[i];
if (op == Operation.Encrypt) {
a += pos;
} else a -= pos;
entryByteData[i] = ((byte)a);
}
}
}
And a sample to test it. (Not for review)
public class Sample{
public static void Main(string[] args)
{
var cypher = new Cryraz(new byte[]{10, 11, 12});
var original = Enumerable.Range(0 , 1024 * 1024).
Select(i => (byte)(i % (byte.MaxValue + 1)))
.ToArray();
var data = new byte[original.Length];
Array.Copy(original, data, original.Length);
var sum = data.Sum(d => (long)d);
cypher.EncryptData(ref data);
cypher.DecryptData(ref data, false);
if(!Enumerable.SequenceEqual(original, data)){
Console.WriteLine("Can not encrypt and desencrypt arrays");
}else{
Console.WriteLine("Can encrypt and desencrypt arrays");
}
var stream = cypher.EncryptData(new MemoryStream(data)) as MemoryStream;
var memStream = cypher.DecryptData(stream) as MemoryStream;
data = memStream.ToArray();
if(!Enumerable.SequenceEqual(original, data)){
Console.WriteLine("Can not encrypt and desencrypt streams");
}else{
Console.WriteLine("Can encrypt and desencrypt streams");
}
memStream = cypher.EncryptData(new MemoryStream(data)) as MemoryStream;
data = memStream.ToArray();
cypher.DecryptData(ref data);
if(!Enumerable.SequenceEqual(original, data)){
Console.WriteLine("Can not encrypt streams and desencrypt arrays");
}else{
Console.WriteLine("Can encrypt streams and desencrypt arrays");
}
cypher.EncryptData(ref data);
memStream = cypher.DecryptData(new MemoryStream(data)) as MemoryStream;
data = memStream.ToArray();
if(!Enumerable.SequenceEqual(original, data)){
Console.WriteLine("Can not encrypt arrays and desencrypt streams");
}else{
Console.WriteLine("Can encrypt arrays and desencrypt streams");
}
}
}