114 lines
3.5 KiB
Python
Executable File
114 lines
3.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: UTF-8 -*-
|
|
from utils import affine_decrypt
|
|
|
|
|
|
#########################################
|
|
# GuessVigenere : Shift values around with a given keylength
|
|
#########################################
|
|
|
|
class GuessVigenere(object):
|
|
def __init__(self, nums):
|
|
self.nums = nums
|
|
|
|
def guess(self, keylength, score_fn): # minimize score_fn
|
|
found = []
|
|
avg_score = 0
|
|
for offset in range(keylength):
|
|
bi = -1
|
|
bs = 9999999
|
|
for i in range(29):
|
|
shifted = [(x - i) % 29 for x in self.nums[offset::keylength]]
|
|
score = score_fn(shifted)
|
|
if score < bs:
|
|
bs = score
|
|
bi = i
|
|
avg_score += bs
|
|
found.append(bi)
|
|
return avg_score / keylength, found
|
|
|
|
|
|
#########################################
|
|
# GuessAffine : Find greatest common affine key
|
|
#########################################
|
|
|
|
class GuessAffine(object):
|
|
def __init__(self, nums):
|
|
self.nums = nums
|
|
|
|
def guess(self, keylength, score_fn): # minimize score_fn
|
|
found = []
|
|
avg_score = 0
|
|
for offset in range(keylength):
|
|
candidate = (None, None)
|
|
best = 9999999
|
|
for s in range(29):
|
|
for t in range(29):
|
|
shifted = [affine_decrypt(x, (s, t))
|
|
for x in self.nums[offset::keylength]]
|
|
score = score_fn(shifted)
|
|
if score < best:
|
|
best = score
|
|
candidate = (s, t)
|
|
avg_score += best
|
|
found.append(candidate)
|
|
return avg_score / keylength, found
|
|
|
|
|
|
#########################################
|
|
# GuessPattern : Find a key that is rotated ABC BCA CAB, or ABC CAB BCA
|
|
#########################################
|
|
|
|
class GuessPattern(object):
|
|
@staticmethod
|
|
def groups(nums, keylen, generator, offset=0):
|
|
for _ in range(offset):
|
|
next(generator)
|
|
ret = [[] for _ in range(keylen)]
|
|
for idx, value in zip(generator, nums):
|
|
ret[idx].append(value)
|
|
return ret
|
|
|
|
def shift_pattern(kl, shift=1): # shift by (more than) one, 012201120
|
|
for i in range(10000):
|
|
p = (i * shift) % kl
|
|
yield from range(p, kl)
|
|
yield from range(p)
|
|
|
|
def mirror_pattern_a(kl): # mirrored, 012210012210
|
|
for i in range(10000):
|
|
yield from range(kl)
|
|
yield from range(kl - 1, -1, -1)
|
|
|
|
def mirror_pattern_b(kl): # mirrored, 012101210
|
|
for i in range(10000):
|
|
yield from range(kl)
|
|
yield from range(kl - 2, 0, -1)
|
|
|
|
@staticmethod
|
|
def zip(nums, key, keylen, shift=1, offset=0):
|
|
gen = GuessPattern.shift_pattern(keylen, shift)
|
|
for _ in range(offset):
|
|
next(gen)
|
|
return [(n - key[k]) % 29 for n, k in zip(nums, gen)]
|
|
|
|
@staticmethod
|
|
def guess(parts, score_fn): # minimize score_fn
|
|
found = []
|
|
avg_score = 0
|
|
for nums in parts:
|
|
best = 9999999
|
|
candidate = 0
|
|
for i in range(29):
|
|
score = score_fn([(x - i) % 29 for x in nums])
|
|
if score < best:
|
|
best = score
|
|
candidate = i
|
|
avg_score += best
|
|
found.append(candidate)
|
|
return avg_score / len(parts), found
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print(list(GuessPattern.shift_pattern(4, 3))[:20])
|