请实现仿射密码,这是一种起源于中东的古老加密系统。
仿射密码是一种单表代换密码。每个字符都会先映射到对应的数值,再用一个数学函数加密,然后转换回与新数值对应的字母。虽然所有单表代换密码都很弱,但仿射密码比埃特巴什密码强得多,因为它的密钥多得多。
加密函数为:
E(x) = (ai + b) mod m
其中:
i是字母的下标,从0到字母表长度减 1。m是字母表的长度。对于拉丁字母表,m为26。a和b是构成加密密钥的整数。要让自动解密成功,a和m这两个值必须_互质_(也就是_互素_),即它们的公因数只有1(更多信息见关于互质整数的维基百科条目)。如果a与m不互质,你的程序应当指出这是一个错误。否则,它应当用给定的密钥进行加密或解密。
在本练习中,数字是有效输入,但不会被加密。空格和标点符号则被排除在外。密文以固定长度的分组写出,组与组之间用空格分隔,传统的分组大小是5个字母。这样做是为了让人更难根据单词边界猜出加密后的文本。
解密函数为:
D(y) = (a^-1)(y - b) mod m
其中:
y是加密后字母的数值,即y = E(x)
a^-1是a mod m的模乘法逆元(MMI)a和m互质时才存在。a的 MMI 是满足以下条件的x:用m除ax,余数为1:
ax mod m = 1
关于如何求模乘法逆元以及它的含义,更多信息见相关维基百科条目。
a = 5、b = 7加密"test"得到"ybty"
a = 5、b = 7解密"ybty"得到"test"
a = 11、b = 7解密"ybty"得到"lqul"
a = 19、b = 13解密"kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx"得到"thequickbrownfoxjumpsoverthelazydog"
a = 18、b = 13加密"test"是一个错误,因为18和26不互质求a = 15的 MMI:
(15 * x) mod 26 = 1(15 * 7) mod 26 = 1,即105 mod 26 = 1
7是15 mod 26的 MMI有时,你需要抛出异常。这样做时,一定要附上有意义的错误信息,指明错误的来源。这能让代码更易读,也大大有助于调试。如果你已经知道错误来源是某种特定类型,可以选择抛出内置错误类型中的一种,但仍然要附上有意义的信息。
本练习要求你使用 raise 语句来“抛出”ValueError。只有既用raise抛出exception,又为它附上一条信息,测试才会通过。
要抛出带信息的ValueError,请把信息写成exception类型的实参:
raise ValueError("a and m must be coprime.")