古典密码-维吉尼亚密码

维吉尼亚密码是一种多密钥密码,其加密方式与凯撒密码类似,不同的是维吉尼亚密码并不使用单独的一个密钥加密明文的所有字母,而是对明文的每一个字母都使用不同的密钥,因此吉尼亚密码又被称为多表替换密码。

章节:

  • 维吉尼亚加解密原理
  • 维吉尼亚破解原理:Kasiski检测、频率分析、频率匹配程度、破解流程
  • 维吉尼亚密码加解密代码+应用
  • 维吉尼亚密码破解代码+应用

1 维吉尼亚密码加解密原理

维吉尼亚加密方法最早被记录于吉奥万·巴蒂斯塔·贝拉索于1553年发表的书籍《吉奥万·巴蒂斯塔·贝拉索先生的密码》中,但是在19世纪被误认为法国外交官布莱斯·德·维吉尼亚(Blaise De Vigenère)所创造,因此被称为“维吉尼亚密码”。

在凯撒密码中,明文中每个字母都会根据字母表进行相同的偏移来进行加密。例如偏移量为3时,凯撒密码密文表如表 1所示,在凯撒加密中字母“A”将被替换为D,字母“B”将被替换为“E”,单词“LOVE”将被替换为“ORYH”。

表1 凯撒密码加密

维吉尼亚密码则是一系列偏移量不同的凯撒密码组成密码字母表的加密算法,即其密钥是一系列字母的组合。

以26个字母为例,为了生成密码,首先使用表格法生成维吉尼亚表格如所图 1示,该表格中每一行中字母的顺序都由前一行向左移动一个距离得到,或者说当前行所属字母与第一个字母的距离作为偏移量进行偏移从而得到当前行中的字母顺序。

图1 维吉尼亚表格

举例说明,26个英语字母的正常顺序如下:

图 1中“D”行的字母顺序如下,该行是由原字母顺序向左移动“D”-“A”= 3个偏移量得到。

以此类推,26个英语字母存在26个偏移位置(包括0),因此表格行数为26。

生成维吉尼亚表格后学习维吉尼亚的加密方法。

  • 现有明文为:thisneedstobeencrypted
  • 现有密钥为:lemon

    首先对密钥进行周期扩展,扩展长度为明文长度,此时明文和密钥的对应关系如下:

明文中第一个字母是“t”,其对应的密钥字母为“l”,那么就使用“l”行的字母序列(如图 2所示蓝色方框)对字母进行加密;待加密的字母为“t”,对应“l”行中的字母为“e”,因此明文的第一个字母“t”加密后为“e”。(也可以理解为密钥“l”距离“a”的偏移量为11,因此明文字母“t”需要向右偏移11)

图2 维吉尼亚加密

明文中第二个字母是“h”,其对应的密钥字母为“e”,那么就使用“e”行的字母序列(如图 3所示蓝色方框)对字母进行加密;待加密的字母为“h”,对应“e”行中的字母为“l”,因此明文的第一个字母“h”加密后为“l”。

图3 维吉尼亚加密

按照上述方式使用维吉尼亚加密方法将所有明文中字母进行加密可以得到:

  • 现有明文为:thisneedstobeencrypted
  • 现有密钥为:lemon
  • 加密后的密文为:elugapipggzfqsanvkdgph

维吉尼亚的解密过程与加密过程相反,即在密文字母对应的密钥行中找到当前密文字母对应的列字母,该列字母即为明文。例如,第一个密文为“e”,对应的密钥为“l”,在“l”行(密钥行)中找到密文字母“e”,该字母处于“t”列,因此解密后为字母“t”。

对维吉尼亚的加解密方式进行总结。维吉尼亚加密的方法就是将明文中的每个字母按照对应的密钥字母进行偏移,每个密钥字母对应的偏移量为该字母到第一个原字母的距离。用字母在字母表中位置替代字母,即用数字0~25替代A~Z, 维吉尼亚密码的加密过程可以进行如下描述:

$$
c_{i}=\left(m_{i}+K_{i}\right) \bmod 26
$$
公式1

其中c为密文,长度为d, ci为密文中单个字母;k为密钥,将其扩展和c同样长度后为k,k为加密解密时使用到的工作密钥,ki为ci对应的单个密钥字母(或者说密钥字母对应的偏移量),即k=k0k1…ki,满足ki=ki mod d 。

解密过程如下:

$$
m_{i}=\left(c_{i}-K_{i}\right) \bmod 26
$$
公式2

2 维吉尼亚密码破解原理

过维吉尼亚加密原理可知,密钥越长则维吉尼亚密码对暴力破解的抗性就越强。例如:密钥为“lemon”时,长度为5位,通过计算机的暴力破解只需要不超过26^5 = 11881376次尝试便可以爆破出密钥。然而每当密钥增加一位时,维吉尼亚密码的可能密钥数就会乘以26,当密钥数够长时,通过计算机也难以对其进行暴力破解。

查尔斯·巴贝奇是最早破译维吉尼亚密码的人,但是他未将破译的方法公开。直到1863年,弗里德里希·卡西斯基于发表了完整的维吉尼亚密码的破译方法,称为卡西斯基检测(Kasiski examination)。在对巴贝奇生前笔记的研究中发现,早在1846年巴贝奇就使用了这一方法,与后来卡西斯基发表的方法相同。(所以说, 弗里德里希·卡西斯发表前,自己偷偷用了多久呢_(:зゝ∠)_ 破解了当时世界上所有人都觉得不可能被破解的密码,谁都会自己偷偷用着牟利吧)

2.1 Kasiski检测确定密钥长度

Kasiski检测是一个用于确定维吉尼亚密钥长度的过程。只要得到了密钥长度,就可以通过频率分析来逐个破解子密钥。

kasiski检测通过如下两个步骤来确定密钥的长度:

  • 找到重复序列
  • 求得间隔因子

(1)找到重复序列

kasiski检测第一步为找到密文中至少由3个字母组成的重复序列。因为类似于“the”这样的常用单词很可能被同样的密钥字母进行加密,从而产生在密文中重复出现的密文字符。

例如,使用长度为15的密钥“FNJFBUHLQCOWRTT”,现有维吉尼亚加密流程如图 4所示,明文“THE MORE YOU LEARN THE CLEVERER YOU ARE”经过加密后得到密文“YUN RPLL JEW ZARKG YUN HMYCPHGF UFN TWR”。

图4 维吉尼亚加密

从上述明文和密文中可以发现,单词“THE”被密钥中的“FNJ”部分连续加密了两次,对应的密文都为“YUN”。密文中两个“YUN”之间的距离称之为间隔,此处为15。思考,通过该间隔我们可以发现什么?在这个例子中该间隔距离与密钥长度一致。

继续举例,使用长度为3的密钥“PJT”,现有维吉尼亚加密流程如图 5所示,明文“THE MORE YOU LEARN THE CLEVERER YOU ARE”经过加密后得到密文“YUN RPLL JEW ZARKG YUN HMYCPHGF UFN TWR”。

图5 维吉尼亚加密

从上述明文和密文中可以发现,单词“THE”被密钥“PJT”连续加密了两次,对应的密文都为“IQX”,密文中两个“YUN”之间的距离为15。

通过长度为3的密钥加密,单词“THE”被加密后密文“IQX”的间隔长度为15,虽然不等于密钥长度,但是15刚好密钥长度3的5倍。

通过如上总结我们可以发现,当密文中出现重复序列密文时,那么它们之间的间隔很有可能是密钥长度的整数倍,因此密钥长度很可能是间隔距离的某个因子。

(2)求得间隔因子

对于一段较长的密文,通常会包含多个重复的序列,即包含多个不同的间隔,kasiski检测的第二步就是对这些间隔进行精简,尽可能的缩小密钥长度选取范围。

因为每个间隔距离都有可能是密钥长度的整数倍,所以可以计算出每个间隔的因子,最后选择出现频率靠前的因子作为密钥的待选长度。 例如,存在密文如下:

上述密文中存在存在重复密文序列“YBN”、“VAR”、“AZU”,重复序列间的间隔如下:

YBN:8

VRA:8、24、32

AZU:48

即上述密文中存在的间隔为:8、24、32、48。计算这些间隔的因子,如表 2所示。

表2 间隔的因子

通过表 2可知,存在如下因子:2、2、2、2、4、4、4、4、6、6、8、8、8、8、12、12、16、24、24、48。其中频率最高的因子为2、4、8,因此它们最有可能为密钥的长度。

2.2 频率分析

通过kasiski检测确定密钥长度后,就可以通过频率分析来逐个破解子密钥。

频率分析是指研究字母或者字母组合在文本出现的频率。在英语中,字母E出现的频率很高,而X则出现得较少,类似地,ST、NG、TH,以及QU等双字母组合出现的频率非常高,NZ、QJ组合则极少。英语26个字母出现的频率如图 6所示[1]

图6 排序后的英文字母频率

对于用英语书写的任意一段文本,都大致的满足上图中的特征字母分布。

思考,对于一段满足上述特征字母分布的明文(即明文中字母频率排序为e,t,a,o,i,n…),若使用凯撒密码进行加密,密钥为1,加密后的密文中的字母是否还符合上述特征?

答案是既“符合”又“不符合”。“符合”是指密文中的各字母的频率条状图仍和明文的频率条状图一致,“不符合”则在于频率对应的字母变了。例如明文中字母e最多,通过密钥为1的凯撒加密后,所有字母e变为了f,因此在密文中字母f最多,并且f的数量和明文中的e的数量仍是一样的。

通过这种分析,可以很快的破解凯撒密码。因为凯撒加密只是将字母进行位移替换,所以并没有扰乱文本中字母的频率信息。因此知道密文中的字母频率排序为f、u、b、p、j、o…,并且知道密文满足特征字母分布的理想情况下,那么密文的频率排序字母频率排序f、u、b、p、j、o…一定与e、t、a、o、i、n…对应,则可以很轻松的破译出密钥为1。

上述为“明文的字母频率与英语的字母频率完全一致”的理想情况下,而通常情况下明文的字母频率并不完全与英语的字母频率一致,那么我们可以先对子密文进行暴力破解,即分别使用26个字母作为密钥对当前子密文进行解密,然后计算解密后字串的字母频率,该字母频率与英文字母频率越匹配,那么当前用于解密的字母越可能是正确的子密钥。

继续思考,如何使用频率分析的方法对维吉尼亚密码进行破译?

对于维吉尼亚加密的密文,不能直接使用频率的方法进行分析,这是因为加密时经过循环使用密钥,完整密文中的字母频率被扰乱。但是在已知密钥长度的情况下,我们可以提取密文中所有由相同子密钥进行加密的字母组成子密文,提取后的子密文和对应的子密钥本质上就是凯撒密码,再使用频率分析的方法即可逐个破译子密钥。

例如现有密文“DAGAVDSAFDS…”,只知道密钥长度为3,并不知道具体密钥字母,如图 7所示。

图7 示例

在上述情况下,我们可以确定所有红色部分的密文字母都由第一个子密钥进行加密,蓝色部分的密文字母由第二个子密钥进行加密,绿色部分的密文字母由第三个子密钥进行加密。

将所有红色部分的字母进行整合,“DASF…”作为子密文进行频率分析,即可对第一个子密钥进行破译。

然后将蓝色部分的字母进行整合,“AVAS…”作为子密文进行频率分析,即可对第二个子密钥进行破译。

然后将绿色部分的字母进行整合,“GDF…”作为子密文进行频率分析,即可对第三个子密钥进行破译。

2.3 如何判定频率匹配程度

之前提到了对子密文进行破译的方法,使用26个字母作为密钥对子密钥进行解密,解密后的字母频率与英语的字母频率越匹配,则当前解密字母越可能是当前的子密钥。

如何度量字母频率的匹配程度?本节介绍一种简单的用于判定频率匹配程度的方法。 例如,对于某子密钥对应的子密文,使用“A”对其进行解密后为:

I rc ascwuiluhnviwuetnh,osgaa ice tipeeeee slnatsfietgi tittynecenisl. efo f fnc isltn sn o a yrs sd onisli ,i erglei trhfmwfrogotn,1 stcofiit.aea wesn,lnc ee w,l eih eeehoer ros iol er snh nl oahsts ilasvih tvfehrtira id thatnie.im ei-dlmf i thszonsisehroe,aiehcdsanahiec gv gyedsBaffcahiecesd d lee onsdihsoc nin cethiTitX eRneahgin r e teom fbiotd nntacscwevhtdhnhpiwru

上述文本中各字母按出现的频率大小排序为“EISNTHAOCLRFDGWVMUYBPZXQJK”。该频率排序中前6个字母和后6个字母与英文频率的前6个字母和后6个字母进行比较,如图 8所示:

图6 频率比较

观察上述比较,英文频率前6个字符有4个出现在了解密文本的前六个字母中,这4个字母为“E、I、N、T”;英文频率后6个字符有5个出现在了解密文本的后六个字母中,这5个字母为“Z、X、Q、J、K”。因此该段文本的字母频率与英文字母频率的匹配度为4+5=9,即当字符“A”作为子密钥的匹配程度为9。

通过上述方法得到26个字母作为子密钥时的匹配程度,该匹配程度越大的字母越有可能是子密钥。

2.4 维吉尼亚密码破解流程

总结维吉尼密码的破解流程。首先使用kasiski检测来确定密钥可能的长度;针对于每种可能的长度,将密文划分为子密钥和子密文;针对于每个子密钥和子密文对,通过频率分析的方法来确定可能的子密钥;通过可能的子密钥整合成可能的密钥;最后通过对所有可能的密钥进行爆破。

举例:

(以下数据基于假设,主要用于说明解密的具体流程)

(1)使用kasiski检测来确定密钥可能的长度为2,3。

(2)频率分析得到可能的密钥

  • 当密钥长度为2时

可将密文划分为两个子密文。通过频率分析方法可以得到可能的子密钥如下:

第1个子密钥可能的字母:A,B,

第2个子密钥可能的字母:C,D

对子密钥进行整合。最终可能的密钥为AC、AD、BC、BD

  • 当密钥长度为3时

可将密文划分为三个子密文。通过频率分析方法可以得到可能的子密钥如下:

第1个子密钥可能的字母:A,B,

第2个子密钥可能的字母:C,D

第2个子密钥可能的字母:E,F

对子密钥进行整合。最终可能的密钥为ACE、ACF、ADE、ADF、BCE、BCF、BDE、BDF

(3)最后对所有可能的密钥AC、AD、BC、BD、ACE、ACF、ADE、ADF、BCE、BCF、BDE、BDF进行爆破。

3 维吉尼亚密码加解密代码+应用

加解密代码函数:

def vigenereCipher(text,key,mode):
    LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    reusltText = ""
    keyIndex = 0
    key =key.upper()
    for letter in text:
        letterIndex = LETTERS.find(letter.upper())
        if letterIndex != -1:
            if mode == 1:
                letterIndex += LETTERS.find(key[keyIndex])
            elif mode == 0:
                letterIndex -= LETTERS.find(key[keyIndex])
            letterIndex %= len(LETTERS)
            if letter.isupper():
                reusltText += LETTERS[letterIndex]
            if letter.islower():
                reusltText += LETTERS[letterIndex].lower()
            keyIndex += 1
            if keyIndex == len(key):
                keyIndex = 0
        else:
            reusltText += letter
    return reusltText

明文:

There are moments in life when you miss someone so much that you just want to pick them from your dreams and hug them for real! Dream what you want to dream;go where you want to go;be what you want to be,because you have only one life and one chance to do all the things you want to do.May you have enough happiness to make you sweet,enough trials to make you strong,enough sorrow to keep you human,enough hope to make you happy? Always put yourself in others’shoes.If you feel that it hurts you,it probably hurts the other person, too.The happiest of people don’t necessarily have the best of everything;they just make the most of everything that comes along their way.Happiness lies for those who cry,those who hurt, those who have searched,and those who have 
tried,for only they can appreciate the importance of people.

进行加密:

密文:

Rvhdc ouq kcpqlhv ul zlrc kkql mrg kwve qcpqmbh em axof hkmr mrg hivf uoqf rc suay wtca idma basf gdcope ybg tsu wtca iap fhmj! Ruqya ztyh bas kdzr hr ppsdy;ec ztcfh kmi zmlh wa ec;eq uvdf wcx iybw fm ph,ncqdgqs bas vdhc cqxw cqq jwiq ybg als ftybfq rc ga yzo ffs wtgbje wcx iybw fm rr.Yym bas vdhc sqasuk tydsulsve rc pmis bas gzqch,hzmijt rflmjg wa konq wcx erfrze,sqasuk emfuau hr wcss kmi kgkoq,qlcxsf vrbc hr yyyh kmi kmndb? Mjkdkq dxf wcxdqsor gb rffsue’qvrqq.Wi kmi iqcz wtyh lf fiufq mrg,gh sdmpdnjm kgphv ffs rffsu bcfval, hra.Rvh tydsucgw ad dhanzh pmb’w zcqheqouujm kmts wtc pher ci qtsukrvlze;hkqw xxer adwc hkq kcvf mt hhcfbffwqs rvdf acpqq ooalu wtcwu iym.Kmndlzcgv xgsv rmf wtmgh ifc fdw,hkaqs ztm vxdr, hkaqs ztm vdhc ghmpqkqb,oqp rvrec kka foyq rflqb,trd mbok rvhk aoq mnduqawdfc hkq gasaphdzas rr nsrbjs.

进行解密:

4 维吉尼亚密码破解代码+应用

就对上面的密文破解,很快就破了。

代码如下,真不想写注释。

LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def findRepeatSequencesSpacings(ciphertext):
    # 删除非字母的字符
    ciphertext = re.compile('[^A-Z]').sub('',ciphertext.upper())
    seqSpacings = {}
    # 获取任何重复3~5个字母的序列
    for seqLen in range(3,6):
        # 从左到右,依次截取当前指定长度字符序列
        for seqStart in range(len(ciphertext) - seqLen):
            # 按当前长度截取字符序列
            seq = ciphertext[seqStart:seqStart + seqLen]
            # 在其余部分中查找截取的字符序列
            for i in range(seqStart + seqLen,len(ciphertext) - seqLen):
                # 如果其余部分存在同样的字符序列
                if ciphertext[i:i+seqLen] == seq:
                    # 如果字符序列不在字典中则进行添加
                    if seq not in seqSpacings:
                        seqSpacings[seq] = []
                    # 添加间隔距离
                    seqSpacings[seq].append(i - seqStart)
    return seqSpacings
    
def getUsefulFactors(num):
    if num < 2:
        return []
    factors = []
    for i in range(2 , 16+1):
        if num % i == 0:
            factors.append(i)
            otherFactors = int(num/i)
            if otherFactors < (16 + 1) and otherFactors != 1:
                factors.append(otherFactors)
    return list(set(factors))
    # return  factors
# findRepeatSequencesSpacings(ciphertext)

def getItemAtIndexOne(items):
    return items[1]

def getMostCommonFactors(seqFactors):
    # 计算一个因子出现的次数
    factorCounts = {}
    for seq in seqFactors:
        factorList = seqFactors[seq]
        for factor in factorList:
            if factor not in factorCounts:
                factorCounts[factor] = 0
            factorCounts[factor] += 1
    factorsByCount = []
    for factor in factorCounts:
        if factor <= 16:#可以删掉的
            factorsByCount.append((factor, factorCounts[factor]))
    factorsByCount.sort(key = getItemAtIndexOne,reverse = True)
    return factorsByCount
def kasiskiExamination(ciphertext):
    repeatedSeqSpacings = findRepeatSequencesSpacings(ciphertext)
    seqFactors = {}
    for seq in repeatedSeqSpacings:
        seqFactors[seq] = []
        for spacing in repeatedSeqSpacings[seq]:
            # print(seq,spacing)
            seqFactors[seq].extend(getUsefulFactors(spacing))
    factorByConut = getMostCommonFactors(seqFactors)
    allLikelyLengths = []
    for twoIntTuple in factorByConut:
        allLikelyLengths.append(twoIntTuple[0])
    return allLikelyLengths
def getNthSubkeysLetters(nth,keyLength,message):
    message = re.compile('[^A-Z]').sub('',message)
    i = nth -1
    letters = []
    while i< len(message):
        letters.append(message[i])
        i += keyLength
    return ''.join(letters)
ETAOIN = 'ETAOINSHRDLCUMWFGYPBVKJXQZ'
# 返回指定字符串中每个字母的数量
def getLetterCount(message):
    letterCount = {'A':0,'B':0,'C':0,'D':0,'E':0,'F':0,'G':0,'H':0,'I':0,'J':0,'K':0,'L':0,'M':0,'N':0,'O':0,'P':0,'Q':0,'R':0,'S':0,'T':0,'U':0,'V':0,'W':0,'X':0,'Y':0,'Z':0}
    for letter in message.upper():
        if letter in LETTERS:
            letterCount[letter] +=1
    return letterCount

def getItemAtIndexZero(items):
    return items[0]

def getFrequencyOrder(message):
    # 1.计算每个字母的个数,{'A': 4,'B': 1}
    letterToFreq = getLetterCount(message)
    # 2.将键值对反过来,{4: ['A', 'D'],1: ['B']}
    freqToLetter= {}
    for letter in LETTERS:
        if letterToFreq[letter] not in freqToLetter:
            freqToLetter[letterToFreq[letter]] = [letter]
        else:
            freqToLetter[letterToFreq[letter]].append(letter)
    # 3.将值列表中的字母按照ETAOIN的反序排列,并返回字符串
    for freq in freqToLetter:
        freqToLetter[freq].sort(key=ETAOIN.find,reverse=True)
        freqToLetter[freq] = ''.join(freqToLetter[freq])
    # 4.按数量排序
    freqPairs = list(freqToLetter.items())
    freqPairs.sort(key=getItemAtIndexZero,reverse=True)

    # 5.提取字符排序
    freqOrder = []
    for freqPair in freqPairs:
        freqOrder.append(freqPair[1])
    return ''.join(freqOrder)

def englishFreqMatchScore(message):
    freqOrder = getFrequencyOrder(message)
    matchScore = 0
    for commonLetter in ETAOIN[:6]:
        if commonLetter in freqOrder[:6]:
            matchScore += 1
    for uncommonLetter in ETAOIN[-6:]:
        if uncommonLetter in freqOrder[-6:]:
            matchScore += 1
    return matchScore
    
def checkEn(decryptText):
    dct = enchant.Dict('en_US')
    wordList = decryptText.split(' ')
    result = sum([dct.check(word) for word in wordList]) / len(wordList)
    return result
def attmptHackWithKeyLength(ciphertext,keylength):
    result = []
    print("\n=================尝试密钥长度{}================".format(keylength))
    ciphertextUP = ciphertext.upper()
    # print(len(ciphertext))
    allFreqScores = []
    for nth in range(1,keylength+1):
        nthLeeters = getNthSubkeysLetters(nth,keylength,ciphertextUP)
        # print("nthLeeters:",nthLeeters)
        freqScores = []
        for possibleKey in LETTERS:
            decryptedText = vigenereCipher(nthLeeters,possibleKey,0)
            # print(decryptedText)
            keyAndFreqMatchTuple = (possibleKey,englishFreqMatchScore(decryptedText))
            # print(keyAndFreqMatchTuple)
            freqScores.append(keyAndFreqMatchTuple)
        freqScores.sort(key=getItemAtIndexOne,reverse=True)
        allFreqScores.append(freqScores[:4])

    # print(allFreqScores)
    for i in range(len(allFreqScores)):      
        print("密钥第{}个字符的可能字符集:".format(i+1),end='')
        for freqScore in allFreqScores[i]:
            print(freqScore[0],end=' ')
        print()

    possibleKeynNum = 4 ** keylength
    print("需尝试的密钥数量为:{}".format(possibleKeynNum))
    # 尝试各种组合
    for indexes in itertools.product(range(4),repeat=keylength):
        possibleKey = ''
        for i in range(keylength):
            possibleKey += allFreqScores[i][indexes[i]][0]
        decryptedText = vigenereCipher(ciphertext,possibleKey,0)
        resultScore = checkEn(decryptedText)

        if resultScore > 0.5:
            print("当前尝试的密钥为:{}".format(possibleKey))
            print(resultScore," : ",decryptedText[:100])
            result.append((resultScore,possibleKey,decryptedText))
        else:
            print(".",end="")
        if resultScore > 0.8:
            return result
    return result
def hackVigenere(cipherText):
    allLikelyLengths = kasiskiExamination(cipherText)
    print("可能的key长度为:{}".format(allLikelyLengths))
    for keylength in allLikelyLengths[:4]:
        hackResult = attmptHackWithKeyLength(cipherText,keylength)
    return hackResult

引用:

[1]Beker, Henry; Piper, Fred. Cipher Systems: The Protection of Communications. Wiley-Interscience. 1982: 397. Table also available from Lewand, Robert. Cryptological Mathematics. The Mathematical Association of America. 2000: 36 [2013-06-05]. ISBN 978-0-88385-719-9.

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注