#playfair破解#checkio#启发式算法
from itertools import product
# encrypt函数用于根据Playfair密码表加密信息
def encrypt(message, key_table):
# 创建一个反向的密钥表,用于从坐标找回字母
back_key = {key_table[i]: i for i in key_table}
answer = ''
# 遍历信息中的每对字母
for i in range(0, 15, 2):
a, b = message[i], message[i + 1]
ax, ay = key_table[a]
bx, by = key_table[b]
# 如果两个字母在同一行
if bx == ax:
# 将它们右边的字母作为加密结果
cx, cy = ax, (ay + 1) % 5
dx, dy = bx, (by + 1) % 5
# 如果两个字母在同一列
elif ay == by:
# 将它们下面的字母作为加密结果
cx, cy = (ax + 1) % 5, ay
dx, dy = (bx + 1) % 5, by
# 如果两个字母不在同一行也不在同一列
else:
# 交叉取对角线上的字母作为加密结果
cx, cy = ax, by
dx, dy = bx, ay
# 将加密结果添加到答案中
answer += back_key[(cx, cy)] + back_key[(dx, dy)]
return answer
def generateVariant(pair,table):
#接受两个参数,分别是即将参与变体构造的字符对,和一个候选的密码表
#先创建一个5×5的空位置集合
freeCells = {i for i in product(range(5), range(5)) if i not in table.values()}
a,b,c,d=pair
variants=[]#储存产生的变体
#如果候选表中没有给定第一个字符,a的位置,那我们假设它可以存在5×5表上任意一个位置
aPosSet = freeCells if a not in table else {table[a]}
for aPos in aPosSet:
# 如果字母b不在表中,则考虑所有可能的位置
bPosSet = freeCells - {aPos} if b not in table else {table[b]}
for bPos in bPosSet:
#将a,b的位置带入密钥配对过程,筛选选出符合条件的密码表,当然这个密码表对当下的pair管用
try:
# 尝试更新密钥表表
newTable = table.copy()
newTable.update({a: aPos, b: bPos})
v = resolve(newTable, a + b + c + d)#不是所有情况都有解,因此此步骤可能失败,用try语句执行
variants.append(v)
except:
continue
return variants
def resolve(table, pair):
#接受两个参数,分别是密钥表和字符对
a, b, c, d = pair
update_ = {a: table[a], b: table[b]}
# 检查剩余两个字母的坐标,根据play fair的规则,a与c关系绑定,b与d关系绑定
for (i, j, k) in [(c, a, b), (d, b, a)]:
# 如果两个明文字母a,b在同一列
if table[j][1] == table[k][1]:
# 计算新的坐标
coords = ((table[j][0] + 1) % 5, table[j][1])
#( ... ) % 5: 对加1后的结果进行模5运算。模运算(或称为取余运算)会返回除以5后的余数。
#这样做通常是为了实现循环效果,即当值达到某个上限(这里是5)时,它会回到0并继续循环
# 如果两个明文字母a,b在同一行
elif table[j][0] == table[k][0]:
# 计算新的坐标
coords = (table[j][0], (table[j][1] + 1) % 5)
# 如果两个明文字母不在同一行也不在同一列
else:
# 计算新的坐标
coords = (table[j][0], table[k][1])
# 检查新坐标是否与已知坐标冲突
if i in table:
if table[i] != coords:#如果密文字母i已经在密钥表中,且不是上一步算出的坐标
raise AssertionError#对应generateVariant函数的try语句,实现错误跳过
elif coords in table.values():#如果上一步算出的坐标已经存在于函数参数给定的密钥表中
raise AssertionError
else:#秘文字母已经在且坐标就是给定的坐标值,密文字母不在
#将密文的字符加入
update_[i] = coords
#update_ 包含明文和密文字符对各自的位置值
return update_
def searchPos(candidateTable,unusedPairs):
#接受两个参数,分别是候选的密码表(键值集合,键为字母,值为坐标),和未使用的字符对(两明文,两密文)
if not unusedPairs:
return candidateTable
#我们需要将每个未使用的字符对存在的可能情况算出,因此我们需要重新定义一个函数,它接受我们传入的字符对和已经生成的密钥表
nextPair=max(unusedPairs,key=lambda x: sum([l in candidateTable for l in x]))
#sum([l in candidate_table for l in x])计算unused中的每个字符对(对应lambda的x)中的每个字母(对应for l in x)在candidate中出现的频率的各自的和,从而得出待定位置最少的字符对
#启发式算法
#生成所有可能的密钥表变体
variants = generateVariant(nextPair, candidateTable)
for v in variants:
# 尝试更新密钥表并递归搜索
new_table = candidateTable.copy()
new_table.update(v)
#递归搜索,知道所有字符对算尽
result = searchPos(new_table, unusedPairs - {nextPair})
if result is not None:
return result
def playfair_attack(plain,crypto):
#将明文和密文组成双字母组
pairs=[plain[i:i+2]+crypto[i:i+2] for i in range(0,len(plain),2)]
start=pairs[0]
#可知这些字母组在数表上都是存在的同时一定各自只占据一个位置
#因此我们从第一组字母组的第一个字母开始搜索这个字母所有可能的位置位置,这并不是无顺序的而是按照playfair的规则
keyTable=searchPos({start[0]:(0,0)},set(pairs))
# 找出所有未知的字母
unknown = list(set('abcdefghiklmnopqrstuvwxyz') - set(keyTable.keys()))
# 如果只有一个未知字母,则找出其位置
if len(unknown) == 1:
for xy in product(range(5), range(5)):
if xy not in keyTable.values():
keyTable[unknown[0]] = xy
break
# 使用密钥表加密信息
return encrypt('topsecretmessage', keyTable)
generateVariant(pair,table)用于生成一对字符串在密钥表中所有可能的位置变体,其中使用一个resolve(table, pair)函数用于将明文字符对推演出密文字符对。
searchPos(candidateTable,unusedPairs)函数使用递归逻辑用于完善更新不完整的密钥表,调用generateVariant函数不断生成用于更新的元素。