String encryption using keys
I know it's obvious but I really have to say this: outside a programming exercise do not write your own encryption function.



Let's now talk about code...

The first thing to do is to separate responsibilities. Encrypt() and Decrypt() methods (note PascalCase, you should follow C# naming guidelines) should do only one thing: encrypt and decrypt a string. To get user input and to provide an output is something else responsibility.

If you strip out all the console I/O your Encrypt() will be much simpler and you may note you can make it even simpler (to read):

static (IEnumerable&lt;int&gt; key, string encrypted) Encrypt(string text)
{
    var key = GenerateEncryptionKey(text.Length);
    return (key, new String(text.Zip(key, (v, k) =&gt; (char)(v + k))));
}

static IEnumerable&lt;int&gt; GenerateEncryptionKey(int length)
    =&gt; new Enumerable.Range(0, length).Select(x =&gt; _rnd.Next(1, 9));

static _rnd = new Random();


What I did:


I moved the generation of the encryption key to a separate function. In real world it will be a service you set separately. This gives you the ability to test the Encrypt() code with edge-cases using well-known and repeatable encryption keys (what will happen if v + k - which is Int32 - is higher than UInt16.MaxValue when casting back to char? Try and see). 
I used Enumerable.Zip() function instead of manually looping through the input + key. Whenever possible you should use available functions to perform a task because they make your code faster to write, easier to read and...less buggy (they already been tested!) Take the time to explore LINQ functions, when working with collections they provide many things you may need.
I moved everything not directly related to encryption out of this function. Now I can test encryption in isolation!


It's an exercise for the reader to do the same thing for the Decrypt() function and to move magic values (like 0 and 9) to appropriate const int fields. Also, as exercise, think that adding a random value to a character does not necessarily produce another valid character (Unicode surrogates) and the string resulting from concatenation may not be a valid or representable string (you'd better encode the IEnumerable&lt;int&gt; produced by encryption instead of simply creating a string out of it).



Your logic for looping in the Main() method is little bit weird. You have a loop, you exit the loop and you recursively (from the encryption/decryption functions...) call Main() again. I will skip the StackOverflowException because you probably won't ever use this app long enough but it makes the program flow really hard to follow.

There are many ways to implement this but let's start with something easy:

static void Main()
{
    while (true)
    {
        var actionToPerform = Prompt("(E)ncrypt, (D)ecrypt or (Q)uit?");
        if (actionToPerform.Equals("e", StringComparison.CurrentCultureIgnoreCase))
        {
            ReadTextAndEncrypt();
        }
        else if (actionToPerform.Equals("d", StringComparison.CurrentCultureIgnoreCase))
        {
            ReadTextAndDecrypt();
        }
        else if (actionToPerform.Equals("q", StringComparison.CurrentCultureIgnoreCase))
        {
            return;
        }
        else
        {
            Console.WriteLine("Unknown option. Try again.");
        }
    }
}


The same can be done using a dictionary (&lt;string, Action&gt;) but for now let's keep it simple. For string comparison I do not use ToLower() but the appropriate overload of String.Equals() where I can specify a StringComparison value. The schoolbook example about case insensitive comparison is the i in Turkish (== performs comparison using current culture) where "i" != "I".ToLower() (and similarly "i".ToUpper() != "I").

Note the Prompt() function which is also reused elsewhere:

static string Prompt(string prompt)
{
    Console.Write(prompt + ' ');
    return Console.ReadLine();
}


We can now write our ReadTextAndEncrypt() function putting the things together:

static void ReadTextAndEncrypt()
{
    (string key, string encrypted) result = Encrypt(Prompt("Enter the text to encrypt:"));

    var key = new String(result.key.Select(x =&gt; (char)('0' + x)));
    Console.WriteLine("Encryption key is: {0}, key);
    Console.WriteLine("Encrypted string is: {0}, result.encrypted);
}


That's all, now, as exercise, write ReadTextAndDecrypt() with the same logic. You may also add Console.Clear() and Console.ReadKey() as appropriate (also that conversion for result.key isn't something to be proud of...)



In this example where I wrote (T1 key, T2 encrypted) I used value tuples (a C# 7 feature), it's similar to Tuple&lt;T1, T2&gt; but with an attached compile-time name. Function may be well rewritten as:

static Tuple&lt;IEnumerable&lt;int&gt;, string&gt; Encrypt(text)
{
    var key = GenerateEncryptionKey(text.Length);
    var encryptedText = new String(text.Zip(key, (v, k) =&gt; (char)(v + k)));

    return Tuple.Create(key, encryptedText);
}