Python GUI 045 – Transpo – Railfence and Redefence

Along with Complete Columnar, Railfence is one of the easiest transposition types to work with. It gets its name from the vague appearance of an old wooden fence that used rails. The base approach is to pick the number of rows (rails) you want to use, then write the message in a “down-up” zigzag.


(Railfence message)

The message is taken off in rows, and grouped in 5’s by tradition and for readability.

TAPRN SHSNM LAAEC SAIIE AEFIF EEGSX OLME

Decryption is the opposite process, where the message is written on in rows and taken off in the zigzag.

There are several ways to complicate the message to add a little more security. The first is to invert the process, where the plaintext is written on in rows and taken off in the zigzag (that is, we’re using the decryption process on the plaintext).


(Inverted railfence)

TAAER NHEAS IXIAL SFMSP EANLI ECGEO SFME

The second is to add an “offset” to start writing the message on one of the other rails, rather than starting at rail 0.


(Railfence, with an offset of 2)

IAFFE SSXMO ALEMS ETIAE PERIN ESGHN LACA

Of course, we can combine the offset with the invert operation.


(Inverted railfence with an offset of 2)

FEAST ARSAN HEISL XIAFA EMSPN GCLIE EEMO

The number of possible offsets is (# rails – 2) * 2 + 1

Rails Offsets
----- -------
. 3 .... 3
. 4 .... 5
. 5 .... 7
. 6 .... 9
. 7 .... 11

The American Cryptogram Association (ACA) guidelines suggest railfence CONs of 3 to 7 rails, inversion and offsets accepted.

Redefence
The next step is to allow for transposing the rails themselves. This complication creates an all-new cipher type, called Redefence. But really, this is just Railfence plus a variant of Incomplete Columnar. And again, offsets and inversion are allowed.


(Redefence, with a key of 1032)

HSNML AAECS ATAPR NSSXO LMEII EAEFI FEEG

If we look at the total possible combinations of rails (from 3 to 7) and offsets for auto-solving (not including inversion), we get: (1 + 3) + (1 + 5) + (1 + 7) + (1 + 9) + (1 + 11) = 40 trials to find the correct key. That’s even doable by hand (tedious maybe, but doable). Double that if we also need to test for inversion.

As for Redefence, we need to include all of the key permutations for each rail width.

4 * 3! + 6 * 4! + 8 * 5! + 10 * 6! + 12 * 7!

4 * 6 + 6 * 24 + 8 * 120 + 10 * 720 + 12 * 5,040

24 + 144 + 960 + 7,200 + 60,480 = 68,808

Call it 69K trials to find the correct Redefence key. Again, this is trivial, and will take Python not much more than one or two seconds to fully complete. We don’t have to worry about TKinter causing us “program not responding” errors, and we can skip doing time slicing with the auto-solver. This also means that there’s no need for hillclimbing.

Screen cap examples:


(Railfence manual encryption/decryption screen)


(Railfence, with solution)


(Redefence auto-solve screen)


(Redefence, auto-solve screen with solution)

Functions in this file:

railfence_make_frames(): . TKinter GUI interface
railfence_ed(): .......... Manual encrypter/decrypter
railfence_showtemplate():. Display intermediate steps
make_railfence_template(): Form the numeric railfence pattern
make_redefence_template(): Form the numeric redefence pattern
proc_key(): .............. Specialist key parsing
railfence_solve(): ....... Railfence auto-solver
redefence_solve(): ....... Redefence auto-solver

########################
# transpo_railfence.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, \
.... transpo_solver_data, key_to_str
from crypt_utils import show_grams, count_grams_w_crib, sort_first
from inc_utils import inc_perm, make_key

"""
railfence_make_frames()

Standard TKinter GUI code. Check out the screencaps above for what the screen looks like. Common to both Railfence and Redefence.
"""

def railfence_make_frames(root, entries, frames_list, use_font, \
......................... fixed_font, option, ciphertype):

... frame = ttk.Frame(root)

... frame.columnconfigure(0, weight=1)
... frame.columnconfigure(1, weight=3)

... label_parms = [('Title:', 0, 0), ('Key:', 0, 1), \
...................('Input:', 0, 3), ('Output:', 0, 4), \
.................. ('%s:' % option, 0, 2)]

... for label_name, label_col, label_row in label_parms:
....... ttk.Label(frame, text= label_name).grid(column=label_col, /

................. row=label_row, sticky=tk.W)

... entry_parms = [('title', 1, 0, 80), ('key', 1, 1, 20)]

... for param_name, param_col, param_row, param_width in entry_parms:
....... entry_text = tk.StringVar()
....... entry_widget = ttk.Entry(frame, width=param_width, \

...................... textvariable=entry_text, font=use_font)
....... entry_widget.grid(column = param_col, row = param_row, \

......................... sticky=tk.W, padx=5, pady=5)
....... entries[param_name] = entry_text

... invert_var = tk.BooleanVar()
... invert_check = ttk.Checkbutton(frame, variable=invert_var)
... invert_check.grid(column = 1, row = 2, sticky=tk.W, padx=5, \

..................... pady=5)
... entries['invert_var'] = invert_var

... in_area = tk.Text(frame, width=80, height=10)
... in_area.grid(column=1, row=3, sticky=tk.EW, padx=5, pady=5)
... in_area.configure(font= fixed_font)
... entries['in_area'] = in_area

... in_bar = ttk.Scrollbar(frame, orient = tk.VERTICAL)
... in_bar.grid(column=2, row=3, sticky=tk.NS)

... in_area['yscrollcommand'] = in_bar.set
... in_bar['command'] = in_area.yview

... out_area = tk.Text(frame, width=80, height=10)
... out_area.grid(column=1, row=4, sticky=tk.EW, padx=5, pady=5)
... out_area.configure(font= fixed_font)
... entries['out_area'] = out_area

... out_bar = ttk.Scrollbar(frame, orient = tk.VERTICAL)
... out_bar.grid(column=2, row=4, sticky=tk.NS)

... out_area['yscrollcommand'] = out_bar.set
... out_bar['command'] = out_area.yview

... frames_list['cipher_frame'] = frame

... entries['type'] = ciphertype
... frames_list['input_frame'] = frame

... return entries, frames_list, frame

"""
railfence_ed()

Manual Encrypter/Decrypter. Common to both Railfence and Redefence.

action: 0 = Encrypt, 1 = Decrypt
msg: Our plaintext or cipher text
keystr: The user's entry of the key as a string
invert: 0 = onby zigzag, 1 = onby rails
is_railfence: True = Railfence, False = Redefence
"""

def railfence_ed(action, msg, keystr, invert, is_railfence):

# Parse the key string. Return the key and offset as
# separate items.
# Format: R-O, or KKKK-O

... key, offset = proc_key(keystr, is_railfence)
... msglen = len(msg)

... if is_railfence:

# For Railfence, just make the numeric template as
# a list of lists.

....... template = make_railfence_template(msglen, key, offset)

... else:

# For Redefence, turn the key string into a list for use
# by the encrypter section below.
# Then make the numeric template as a list of lists.

....... key = make_key_from_string(key)
....... template = make_redefence_template(msglen, key, offset)

# If the user wants to invert the plaintext onby route
# (onby rows instead of onby zigzag), flip the value of action.

... if invert == 1:
....... action = (action + 1) % 2

# Do the encryption/decryption step.
# 0: Encrypt
# 1: Decrypt

... if action == 0:
....... text_out = ravel(msg, template)
... else:
....... text_out = unravel(msg, template)

# Return the text string results to the calling GUI.

... return text_out

"""
railfence_showtemplate()

Create a string representation of the rail portion of the encryption process to show that it was performed correctly. Needed if I submit a CON to the newsletter. Pretty much the same as the first half of the manual encrypter/decrypter. Look at the screencap above for the example.
"""

def railfence_showtemplate(msg, keystr, invert, is_railfence):

# Prep the return object.
# Parse the key and offset values.

... box = []
... key, offset = proc_key(keystr, is_railfence)

# If we have a Railfence, treat the value of width the user
# entered as the number of rails.
# Otherwise, create the key as a string (i.e. - '0123...')

... if is_railfence:
....... width = key
... else:
....... width = len(key)

... if invert == 1:

# If we're inverting the text, then preprocess it by running
# it through the manual encrypter to encrypt it first. This
# gives us the ciphertext version that we can display below.

....... msg = railfence_ed(0, msg, keystr, invert, is_railfence)

# Calculate the maximum allowed offset for the current width.

... offmax = (width * 2) - 2

# Clean up the offset in case the user entered a value that's
# too large.

... ptr = offset % ((width * 2) - 2)

# Initialize the list direction pointer. If we're in the first
# half of the zigzag (i.e. - incrementing the list pointer),
# use direction = 1. Otherwise (decrementing the pointer) use
# direction = -1.

... direction = 1
... if ptr > width - 2:
....... ptr = offmax - ptr
....... direction = -1

# Build up the image of the zigzag one column at a time, per
# character in the message. Most of the column will be spaces,
# but our current character will be inserted at the point
# given by the pointer. Look at the above screencap for the
# example.

... for ch in msg:
....... column = [' '] * width
....... column[ptr] = ch
....... box.append(column)
....... ptr += direction
....... if ptr == 0 or ptr == width - 1:
........... direction *= -1

# Read the cipher off by rows. If we have Redefence, prepend
# the key to the rows.

... ret = []
... for row in range(width):
....... if is_railfence:
........... temp = []
....... else:
........... temp = ['%s|' % (key[row])]
....... for col in box:
........... temp.append(col[row])
....... ret.append(''.join(temp))

# Join the string list with newlines and return
# the results to the calling GUI.

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

"""
make_railfence_template()

Make a list of lists, where each sublist contains the numeric positions for where the characters will go in the zigzag, with each sublist representing one rail.
"""

def make_railfence_template(msglen, width, offset):

# Initialization.

... box = [[] for i in range(width)]

# Calculate the offset values, and determine the starting
# pointer direction (either up or down the zigzag).

... offmax = (width * 2) - 2
... ptr = offset % offmax
... direction = 1
... if ptr > width - 2:
....... ptr = offmax - ptr
....... direction = -1

# Run through the message and put in the numeric position
# values on each rail.

... for i in range(msglen):
....... box[ptr].append(i)
....... ptr += direction
....... if ptr == 0 or ptr == width - 1:
........... direction *= -1

# Flatten the list of lists into one long list.

... ret = []
... for elem in box:
....... for e in elem:
........... ret.append(e)

# Return the flattened list to the caller.

... return ret

"""
make_redefence_template()

The first section is the same as for making the Railfence template. The last part enciphers the rails based on the user's key list (i.e.: [3,1,0,2]).
"""

def make_redefence_template(msglen, key, offset):

# Initialization.

... width = len(key)
... box = [[] for i in range(width)]

# Prep the zigzag pointer.

... offmax = (width * 2) - 2
... ptr = offset % offmax
... direction = 1
... if ptr > width - 2:
....... ptr = offmax - ptr
....... direction = -1

# Put the numbers in the zigzag.

... for i in range(msglen):
....... box[ptr].append(i)
....... ptr += direction
....... if ptr == 0 or ptr == width - 1:
........... direction *= -1

# Encrypt the rails using the key.

... ret = []
... for i in range(len(key)):
....... k = key.index(i)
....... for b in box[k]:
........... ret.append(b)

# Return the numeric template to the caller.

... return ret

"""
proc_key()

Specialized key parsing. If we have Railfence, then the user enters the key in the format:
rails-offset (i.e. - '5-3').
Split the string on the dash, and return both parts as ints.

For Redefence, the format is:
kkkk-offset (i.e. - '31024-3').
Split the string on the dash. We'll turn the key part into a list of ints in railfence_ed(). For right now, return the offset as an int:
'31024', 3 (will become: [3,1,0,2,4], 3)
"""

def proc_key(s, is_railfence):

# Clean up the key, and split on the dash.

... key = s.strip().upper()
... key = key.replace(' ', '')
... key_list = key.split('-')

... if is_railfence:

# We have Railfence. Take the first item in the list
# and use it for the number of rails.

....... keyval = int(key_list[0])
... else:

# We have Redefence. Store the first element as a key string.

....... keyval = key_list[0]

... offset = 0
... if len(key_list) > 1:

# The user doesn't have to enter an offset. If they don't,
# default to offset = 0. If they do, then we have something
# in element 1. Use that as an int for our offset value.

....... offset = int(key_list[1])

# Return the key value and offset value to the caller.

... return keyval, offset

"""
railfence_solve()

This is our railfence auto-solver.
This one's easy. Just get the min and max widths for our rail constraints. For each value of our rail count, get the maximum number of offsets, and try decrypting the message with the numeric template for that rail-offset combination. Do an n-gram count, and store the potential solutions with the highest counts.
"""

def railfence_solve():

# Grab our transposition data record object.

… global transpo_solver_data

# Read the most common cipher values to variables for speed.

... msg, key, hint, attack_type, option, rails_min, rails_max, \
...... cnt_settings = transpo_solver_data.get()

# Initialization.

... msglen = len(msg)
... have_hint = (len(hint.strip()) != 0)
... ret = []
... strout = ''
... cnt_max, cnt_offset = cnt_settings[0], cnt_settings[1]

# Run through the rail range.

... for rails in range(rails_min, rails_max + 1):

# Calculate the maximum number of offsets, then run
# through the offset range.

....... offset_max = (rails - 1) * 2
....... for offset in range(offset_max):

# Get the encryption order as the numeric template.

........... encrypt_order = make_railfence_template(msglen, rails, /
................................................... offset)
........... if option == 0:

# If option is 0, read the message as offby rails.

............... strout = unravel(msg, encrypt_order)
........... else:

# Otherwise, we're inverting. Read as offby zigzag.

............... strout = ravel(msg, encrypt_order)

# I've modified the gram counter to combine the test for
# the presence of a crib with the counts of n-grams, so
# this is all happening in one statement. If the n-gram
# count is equal to or greater than max count - count offset,
# have_hit will be true. Also, update max count if needed.

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

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

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

# We have a potential solution. Add it as a list to our return object.

............... ret.append([cnt, '%s-%s' % (rails, offset), strout])

# Sort on descending n-gram counts.
# Save the return object to our data record.

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

... return

"""
redefence_solve()

This is our Redefence auto-solver.
This one's almost as easy. Along with all of the stuff in the Railfence solver above, run the permutating key incrementer for each combination of rail count and offset.
"""

def redefence_solve():

# Grab our transposition data record object.

... global transpo_solver_data

# Read the most common data values to variables for speed.

... msg, key, hint, attack_type, option, rails_min, rails_max, \
....... cnt_settings = transpo_solver_data.get()

# Initialization.

... msglen = len(msg)
... have_hint = (len(hint.strip()) != 0)
... ret = []
... strout = ''
... cnt_max, cnt_offset = cnt_settings[0], cnt_settings[1]

# Run through our rail range.

... for rails in range(rails_min, rails_max + 1):

# Run through our offset range.

....... offset_max = (rails - 1) * 2
....... for offset in range(offset_max):

# Initialize our key as a list (i.e. - [0,1,2,3,...]

........... key = make_key(rails)
........... done_inc = False

# Run through our key space.

........... while not done_inc:

# Encrypt our numeric positions using our current key.

............... encrypt_order = make_redefence_template(msglen, key, \
............................... offset)

............... if option == 0:

# Read the message offby rails.

................... strout = unravel(msg, encrypt_order)
............... else:

# Read the message offby zigzag.

................... strout = ravel(msg, encrypt_order)

# Perform our n-gram check.

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

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

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

# We have a potential solution. Add it to the return list.

................... ret.append([cnt, '%s-%s' % (key_to_str(key), \
.............................. offset), strout])

# Increment our key. If we've reached the end of the key space,
# done_inc will be True.

............... done_inc, key = inc_perm(key, rails - 1, rails - 1, \
........................................ rails)

# We've finished testing for all offsets and rails.
# Update the data record object.

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

# Sort the solutions by descending count.
# Save the solutions to the data record object.

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

... return

Next up: No idea

 

Published by The Chief

Who wants to know?

Leave a comment

Design a site like this with WordPress.com
Get started