你的代码:
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
and之间E
,依此类推。这在现代英语中是一个晦涩的案例(我们不再真正使用这些字母),但在其他一些语言中可能更重要;例如,爱尔兰字母表在一些拟声词中使用,并且A,B,C,D,E,F,G,H,I,L,M,N,O,P,R,S,T,U
每个都出现在一些借词中,因此我们可以将爱尔兰字母表列为V
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
像vótaí这样的单词。
†虽然IJ
UCS 中有一个字符位于 U+0132,但这是为了与旧编码兼容。仍然IJ
用作标记字符 forIJ
将巧妙地处理IJ
使用过的数据和数据IJ
。
‡<em>在相当松散的意义上加密,因为这种加密方案在 19 世纪中叶被打破。