维吉尼亚密码破解全流程解析
1. 确定最可能的密钥长度
在破解维吉尼亚密码时,首先要确定密钥的长度。可以使用
getMostCommonFactors()
函数来筛选出最可能的密钥长度。以下是相关代码及解析:
factorsByCount = []
for factor in factorCounts:
# 排除大于 MAX_KEY_LENGTH 的因子
if factor <= MAX_KEY_LENGTH:
# factorsByCount 是一个元组列表:(因子, 因子计数)
factorsByCount.append( (factor, factorCounts[factor]) )
# 对 factorsByCount 进行排序
factorsByCount.sort(key=getItemAtIndexOne, reverse=True)
return factorsByCount
上述代码中,首先遍历
factorCounts
中的每个因子,将小于等于
MAX_KEY_LENGTH
的因子及其计数组成元组添加到
factorsByCount
列表中。然后使用
sort()
方法对
factorsByCount
列表进行排序,按照因子的计数降序排列。最后返回排序后的列表,该列表表明哪些因子出现的频率最高,因此最有可能是维吉尼亚密码的密钥长度。
kasiskiExamination()
函数可以返回给定密文最可能的密钥长度列表,具体步骤如下:
1. 调用
findRepeatSequencesSpacings(ciphertext)
函数,找出密文中重复序列之间的间距,返回一个字典,键为序列字符串,值为间距整数列表。
2. 扩展
repeatedSeqSpacings
字典,将间距的因子存储在
seqFactors
字典中。具体代码如下:
seqFactors = {}
for seq in repeatedSeqSpacings:
seqFactors[seq] = []
for spacing in repeatedSeqSpacings[seq]:
seqFactors[seq].extend(getUsefulFactors(spacing))
-
调用
getMostCommonFactors(seqFactors)函数,返回一个二元整数元组列表,第一个整数表示因子,第二个整数表示该因子在seqFactors中出现的频率。 -
从
factorsByCount中提取因子,存储在allLikelyKeyLengths列表中。代码如下:
allLikelyKeyLengths = []
for twoIntTuple in factorsByCount:
allLikelyKeyLengths.append(twoIntTuple[0])
return allLikelyKeyLengths
2. 获取使用相同子密钥加密的字母
为了提取密文中使用相同子密钥加密的字母,需要编写
getNthSubkeysLetters()
函数。该函数的步骤如下:
1. 使用正则表达式对象及其
sub()
方法,去除消息中的非字母字符。代码如下:
message = NONLETTERS_PATTERN.sub('', message)
-
构建一个字符串,将字母字符串添加到列表中,然后使用
join()方法将列表合并为一个字符串。代码如下:
i = nth - 1
letters = []
while i < len(message):
letters.append(message[i])
i += keyLength
return ''.join(letters)
3. 尝试使用可能的密钥长度进行解密
attemptHackWithKeyLength()
函数用于尝试使用给定的密钥长度对密文进行解密。具体步骤如下:
1. 将密文转换为大写形式,存储在
ciphertextUp
变量中。
ciphertextUp = ciphertext.upper()
-
初始化
allFreqScores列表,用于存储频率匹配分数。
allFreqScores = []
-
遍历每个子密钥,调用
getNthSubkeysLetters()函数获取该子密钥对应的字母。
for nth in range(1, mostLikelyKeyLength + 1):
nthLetters = getNthSubkeysLetters(nth, mostLikelyKeyLength, ciphertextUp)
- 对每个子密钥的字母进行解密,尝试所有 26 种可能的子密钥,计算解密后的文本与英语字母频率的匹配分数。
freqScores = []
for possibleKey in LETTERS:
decryptedText = vigenereCipher.decryptMessage(possibleKey, nthLetters)
keyAndFreqMatchTuple = (possibleKey, freqAnalysis.englishFreqMatchScore(decryptedText))
freqScores.append(keyAndFreqMatchTuple)
-
对
freqScores列表进行排序,按照英语频率匹配分数降序排列。
freqScores.sort(key=getItemAtIndexOne, reverse=True)
-
将每个子密钥的前
NUM_MOST_FREQ_LETTERS个元组添加到allFreqScores列表中。
allFreqScores.append(freqScores[:NUM_MOST_FREQ_LETTERS])
4.
print()
函数的
end
关键字参数
print()
函数的
end
关键字参数可以指定在字符串末尾打印的内容,而不是换行符。例如:
def printStuff():
print('Hello', end='\n')
print('Howdy', end='')
print('Greetings', end='XYZ')
print('Goodbye')
printStuff()
上述代码中,
print('Hello', end='\n')
正常打印字符串并换行;
print('Howdy', end='')
打印字符串后不换行;
print('Greetings', end='XYZ')
打印字符串后添加
XYZ
而不是换行符。
5. 程序的静默模式或向用户打印信息
如果
SILENT_MODE
常量设置为
False
,则会将
allFreqScores
中的值打印到屏幕上,显示每个子密钥的前三个候选字母。代码如下:
if not SILENT_MODE:
for i in range(len(allFreqScores)):
print('Possible letters for letter %s of the key: ' % (i + 1), end='')
for freqScore in allFreqScores[i]:
print('%s ' % freqScore[0], end='')
print()
6. 寻找子密钥的可能组合
为了找到完整的密钥,需要尝试不同的子密钥组合。可以使用
itertools.product()
函数生成所有可能的子密钥组合。以下是相关代码及解析:
for indexes in itertools.product(range(NUM_MOST_FREQ_LETTERS), repeat=mostLikelyKeyLength):
possibleKey = ''
for i in range(mostLikelyKeyLength):
possibleKey += allFreqScores[i][indexes[i]][0]
if not SILENT_MODE:
print('Attempting with key: %s' % (possibleKey))
# 解密密文并检查是否为可读英语
# ...
上述代码中,
itertools.product(range(NUM_MOST_FREQ_LETTERS), repeat=mostLikelyKeyLength)
生成所有可能的索引组合,每个组合表示一个可能的密钥。然后根据索引从
allFreqScores
中提取子密钥字母,构建完整的密钥。如果
SILENT_MODE
为
False
,则打印尝试的密钥。
7. 打印正确大小写的解密文本
由于解密后的文本为大写形式,需要将其转换为与原文相同的大小写形式。可以通过遍历解密后的文本,根据原文的大小写情况进行转换。
以下是整个流程的 mermaid 流程图:
graph TD;
A[确定最可能的密钥长度] --> B[获取使用相同子密钥加密的字母];
B --> C[尝试使用可能的密钥长度进行解密];
C --> D[打印信息或使用静默模式];
D --> E[寻找子密钥的可能组合];
E --> F[打印正确大小写的解密文本];
通过以上步骤,可以逐步破解维吉尼亚密码,找到最可能的密钥并解密出原文。在实际应用中,可以根据具体情况调整参数和方法,提高破解的效率和准确性。
维吉尼亚密码破解全流程解析(续)
8.
itertools.product()
函数详解
itertools.product()
函数是 Python 中一个非常有用的工具,它可以生成列表、字符串或元组等可迭代对象中元素的所有可能组合,这种组合被称为笛卡尔积。以下是一些使用示例:
import itertools
# 示例 1:使用字符串生成组合
result1 = itertools.product('ABC', repeat=4)
print(list(result1))
# 示例 2:使用 range 对象生成组合
result2 = itertools.product(range(8), repeat=5)
print(list(result2))
在示例 1 中,
itertools.product('ABC', repeat=4)
会生成包含四个字符的所有可能组合,每个字符可以是 ‘A’、’B’ 或 ‘C’,最终会得到一个包含 $3^4 = 81$ 个元组的列表。在示例 2 中,
itertools.product(range(8), repeat=5)
会生成包含五个整数的所有可能组合,每个整数的范围是 0 到 7,最终会得到一个包含 $8^5$ 个元组的列表。
在维吉尼亚密码破解中,我们不能直接将潜在的子密钥字母列表传递给
itertools.product()
函数,因为每个子密钥的潜在字母可能不同。我们需要通过索引来访问
allFreqScores
中的子密钥字母。由于
NUM_MOST_FREQ_LETTERS
常量设置为 4,
itertools.product(range(NUM_MOST_FREQ_LETTERS), repeat=mostLikelyKeyLength)
会生成所有可能的索引组合,每个组合表示一个可能的密钥。
9. 访问
allFreqScores
中的子密钥
allFreqScores
是一个存储每个子密钥最可能字母及其频率匹配分数的列表。以下是一个假设的
allFreqScores
值示例:
allFreqScores = [
[('A', 9), ('E', 5), ('O', 4), ('P', 4)],
[('S', 10), ('D', 4), ('G', 4), ('H', 4)],
[('I', 11), ('V', 4), ('X', 4), ('B', 3)],
[('M', 10), ('Z', 5), ('Q', 4), ('A', 3)],
[('O', 11), ('B', 4), ('Z', 4), ('A', 3)],
[('V', 10), ('I', 5), ('K', 5), ('Z', 4)]
]
我们可以通过索引来访问
allFreqScores
中的具体值。例如:
-
allFreqScores[0]
表示第一个子密钥的可能字母及其频率匹配分数列表。
-
allFreqScores[1][0]
表示第二个子密钥最可能的字母及其频率匹配分数元组。
-
allFreqScores[1][0][0]
表示第二个子密钥最可能的字母。
以下是一个表格总结不同索引访问的含义:
| 索引示例 | 含义 |
| ---- | ---- |
|
allFreqScores[i]
| 第
i+1
个子密钥的可能字母及其频率匹配分数列表 |
|
allFreqScores[i][j]
| 第
i+1
个子密钥中第
j+1
个可能的字母及其频率匹配分数元组 |
|
allFreqScores[i][j][0]
| 第
i+1
个子密钥中第
j+1
个可能的字母 |
10. 构建完整密钥并尝试解密
在获取了所有可能的子密钥组合后,我们需要构建完整的密钥并尝试解密密文。以下是具体的代码和步骤:
for indexes in itertools.product(range(NUM_MOST_FREQ_LETTERS), repeat=mostLikelyKeyLength):
possibleKey = ''
for i in range(mostLikelyKeyLength):
possibleKey += allFreqScores[i][indexes[i]][0]
if not SILENT_MODE:
print('Attempting with key: %s' % (possibleKey))
# 解密密文
decryptedText = vigenereCipher.decryptMessage(possibleKey, ciphertextUp)
# 检查是否为可读英语
if freqAnalysis.englishFreqMatchScore(decryptedText) > THRESHOLD:
# 恢复原文大小写
originalCaseText = ''
for i in range(len(ciphertext)):
if ciphertext[i].isupper():
originalCaseText += decryptedText[i].upper()
elif ciphertext[i].islower():
originalCaseText += decryptedText[i].lower()
else:
originalCaseText += ciphertext[i]
print('Possible decrypted message:')
print(originalCaseText)
上述代码中,首先通过
itertools.product()
生成所有可能的索引组合,然后根据索引从
allFreqScores
中提取子密钥字母,构建完整的密钥。接着使用该密钥解密密文,并计算解密后文本与英语字母频率的匹配分数。如果匹配分数超过阈值
THRESHOLD
,则认为解密成功,恢复原文的大小写并打印解密后的消息。
11. 总结与优化建议
通过以上步骤,我们可以完成维吉尼亚密码的破解。整个流程可以总结为以下几个关键步骤:
1. 确定最可能的密钥长度。
2. 获取使用相同子密钥加密的字母。
3. 尝试使用可能的密钥长度进行解密。
4. 打印信息或使用静默模式。
5. 寻找子密钥的可能组合。
6. 打印正确大小写的解密文本。
为了提高破解的效率和准确性,我们可以考虑以下优化建议:
-
调整阈值
:根据实际情况调整
THRESHOLD
的值,避免误判或漏判。
-
增加样本量
:如果可能的话,获取更多的密文样本,以提高密钥长度和子密钥的判断准确性。
-
优化频率分析方法
:可以使用更复杂的频率分析方法,如双字母频率分析或三字母频率分析,提高解密的准确性。
以下是整个优化流程的 mermaid 流程图:
graph TD;
A[确定最可能的密钥长度] --> B[获取使用相同子密钥加密的字母];
B --> C[尝试使用可能的密钥长度进行解密];
C --> D{是否满足阈值};
D -- 是 --> E[恢复原文大小写并打印];
D -- 否 --> F[调整参数或增加样本量];
F --> A;
通过不断优化和调整,我们可以更高效地破解维吉尼亚密码,获取原文信息。在实际应用中,还需要考虑安全性和合法性等问题,确保破解行为符合相关规定。
超级会员免费看
1万+

被折叠的 条评论
为什么被折叠?



