我试图在C#中解密自动密钥密码,但我一直得到一个未处理的异常

时间:2016-05-10 22:59:51

标签: c#

我知道如何修复错误,但我不知道在哪里放置代码行以使其成功运行。我遇到问题的地方是我尝试创建密码字符的地方。

static string AutokeyDecrypt(string encryptmess, string pass)
    {
        //sets both secret message and password to arrays so they can be shifted
        char[] passkey = pass.ToCharArray();
        char[] decryptmess = encryptmess.ToCharArray();
        char newletter = ' ';

        for (int i = 0; i < decryptmess.Length; i++)
        {
            char passletter = (char)(passkey[i] + newletter); //This is the line on which I am having issues. I need to concatenate the key (passletter) with each newletter that I decode.
            char messletter = decryptmess[i];

            //shifts the letters in the message back to original using the first letter of the concatenated key
            int shift = passletter - ' '; // passletter - (space character)
            newletter = (char)(messletter - shift); // Add shift to message letter

            //loops through the ASCII table
            if (newletter > '~')
            {
                newletter = (char)(newletter - 94);
            }
            else if (newletter < ' ')
            {
                newletter = (char)(newletter + 94);
            }
            decryptmess[i] = newletter;
        }
        return new string(decryptmess);
    }

2 个答案:

答案 0 :(得分:0)

问题几乎可以肯定是您尝试访问超出数组末尾的passkey数组中的索引,这会导致IndexOutOfRange异常。

具体做法是:

char passletter = (char)(passkey[i] + newletter);

假设您的密码长度为10个字符。 passkey数组的有效索引是0到9.在消息的第11个字符(i == 10)中,您的代码尝试从索引10读取,这是无效的。

处理此问题的标准方法是使用模数运算符%将索引包装为有效值:

char passletter = (char)(passkey[i % passkey.Length] + newletter);

i = 10(对于10个字符的数组),这将返回数组中的第一个字符(索引0)。

除了索引错误之外,还有一些其他问题需要解决。

在代码顶部执行此操作:

char[] passkey = new char[pass.Length];
passkey = pass.ToCharArray();

这将创建一个空数组,然后立即将其替换为从pass字符串创建的 new 数组。初始数组是浪费的空间,将由垃圾收集器处理,这里不需要。

长话短说,用以下代码替换上面的代码(以及decryptmess之后的相同模式):

char[] passkey = pass.ToCharArray();

对于从C或C ++进入C#的人来说,这是一种非常常见的模式,你经常需要分配数组然后填充它们,大多数代码假定你将给它一个数组来填充而不是期望它分配和返回。您有可能想要这样做的原因,主要是我们不习惯在C#中使用的资源管理。

在C#中,通过ToArray()ToCharArray()等方法创建数组更为正常。这里可能会有一些性能影响,但就是这样。当您调用返回数组或其他对象的方法时,您无需预先分配数组或对象。

答案 1 :(得分:0)

另一个答案没有考虑AutoKey cypher,所以这里有一个更好的。

使用密码描述和示例here我认为您的代码显然不会正确实现解密。

加密算法使用由附加到密码的消息文本组成的密钥流,解密过程会在密钥流过程中重建密钥流。

我选择基于灵活的字母表来实现,如下所示:

char[] alphabet = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".ToCharArray();

这包括数字顺序中从32()到127(~)的所有ASCII可打印字符,但可以是该范围的任何顺序和/或有用子集。字母表是您的特定密码变体的第一个定义特征。从您的代码中可以看出,上面的字母表符合您的预期。

每个加密字符都是根据密钥流中的字符和位置上的消息计算出来的,方法是在数组中查找它们的索引,添加这些索引并在字母表中找到加密字符:

string Encrypt(string plain, string pass) 
{
    char[] message = plain.ToCharArray();
    char[] keystream = (pass + plain).ToCharArray();

    for (int i = 0; i < message.Length; i++)
    {
        int keyidx = Array.IndexOf(alphabet, keystream[i]);
        int msgidx = Array.IndexOf(alphabet, message[i]);

        message[i] = alphabet[(alphabet.Length + keyidx + msgidx) % alphabet.Length];
    }
    return new string(message);
}

注意:设置message[i]的行使用模运算将结果约束到数组边界。由于keyidx + msgidx可能都是负数且%运算符不包含此值(-1 % 10 == -1等),因此我添加了alphabet.Length以确保只有正数索引。

解密基本相同,只是解密循环的输出用于构建密钥流的内容。密钥流以相同的方式初始化 - 连接密码和消息 - 但随着解密的进行而更新。

string Decrypt(string encrypted, string pass)
{
    char[] message = encrypted.ToCharArray();
    char[] keystream = (pass + encrypted).ToCharArray();

    for (int i = 0; i < message.Length; i++)
    {
        int keyidx = Array.IndexOf(alphabet, keystream[i]);
        int msgidx = Array.IndexOf(alphabet, message[i]);

        message[i] = alphabet[(alphabet.Length + msgidx - keyidx) % alphabet.Length];
        keystream[i + pass.Length] = message[i];
    }
    return new string(message);
}

以这种方式构造keystream缓冲区可确保我们始终有足够的空间来更新,即使我们到达流程的最后并且不再需要最后几个字符。

当然,上面的代码假定您的输入表现良好,并且不包括字母表值之外的任何内容。密码中的任何值或与字母表中的某个值不匹配的消息都将被视为与字母表中的最后一个字符相同,因为Array.IndexOf对于任何失败的搜索都返回-1。因此,如果您的输入字符串包含任何Unicode或高位ASCII字符,它们将在输出中呈现为~