AutokeySolver

This commit is contained in:
relikd
2021-01-24 23:44:03 +01:00
parent c2e00a19bd
commit d3cc823268
3 changed files with 89 additions and 72 deletions

View File

@@ -36,6 +36,9 @@ class Probability(object):
X = sum(x * (x - 1) for x in self.prob) X = sum(x * (x - 1) for x in self.prob)
return X / ((self.N * (self.N - 1)) / 29) return X / ((self.N * (self.N - 1)) / 29)
def IC_norm(self, target_ioc=TARGET_IOC):
return abs(self.IC() - target_ioc)
def similarity(self): def similarity(self):
probs = normalized_probability(self.prob) probs = normalized_probability(self.prob)
return sum((x - y) ** 2 for x, y in zip(PROB_NORM, probs)) return sum((x - y) ** 2 for x, y in zip(PROB_NORM, probs))

View File

@@ -34,7 +34,7 @@ class RuneSolver(RuneRunner):
skip = is_interrupt and self.interrupt_counter in self.INTERRUPT_POS skip = is_interrupt and self.interrupt_counter in self.INTERRUPT_POS
mark_this = self.mark_char_at(index) mark_this = self.mark_char_at(index)
if not skip: if not skip:
obj = self.cipher(obj, (encrypted_data, index)) obj = self.cipher(obj, (index, encrypted_data))
mark_next = self.mark_char_at(index) mark_next = self.mark_char_at(index)
return obj, mark_this, mark_next return obj, mark_this, mark_next
@@ -53,65 +53,6 @@ class RuneSolver(RuneRunner):
return txt + f'\ninterrupt jumps: {self.INTERRUPT_POS}' return txt + f'\ninterrupt jumps: {self.INTERRUPT_POS}'
#########################################
# VigenereSolver : Decrypt runes with key; handle key shift, rotation, etc.
#########################################
class VigenereSolver(RuneSolver):
def __init__(self):
super().__init__()
self.current_key_pos = 0
self.reset()
def reset(self):
super().reset()
self.KEY_DATA = [] # the key material
self.KEY_INVERT = False # ABCD -> ZYXW
self.KEY_SHIFT = 0 # ABCD -> DABC
self.KEY_ROTATE = 0 # ABCD -> ZABC
self.KEY_OFFSET = 0 # ABCD -> __ABCD
self.KEY_POST_PAD = 0 # ABCD -> ABCD__
def run(self, data=None):
self.current_key_pos = 0
super().run(data=data)
def is_key_active(self, _=None):
i = self.current_key_pos - self.KEY_OFFSET
if i >= 0 and i < len(self.KEY_DATA):
return self.KEY_DATA[i] != 29 # used as placeholder for unknown
return False
def mark_char_at(self, position):
return self.is_key_active(position)
def rotate_key(self):
key_size = self.KEY_OFFSET + len(self.KEY_DATA) + self.KEY_POST_PAD
if key_size > 0: # mostly for key invert without a key
self.current_key_pos = (self.current_key_pos + 1) % key_size
def cipher(self, rune, context):
r_idx = rune.index
if self.KEY_INVERT:
r_idx = 28 - r_idx
if self.is_key_active():
key_i = self.current_key_pos
i = (key_i - self.KEY_OFFSET + self.KEY_SHIFT) % len(self.KEY_DATA)
r_idx = (r_idx - self.KEY_DATA[i] - self.KEY_ROTATE) % 29
self.rotate_key()
return Rune(i=r_idx)
def __str__(self):
key = RuneText(self.KEY_DATA).description(indexWhitespace=True)
txt = super().__str__()
txt += f'\nkey: {key}'
txt += f'\nkey invert: {self.KEY_INVERT}'
txt += f'\nkey shift: {self.KEY_SHIFT} indices'
txt += f'\nkey offset: {self.KEY_OFFSET} runes'
txt += f'\nkey post pad: {self.KEY_POST_PAD} runes'
return txt
######################################### #########################################
# SequenceSolver : Decrypt runes with sequential function # SequenceSolver : Decrypt runes with sequential function
######################################### #########################################
@@ -141,35 +82,106 @@ class SequenceSolver(RuneSolver):
######################################### #########################################
# AffineSolver : Decrypt runes with an array of (s, t) affine keys # RunningKeySolver : Decrypt runes with key; handles shift, rotation, etc.
######################################### #########################################
class AffineSolver(RuneSolver): class RunningKeySolver(RuneSolver):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.current_key_pos = 0
self.reset() self.reset()
def reset(self): def reset(self):
super().reset() super().reset()
self.KEY_DATA = [] # the key material self.KEY_DATA = [] # the key material
self.KEY_INVERT = False # ABCD -> ZYXW self.KEY_INVERT = False # ABCD -> ZYXW
self.KEY_SHIFT = 0 # ABCD -> DABC
self.KEY_ROTATE = 0 # ABCD -> ZABC
self.KEY_OFFSET = 0 # ABCD -> __ABCD
self.KEY_POST_PAD = 0 # ABCD -> ABCD__
def run(self, data=None): def run(self, data=None):
self.current_key_pos = 0 self.k_current_pos = 0
self.k_len = len(self.KEY_DATA)
self.k_full_len = self.KEY_OFFSET + self.k_len + self.KEY_POST_PAD
super().run(data=data) super().run(data=data)
def rotate_key(self): def mark_char_at(self, position):
self.current_key_pos = (self.current_key_pos + 1) % len(self.KEY_DATA) return self.active_key_pos() != -1
def active_key_pos(self):
i = self.k_current_pos - self.KEY_OFFSET
if i >= 0 and i < self.k_len:
if self.KEY_DATA[i] != 29: # placeholder for unknown
return i
return -1
def cipher(self, rune, context): def cipher(self, rune, context):
r_idx = rune.index r_idx = rune.index
if self.KEY_INVERT: if self.KEY_INVERT:
r_idx = 28 - r_idx r_idx = 28 - r_idx
r_idx = LIB.affine_decrypt(r_idx, self.KEY_DATA[self.current_key_pos]) pos = self.active_key_pos()
self.rotate_key() if pos != -1:
i = (pos + self.KEY_SHIFT) % self.k_len
r_idx = (self.decrypt(r_idx, i) - self.KEY_ROTATE) % 29
# rotate_key
if self.k_full_len > 0: # e.g., for key invert without a key
self.k_current_pos = (self.k_current_pos + 1) % self.k_full_len
return Rune(i=r_idx) return Rune(i=r_idx)
def decrypt(self, rune_index, key_index):
raise NotImplementedError # must subclass
def key__str__(self):
return self.KEY_DATA # you should override this
def key__str__basic_runes(self):
return RuneText(self.KEY_DATA).description(indexWhitespace=True)
def __str__(self): def __str__(self):
return super().__str__() + \ txt = super().__str__()
f'\nkey: {self.KEY_DATA}\nkey invert: {self.KEY_INVERT}' txt += f'\nkey: {self.key__str__()}'
txt += f'\nkey invert: {self.KEY_INVERT}'
txt += f'\nkey offset: {self.KEY_OFFSET} runes'
txt += f'\nkey post pad: {self.KEY_POST_PAD} runes'
txt += f'\nkey shift: {self.KEY_SHIFT} indices'
txt += f'\nkey rotate: {self.KEY_ROTATE} indices'
return txt
#########################################
# VigenereSolver : Decrypt runes with an array of indices
#########################################
class VigenereSolver(RunningKeySolver):
def decrypt(self, rune_index, key_index):
return rune_index - self.KEY_DATA[key_index]
def key__str__(self):
return self.key__str__basic_runes()
#########################################
# AffineSolver : Decrypt runes with an array of (s, t) affine keys
#########################################
class AffineSolver(RunningKeySolver):
def decrypt(self, rune_index, key_index):
return LIB.affine_decrypt(rune_index, self.KEY_DATA[key_index])
#########################################
# AutokeySolver : Decrypts runes by using previously decrypted ones as input
#########################################
class AutokeySolver(RunningKeySolver):
def run(self, data=None):
self.running_key = self.KEY_DATA[:]
super().run(data=data)
def decrypt(self, rune_index, key_index):
rune_index = (rune_index - self.running_key.pop(0)) % 29
self.running_key.append(rune_index)
return rune_index
def key__str__(self):
return self.key__str__basic_runes()

View File

@@ -1,9 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from RuneSolver import VigenereSolver from RuneSolver import VigenereSolver, AffineSolver, AutokeySolver
from RuneText import alphabet, RuneText from RuneText import alphabet, RuneText
import lib as LIB import lib as LIB
SOLVER = VigenereSolver() SOLVER = VigenereSolver()
# SOLVER = AffineSolver()
# SOLVER = AutokeySolver()
SOLVER.input.load(file='_input.txt') SOLVER.input.load(file='_input.txt')
@@ -204,7 +206,7 @@ def command_k(cmd, args): # (k)ey manipulation
elif cmd[1] == 'i': elif cmd[1] == 'i':
SOLVER.KEY_INVERT = not SOLVER.KEY_INVERT SOLVER.KEY_INVERT = not SOLVER.KEY_INVERT
print('set key invert: {}'.format(SOLVER.KEY_INVERT)) print('set key invert: {}'.format(SOLVER.KEY_INVERT))
elif cmd[1] == 'j': elif cmd == 'kj':
args = args.strip('[]') args = args.strip('[]')
pos = [int(x) for x in args.split(',')] if args else [] pos = [int(x) for x in args.split(',')] if args else []
SOLVER.INTERRUPT_POS = pos SOLVER.INTERRUPT_POS = pos