Python GUI 049 – Transpo – AMSCO

AMSCO is a variation of Columnar that was created by ACA member A. M. Scott. As such, the AMSCO code is very similar to the code for Complete and Incomplete Columnar, so I’m going to keep the write-up this time a little more brief.

In essence, we’re still building up our rectangle in rows across columns, but this time we’ll alternate between single letters and pairs (making this a monome-dinome cipher type). Each row will start with the alternative to the start of the row above it. In other words, we’ll start each row with alternating monome and dinome groupings. As with Columnar, we begin with a key number:

people
514632

.p .e .o .p .l .e
.5 .1 .4 .6 .3 .2
-----------------
.t hi .s is .a ne
xa .m pl .e te .s
.t me .s sa .g .e

HIMME NESEA TEGSP LSTXA TISES A

Solving with pencil and paper is a lot more challenging, and involves trying to find contacts between pairs of letters in the hint word to attempt to establish the key width. After that, you can use a combination of swapping around the columns on lengths of paper as for complete columnar, and “hatting” as with incomplete columnar. This is one reason why I prefer a software solver approach.

Note that you can start writing in the plaintext in the “rectangle” using either the monome or dinome. The manual encryption/decryption screen has an option for doing both.

Screen caps:


(Manual encryption and decryption screen)


(Intermediary step screen)


(Auto-solver screen – for the key incrementer and hillclimber)

You may notice that the manual solver starts numbering the key at 0, while the auto-solver has the same key starting at 1 (13024 versus 24135). This isn’t really an issue as long as the sender and receiver know what to expect and remain consistent. But, it would probably be a good idea in the long run to use the ACA guidelines in all cases, if I intend to submit CONs to the Cryptogram in the future.

Functions in this file:

amsco_ed() .............. Manual encrypt/decrypt code
amsco_showtemplate() .... Show intermediate encryption step
make_template() ......... Make the numeric position template
toggle_md() ............. Toggle the monome/dinome flag
flatten() ............... Position encryption code
amsco_solve() ........... GUI entry point for the auto-solvers
amsco_keyattack_solve() . Incrementing attack on the key
amsco_hillclimber_solve() Hillclimber attack

####################
# transpo_amsco.py #
####################

# Imports

import tkinter as tk
from tkinter import Menu, ttk
from tkinter.messagebox import showinfo, askyesno, showerror
from transpo_utils import make_key_from_string, ravel, unravel, \
.... key_to_str, transpo_solver_data
from crypt_utils import count_grams_w_crib, sort_first
from inc_utils import make_key, inc_perm
import random
from time import time

"""
amsco_ed()

Manual encryption-decryption code. Note that we're using the railfence GUI for the manual data entry screen for AMSCO.
action: 0 = encrypt, 1 = decrypt
msg: Our cleaned up plain or cipher text from the GUI
keystr: Our key from the GUI, not processed yet
start2: 0 = Start with monome, 1 = start with dinome
"""

def amsco_ed(action, msg, keystr, start2):

# Error check for blank key.

... if len(keystr.strip()) == 0:
....... showinfo('Blank key', 'Need to select a key.')
....... return ''

# Convert the key string from a keyword or digit string.
# Create the numeric positions template using the lengths
# of the message and key, plus the monome/dinome starting
# option.

... key = make_key_from_string(keystr.upper())
... template = make_template(len(msg), len(key), start2)

# Encrypt the positions template using the key.

... target = flatten(template, key)

... if action == 0:

# Encrypt the message using the encrypted positions.

....... text_out = ravel(msg, target)

... else:

# Decrypt the message using the encrypted positions.

....... text_out = unravel(msg, target)

... return text_out

"""
amsco_showtemplate()

Return the intermediate encryption step as a string.
See the screen cap above for an example of the output.
"""

def amsco_showtemplate(msg, keystr, start2):

# Initialization.

... msglen = len(msg)
... key = make_key_from_string(keystr.upper())
... keylen = len(key)

# Build up the key header sections.

... header = [''] * 2
... for i in range(len(keystr)):
....... header[0] += '%3s' % keystr[i]
....... header[1] += '%3s' % key[i]
... print(header)

# Duplication of the transposition code, but applied to the
# message, and not the numeric positions.

... ptr1 = 0
... ptr2 = 0
... start_width = start2 + 1
... width = start_width
... col = 0
... row = ''
... col_left, col_right = [], []
... ch_cnt = 0
... max_width = 0

# Build up the unencrypted rectangle.

... while ptr1 < msglen:
....... row += '%3s' % msg[ptr1: ptr1 + width]
....... ptr1 += width
....... col += 1
....... ch_cnt += width
....... if col < keylen:
........... width = toggle_md(width)
........... if ptr1 >= msglen:
............... col_right.append(row)
............... col_left.append(msg[ptr2: ptr2 + ch_cnt])
....... else:
........... col_right.append(row)
........... splice = msg[ptr2: ptr2 + ch_cnt]
........... col_left.append(splice)
........... if len(splice) > max_width:
............... max_width = len(splice)
........... col = 0
........... ptr2 += ch_cnt
........... ch_cnt = 0
........... start_width = toggle_md(start_width)
........... width = start_width
........... row = ''

# Prep the two rectangles, the first one as plain rows,
# the second as space-separated monome-dinome columns.

... text_out = []
... spacer = 3
... for i in range(2):
....... text_out.append(' ' * max_width + ' ' * spacer + header[i])
... text_out.append('-' * max_width + ' ' * \

................... (spacer+1) + '-' * (len(header[0]) - 1))

... for i in range(len(col_right)):
....... l = col_left[i] + ' ' * max_width
....... text_out.append('%s%s%s' % ((l)[:max_width], \

....................... ' ' * spacer, col_right[i]))

# Return the finished string as newline separated.

... return '\n'.join(text_out)

"""
make_template()

Given the message length, the key length and whether we're starting with the monome or dinome, build up the "rectangle" with the position numbers.
"""

def make_template(msglen, keylen, start2):

# Initialize the rectangle, AKA: "box".

... box = [[] for i in range(keylen)]
... temp = []

# Create the numeric positions list.

... for cntr in range(msglen):
....... temp.append(cntr)

# We'll alternate grabbing parts of the "message" in width
# segments, and we'll use start_width to track how we
# start each row. start2 is either 0 or 1, so we need to
# increment that to get start_widths of 1 or 2.

... start_width = start2 + 1
... width = start_width
... ptr = 0
... col = 0

# Run through the "message", slicing the "text" into width
# segments and appending them to the correct "columns"
# in our box matrix.

... while ptr < msglen:
....... for i in range(width):
........... if ptr + i < msglen:
............... box[col].append(temp[ptr + i])
....... ptr += width
....... col += 1

....... if col < keylen: # Not at the end of the key width yet.
........... width = toggle_md(width)
....... else: # At the end of the key width, go to the next row. ........... col = 0
........... start_width = toggle_md(start_width)
........... width = start_width

... return box

"""
toggle_md()

Used to toggle width and start_width between 1 and 2.
"""

def toggle_md(val):
... if val == 1:
....... return 2
... return 1

"""
flatten()

Numeric position encrypter. In most other files I've called this transpose(). I'm using "flatten" here because of the imagery of how the rectangle matrix is being turned into a flat list. But yeah, I should have been consistent and used "transpose." Oh well.
"""

def flatten(template, key):

# Run through the template and append each column
# to ret in key digit order.
# Return as a flat list.

... ret = []
... for i in range(len(key)):
....... ptr = key.index(i)
....... for val in template[ptr]:
........... ret.append(val)

... return ret

"""
amsco_solve()

Entry point from the GUI for the auto-solvers.
"""

def amsco_solve():
... global transpo_solver_data

... if transpo_solver_data.attack_type == 0:
....... amsco_keyattack_solve()

... else:
....... amsco_hillclimber_solve()

"""
amsco_keyattack_solve()

Incrementing key attack.
"""

def amsco_keyattack_solve():

# Access the record data object.

... global transpo_solver_data

# Load the most common data record variables for speed.

... msg, key, hint, attack_type, start2, min_width, max_width, \
........ cnt_settings = transpo_solver_data.get()

# Initialization.

... cnt_max, cnt_offset = cnt_settings[0], cnt_settings[1]
... ret = []
... timer = time()
... done = False
... have_hint = (len(hint.strip()) != 0)
... msglen = len(msg)
... keylen = len(key)

# Make the numerical positions matrix.

... template = make_template(msglen, keylen, start2)

... while not done:

# Encrypt the numeric matrix with the current key.
# Decrypt the message with the current encrypted positions.

....... encrypt_order = flatten(template, key)
....... strout = unravel(msg, encrypt_order)

# Get the n-gram count for this message and update
# max count if necessary.

....... have_hit, cnt_max, cnt = \
................. count_grams_w_crib(strout, hint, have_hint, \

.................................... cnt_max, cnt_offset)

# If we have a potential solution, save it to the list.

....... if have_hit:
........... ret.append([cnt, key_to_str(key), strout])

# Increment the key.

....... done_inc, key = inc_perm(key, keylen - 1, keylen - 1, keylen)

....... if done_inc:

# We've reached the end of the current width key space.
# Go to the next width.

........... min_width += 1
........... if min_width > max_width:

# We've reached the end of the width test range.
# Save the settings to the data record and quit out.

............... transpo_solver_data.done = True
............... transpo_solver_data.key = key
............... transpo_solver_data.ret_msg = 'Reached end of list'
............... done = True

........... else:

# We're still testing. Init the key for the new width.

............... key = make_key(min_width)
............... keylen = len(key)
............... template = make_template(msglen, keylen, start2)

....... if time() - timer > 1:

# We've timed out for the current time slice.
# Update the data record and quit out.

........... done = True
........... transpo_solver_data.key = key
........... transpo_solver_data.width_min = min_width
........... transpo_solver_data.cnt[0] = cnt_max
........... transpo_solver_data.ret_msg = key_to_str(key, 0)

# Sort the potential solutions on decreasing n-gram
# count and add the list to the data record.

... ret.sort(key=sort_first, reverse=True)
... transpo_solver_data.solutions = ret

... return

"""
amsco_hillclimber_solve()

Hillclimber attack.
"""

def amsco_hillclimber_solve():

# Access the record data object.

... global transpo_solver_data

# Load the most common data record variables for speed.

... msg, loop_cnt, hint, attack_type, start2, min_width, max_width, \
........ cnt_settings = transpo_solver_data.get()

# Load the hillcounter settings and the currently
# obtained potential solutions.

... hill = transpo_solver_data.hill
... solutions = transpo_solver_data.solutions

# Initialization.

... cnt_max = 0
... hill_loop_max, hill_try_max = hill[1], hill[2]

... ret = []
... timer = time()
... done = False
... have_hint = (len(hint.strip()) != 0)
... msglen = len(msg)
... key = make_key(min_width)
... keylen = min_width

# Make the numerical positions matrix.

... template = make_template(msglen, keylen, start2)

... while not done:

# Set up the swap try var and randomize the key.

....... hill_try_cnt = 0
....... random.shuffle(key)
....... done_inner = False

....... while not done_inner:

# Swap two different random digits in the key.

........... d1 = random.randint(0, min_width - 1)
........... d2 = d1
........... while d1 == d2:
............... d2 = random.randint(0, min_width - 1)

........... key[d1], key[d2] = key[d2], key[d1]

# Encrypt the numeric positions with the new key.
# Decrypt the message with the new positions.

........... encrypt_order = flatten(template, key)
........... strout = unravel(msg, encrypt_order)

# Get the n-gram count for this message and update
# max count if necessary.

........... have_hit, cnt_max, cnt = \
............... count_grams_w_crib(strout, hint, have_hint, \

.................................. cnt_max, -1)

........... if have_hit:

# We have a potential solution. Turn the key list to a string,
# starting key numbering at 1.
# Save the key and unique solutions to the dict object.

............... strkey = key_to_str(key, 1)
............... hill_try_cnt = 0
............... if strkey not in solutions:
................... solutions[strkey] = (cnt, strout)

........... else:

# No solution. Swap the key digits back and increment
# the swap try counter.

............... key[d1], key[d2] = key[d2], key[d1]
............... hill_try_cnt += 1

........... if hill_try_cnt > hill_try_max:

# The try counter has hit the try max limit.
# Increment the loop counter.

............... done_inner = True
............... loop_cnt += 1

............... if loop_cnt >= hill_loop_max:

# The loop counter hit the loop max limit.
# Stop testing and update the data record object.

................... done = True
................... transpo_solver_data.done = True

# Convert the dict object to a list

................... holder = []
................... for index in solutions:
....................... holder.append([solutions[index][0], index, \
...................................... solutions[index][1]])

# Sort the list on descending count and
# add it to the data record object. Quit out.

................... holder.sort(key=sort_first, reverse=True)
................... transpo_solver_data.solutions = holder

................... return

........... if time() - timer > 1:

# We've timed out for the current time slice.
# Update the data record and quit out.

............... done_inner = True
............... done = True
............... transpo_solver_data.key = loop_cnt
............... transpo_solver_data.solutions = solutions

... return

Next up: Nihilist Transposition Solver

Published by The Chief

Who wants to know?

Leave a comment

Design a site like this with WordPress.com
Get started