我正在用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();
}
}
}
}
答案 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,[Þ]
,一般就是一个不列出Ð
,但如果您的数据中有一个词开头,那么您可以将其放在D
和E
之间,依此类推。这是现代英语中一个不起眼的案例(我们不再使用这些字母),但在其他一些语言中可能更为重要;例如爱尔兰字母是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
开头的单词位于一组数据中,I
和L
之间的J
。这使像Vigenère这样的密码问题变得复杂,因为我们必须在计算中使用字母而不是严格的字母表部分,否则不加密像vótaí这样的单词的V
。
†虽然UCS中的U + 0132处有IJ
个字符,但这是为了与传统编码兼容。仍然使用IJ
作为IJ
的标记字符可以巧妙地处理IJ
和使用IJ
的数据。
‡以相当宽松的方式加密,因为这种加密方案在19世纪中叶被打破了。