Vigenere Cipher c#with“ñ”

时间:2014-10-22 21:55:30

标签: c# vigenere

我正在用c#编写一个Vigenere密码程序,但是我遇到了一个问题我没有“Ñ”我希望加密,就像在Vigenere密码中发生的那样但是用“Ñ”如何添加字母“Ñ”到这个代码? 这样,key和s都保持这种方式: a = 0 b = 1 ... n =13ñ= 14 ... z = 26 在一个地方飞行之后

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
class Program
{
    static void VigenereEncrypt(ref StringBuilder s, string key)
    {
        for (int i = 0; i < s.Length; i++) s[i] = Char.ToUpper(s[i]);
        key = key.ToUpper();
        int j = 0;
        for (int i = 0; i < s.Length; i++)
        {
            if (Char.IsLetter(s[i]))
            {
                s[i] = (char)(s[i] + key[j] - 'A');
                if (s[i] > 'Z') s[i] = (char)(s[i] - 'Z' + 'A' - 1);
            }
            j = j + 1 == key.Length ? 0 : j + 1;
        }
    }

    static void VigenereDecrypt(ref StringBuilder s, string key)
    {
        for (int i = 0; i < s.Length; i++) s[i] = Char.ToUpper(s[i]);
        key = key.ToUpper();
        int j = 0;
        for (int i = 0; i < s.Length; i++)
        {
            if (Char.IsLetter(s[i]))
            {
                s[i] = s[i] >= key[j] ?
                          (char)(s[i] - key[j] + 'A') :
                          (char)('A' + ('Z' - key[j] + s[i] - 'A') + 1);
            }
            j = j + 1 == key.Length ? 0 : j + 1;
        }
    }

    public static void Main()
    {
        while (true)
        {
            StringBuilder s = new StringBuilder(Console.ReadLine());
            string key = Console.ReadLine();
            VigenereEncrypt(ref s, key);
            Console.WriteLine(s);
            VigenereDecrypt(ref s, key);
            Console.WriteLine(s);
            Console.ReadLine();
        }
    }

 }
}

1 个答案:

答案 0 :(得分:7)

您的代码:

if (Char.IsLetter(s[i]))
{
        s[i] = (char)(s[i] + key[j] - 'A');
        if (s[i] > 'Z') s[i] = (char)(s[i] - 'Z' + 'A' - 1);
}

取决于U + 0041到U + 005A的字母碰巧匹配某些语言的字母,例如英语*。 (如果测试依赖于此而不仅仅是检查它是一个字母,那么你应该保持Ñ不变而不是出错。还有一些其他语言的字母表是连续的,并且在UCS中按顺序排列,但大多数语言都没有。

因此,您需要定义自己的字母表。对于大多数用途来说,字符串是一种简单的方法。

string spanishAlphabet = "ABCDEFGHIJKLMNÑOPQRSTUVWXYZ";
string englishAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string irishAlphabet = "ABCDEFGHILMNOPRSTU";
string danishAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ";
string norwegianAlphabet = danishAlphabet;

然后,您可以使用您关注的字母表,而不是依赖于字母和UCS之间的巧合:

static void VigenereEncrypt(StringBuilder s, string key, string alphabet)
{
  for (int i = 0; i < s.Length; i++) s[i] = Char.ToUpper(s[i]);
  key = key.ToUpper();
  int j = 0;
  for (int i = 0; i < s.Length; i++)
  {
    if(alphabet.Contains(s[i]))
      s[i] = alphabet[(alphabet.IndexOf(s[i]) + alphabet.IndexOf(key[j])) % alphabet.Length];
    j = (j + 1) % key.Length;
  }
}

static void VigenereDecrypt(StringBuilder s, string key, string alphabet)
{
  for (int i = 0; i < s.Length; i++) s[i] = Char.ToUpper(s[i]);
  key = key.ToUpper();
  int j = 0;
  for (int i = 0; i < s.Length; i++)
  {
    if(alphabet.Contains(s[i]))
    {
      s[i] = alphabet[(alphabet.IndexOf(s[i]) - alphabet.IndexOf(key[j]) + alphabet.Length) % alphabet.Length];
      j = (j + 1) % key.Length;
    }
  }
}

(我假设密钥总是仅由相关的字母表组成,更强大的解决方案不会做出这样的假设,但是在这种情况下,有一些不同的方法可以做什么,所以没有一种正确的方法可以解决这个问题,我忽略了这个问题。)

我还取出了ref关键字,因为StringBuilder没有像该签名所暗示的那样更改为其他参考,而是就地变异。更惯用的方法是接收字符串并返回另一个字符串:

static string VigenereEncrypt(string s, string key, string alphabet)
{
  s = s.ToUpper();
  key = key.ToUpper();
  int j = 0;
  StringBuilder ret = new StringBuilder(s.Length);
  for (int i = 0; i < s.Length; i++)
  {
    if(alphabet.Contains(s[i]))
      ret.Append(alphabet[(alphabet.IndexOf(s[i]) + alphabet.IndexOf(key[j])) % alphabet.Length]);
    else
      ret.Append(s[i]);
    j = (j + 1) % key.Length;
  }
  return ret.ToString();
}

static string VigenereDecrypt(string s, string key, string alphabet)
{
  s = s.ToUpper();
  key = key.ToUpper();
  int j = 0;
  StringBuilder ret = new StringBuilder(s.Length);
  for (int i = 0; i < s.Length; i++)
  {
    if(alphabet.Contains(s[i]))
      ret.Append(alphabet[(alphabet.IndexOf(s[i]) - alphabet.IndexOf(key[j]) + alphabet.Length) % alphabet.Length]);
    else
      ret.Append(s[i]);
    j = (j + 1) % key.Length;
  }
  return ret.ToString();
}

如果要处理Unicode不将单个字符视为字母的字符串,例如荷兰语IJ†这变得更加复杂。一种可能性是对这样的序列使用标记字符,然​​后在加密‡之前首先用它替换序列的每个情况,然后如果标记出现在输出中则再次替换。人们必须确保标记字符没有出现在输入中,这会使像U + FFFE这样的非字符在这里有用。

不被视为字母表中不同部分的变音符号(如Ñ为西班牙文),是另一个复杂因素。在像Vigenère这样的密码实际使用的那些日子里,通常只是剥离变音符号并处理输出不应该具有变音符号这一事实。一种简单的方法是使用如下方法:

public static IEnumerable<char> RemoveDiacriticsEnum(string src, string alphabet)
{
  foreach(char c in src.Normalize(NormalizationForm.FormD))
    if(alphabet.Contains(c))  // Catch e.g. Ñ in Spanish, considered letter in own right
      yield return c;
    else
      switch(CharUnicodeInfo.GetUnicodeCategory(c))
      {
        case UnicodeCategory.NonSpacingMark:
        case UnicodeCategory.SpacingCombiningMark:
        case UnicodeCategory.EnclosingMark:
          //do nothing
          break;
        default:
          yield return customFolding(c);
          break;
      }
}

然后使用一个执行foreach(char c in RemoveDiacriticsEnum(s, alphabet))并使用c的循环,其中上面的代码使用s[i]。这不会涵盖所有情况,请参阅https://stackoverflow.com/a/3769995/400547了解一些可能的并发症。

或者,可以在字母表中包括常见的重音组合:

string spanishAlphabet = "AÁBCDEÉFGHIÍJKLMNÑOÓPQRSTUÚÜVWXYZ";

*严格来说,如果使用了其他一些字符,特别是Ð,Ȝ和Þ的位置有各种各样的惯例,所以现代英文字母的一个版本是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,[Þ],一般就是一个不列出Ð,但如果您的数据中有一个词开头,那么您可以将其放在DE之间,依此类推。这是现代英语中一个不起眼的案例(我们不再使用这些字母),但在其他一些语言中可能更为重要;例如爱尔兰字母是A,B,C,D,E,F,G,H,I,L,M,N,O,P,R,S,T,U,但V用于一些onomatopœic单词,J,K,Q,V,W,X,Y,Z分别在一些借词中找到,因此我们可以将爱尔兰字母列为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] ,通常不会在括号中列出字母,而是定位例如如果以J开头的单词位于一组数据中,IL之间的J。这使像Vigenère这样的密码问题变得复杂,因为我们必须在计算中使用字母而不是严格的字母表部分,否则不加密像vótaí这样的单词的V

†虽然UCS中的U + 0132处有IJ个字符,但这是为了与传统编码兼容。仍然使用IJ作为IJ的标记字符可以巧妙地处理IJ和使用IJ的数据。

以相当宽松的方式加密,因为这种加密方案在19世纪中叶被打破了。