Creating a more secure 'keyless' cipher using C#


Introduction

As the name suggests, a 'keyless' cipher is a cipher where the user does not need to provide a key to encrypt or decrypt a piece of text. The key, if there is one, is implicit from the algorithm used.

This means, of course, that you don't have to worry about how or where to store the key or key file for your encryption system. However, keyless systems are not usually secure due to the nature of the algorithms used.

ROT13

This is perhaps the best known keyless cipher. Every letter in the plain text is rotated by 13 places in the alphabet. When 'Z' is reached, the cipher wraps around to 'A' so it's really just a simple substitution cipher:

Text A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Code N O P Q R S T U V W X Y Z A B C D E F G H I J K L M

Only upper and lower case letters are rotated in this way. All other characters (include spaces) remain unaltered.

So C# Corner in ROT13 would be : P# Pbeare.

An interesting property of ROT13 is that you can apply the same method for both encryption and decryption.

Although it's used for various purposes on the Internet, ROT13 has no pretensions to being secure. If you know that something has been encrypted using ROT13, then there are several online utilities available which can recover the plain text and, even if you don't, then it's easily defeated using frequency analysis.

There is a variation known as ROT5 which can be used for encrypting the numerals 0 to 9 by rotating them 5 places to the right and wrapping around to 0 when 9 is reached. This can also be used in conjunction with ROT13 to encrypt alphanumeric text.

ROT47


ROT47 is a keyless cipher based on the same principles as ROT13 which encrypts all the printable ASCII characters (excluding spaces) between codepoints 33 (exclamation mark) and 126 (tilde), a range of 94 characters, by rotating them 47 places to the right and wrapping around to character 33 when character 126 is reached.
So C# Corner in ROT47 would be: rR r@C?6C.

As in the case of ROT13, you can apply the same method for both encryption and decryption and, although the encryption is more thorough, it can still be defeated using frequency analysis.

Also it can be more error-prone to write down the cipher text than ROT13 because of the use of symbols as well as letters and numerals.

ROT1035

You won't find this one in the text books as it's my own variation of ROT13.

It differs from it in that the consonants are rotated separately (by 10 places) from the vowels (by 3 places). The vowels for this purpose include 'Y'. The numerals are also rotated 5 places, as in ROT5.

So C# Corner in ROT1035 would be : P# Pafbuf.

As each word of the cipher text will include as many vowels as it did in the first place, the result is something that looks like it could be an obscure foreign language rather than encrypted text.

Also, if any one attempts to decrypt it using ROT13, the result is gibberish.

You can still apply the same method for both encryption and decryption but it is just as susceptible to defeat by frequency analysis as the other ROT variants.

ROTA

So is there anything we can do to make ROT1035 more secure?

The problem at the moment is that the various categories of character are always rotated by the same number of places. Suppose we were to make the number of places to rotate (or 'offsets') variable depending on the index of the character in the string and not just in a predictable way but in a more random fashion. Suppose also that the starting point within this sequence of offsets were made variable as well depending on the total number of characters in the string.

The result is an encryption scheme I've called ROTA which is much harder to crack than the other ROT variants though at the expense of requiring different methods for encryption and decryption.

C# Corner in ROTA would be : J# Bitgiv.

The encrypted text has similar properties to ROT1035 except that repeated words are (usually) encrypted differently which makes attacks using frequency analysis much more difficult.

The sequence of 'offsets' for letters was found by using the perfect pangram, junky qoph-flags vext crwd zimb (see http://en.wikipedia.org/wiki/List_of_pangrams ) and converting the letters to their consonant or vowel numbers in the alphabet which gives reasonably random results. The sequence for numerals uses the letters 'a' to 'j' from this pangram. Other pangrams or approaches could, of course, be used to generate these sequences.

Arguably, it would have been more secure to recombine the vowels and consonants into a single sequence (as in ROT13) so as not to offer a possible 'way in' by studying the vowels in single letter or other short words but I decided against this.

Source Code

I've coded a method for each of these variants (ROTA uses a static class with two methods) so we can compare the output. The following program is also available as a download:

using System;
 
class Program
{
    static void Main()
    {
        string[] s = new string[2];
        s[0] = "The Quick Brown Fox Jumps Over The Lazy Dog 2012";
// Dog
        s[1] = "The Quick Brown Fox Jumps Over The Lazy Dogs 2012"; // now Dogs 
       
        for (int i = 0; i < 2; i++)
        {
            Console.WriteLine("Using ROT13\n");
            string t = ROT13(s[i]);
            Console.WriteLine(t);
            Console.WriteLine(ROT13(t));
 
            Console.WriteLine("\nUsing ROT47\n");
            string u = ROT47(s[i]);

            Console.WriteLine(u);
            Console.WriteLine(ROT47(u));
 
            Console.WriteLine("\nUsing ROT1035\n");
            string v = ROT1035(s[i]);
            Console.WriteLine(v);
            Console.WriteLine(ROT1035(v));
 
            Console.WriteLine("\nUsing ROTA\n");
            string w = ROTA.Encrypt(s[i]);
            Console.WriteLine(w);
            Console.WriteLine(ROTA.Decrypt(w)); 
            Console.WriteLine("\n" + new string('-', s[i].Length) + "\n");
        } 
        Console.ReadKey();            
    }
 
    static string ROT13(string s)
    {
        if (String.IsNullOrEmpty(s)) return s;
        char[] ca = new char[s.Length]; 
        for (int i = 0; i < s.Length; i++)
        {
            char c = s[i]; 
            if (c >= 97 && c <= 122)
            {
                int j = c + 13;
                if (j > 122) j -= 26;
                ca[i] = (char)j;
            }
            else if (c >= 65 && c <= 90)
            {
                int j = c + 13;
                if (j > 90) j -= 26;
                ca[i] = (char)j;
            }
           
else
            {
                ca[i] = c;
            }
        } 
        return new string(ca);
    }
 
    static string ROT47(string s)
    {
        if (String.IsNullOrEmpty(s)) return s;
        char[] ca = new char[s.Length]; 

        for (int i = 0; i < s.Length; i++)
        {
            char c = s[i];
 
            if (c >= 33 && c <= 126)
            {
                int j = c + 47;
                if (j > 126) j -= 94;
                ca[i] = (char)j;
            }
           
else
            {
                ca[i] = c;
            }
        } 
        return new string(ca);
    }
 
    static string ROT1035(string s)
    {
        if (String.IsNullOrEmpty(s)) return s;
        string consonants = "bcdfghjklmnpqrstvwxz";
        string vowels = "aeiouy"
        char[] ca = new char[s.Length];
 
        for (int i = 0; i < s.Length; i++)
        {
            char c = s[i]; 
            int j;
 
            if ((j = vowels.IndexOf(c)) > -1)
            {
                j += 3;
                if (j > 5) j -= 6;
                ca[i] = vowels[j];
            }
            else if ((j = consonants.IndexOf(c)) > -1)
            {
                j += 10;

                if (j > 19) j -= 20;
                ca[i] = consonants[j];
            }
            else if ((j = vowels.ToUpper().IndexOf(c)) > -1)
            {
                j += 3;
                if (j > 5) j -= 6;
                ca[i] = vowels.ToUpper()[j];
            }
            else if ((j = consonants.ToUpper().IndexOf(c)) > -1)
            {
                j += 10;
                if (j > 19) j -= 20;
                ca[i] = consonants.ToUpper()[j];
            }
            else if (c >= 48 && c <= 57)
            {
                j = c + 5;
                if (j > 57) j -= 10;
                ca[i] = (char)j;
            }
           
else
            {
                ca[i] = c;
            }
        }
 
        return new string(ca);
    }
}
 
static class ROTA
{
    static int[] keyC = { 7, 11, 8, 13, 12, 6, 4, 9, 5, 15, 17, 19, 16, 2, 14, 18, 3, 20, 10, 1 };
    static int[] keyV = { 5, 6, 4, 1, 2, 3 };
    static int[] keyN = { 10, 8, 6, 1, 7, 5, 3, 4, 9, 2 };
    static string consonants = "bcdfghjklmnpqrstvwxz";
    static string vowels = "aeiouy";
 
    public static string Encrypt(string s)
    {
        if (String.IsNullOrEmpty(s)) return s;
 
        int lenC = (s.Length - 1) % 20;
        int lenV = (s.Length - 1) % 6;
        int lenN = (s.Length - 1) % 10;
 
        char[] ca = new char[s.Length];
 
        for (int i = 0; i < s.Length; i++)
        {
            char c = s[i];
 
            int j;
 
            if ((j = vowels.IndexOf(c)) > -1)
            {
                j += keyV[(i + lenV) % 6];
                if (j > 5) j -= 6;
                ca[i] = vowels[j];
            }
            else if ((j = consonants.IndexOf(c)) > -1)
            {
                j += keyC[(i + lenC) % 20];
                if (j > 19) j -= 20;
                ca[i] = consonants[j];
            }
            else if ((j = vowels.ToUpper().IndexOf(c)) > -1)
            {
                j += keyV[(i + lenV) % 6];
                if (j > 5) j -= 6;
                ca[i] = vowels.ToUpper()[j];
            }
            else if ((j = consonants.ToUpper().IndexOf(c)) > -1)
            {
                j += keyC[(i + lenC) % 20];
                if (j > 19) j -= 20;
                ca[i] = consonants.ToUpper()[j];
            }
            else if (c >= 48 && c <= 57)
            {
                j = c + keyN[(i + lenN) % 10];
                if (j > 57) j -= 10;
                ca[i] = (char)j;
            }
           
else
            {
                ca[i] = c;
            }
        }
 
        return new string(ca);
    }
 
    public static string Decrypt(string s)
    {
        if (String.IsNullOrEmpty(s)) return s;
 
        int lenC = (s.Length - 1) % 20;
        int lenV = (s.Length - 1) % 6;
        int lenN = (s.Length - 1) % 10;
 
        char[] ca = new char[s.Length];
 
        for (int i = 0; i < s.Length; i++)
        {
            char c = s[i]; 
            int j; 
            if ((j = vowels.IndexOf(c)) > -1)
            {
                j -= keyV[(i + lenV) % 6];
                if (j < 0) j += 6;
                ca[i] = vowels[j];
            }
            else if ((j = consonants.IndexOf(c)) > -1)
            {
                j -= keyC[(i + lenC) % 20];
                if (j < 0) j += 20;
                ca[i] = consonants[j];
            }
            else if ((j = vowels.ToUpper().IndexOf(c)) > -1)
            {
                j -= keyV[(i + lenV) % 6];
                if (j < 0) j += 6;
                ca[i] = vowels.ToUpper()[j];
            }
            else if ((j = consonants.ToUpper().IndexOf(c)) > -1)
            {
                j -= keyC[(i + lenC) % 20];
                if (j < 0) j += 20;
                ca[i] = consonants.ToUpper()[j];
            }
            else if (c >= 48 && c <= 57)
            {
                j = c - keyN[(i + lenN) % 10];
                if (j < 48) j += 10;
                ca[i] = (char)j;
            }
           
else
            {
                ca[i] = c;
            }
        } 
        return new string(ca);
    }
}


Results

The following is a screen-shot of the output from this program:

keyless.gif

Notice that the second "the" is encoded differently in ROTA but not in the other variants.

Notice also that changing "Dog" to "Dogs" only changes the encrypted version of that particular word when using the first three variants. However, when using ROTA it changes the whole sentence!

Conclusion

It's therefore clear that ROTA is much more secure than the other ROT variants.

However, it's still not cryptographically strong by modern standards - an expert would have no trouble deciphering it.

It could be made stronger by adding other refinements such as changing the spacing between words but I didn't think that this would be worthwhile.

The important thing is to produce a keyless cipher which is quick and easy to use and could be used (for example) to encrypt string literals or small files and so protect them from casual inspection. I believe that ROTA already achieves this limited aim.


Similar Articles