Okay, insomnia got me, so I went ahead and implemented it in Python. It appears to work reasonably well, at least it successfully deciphers their test message. You can specify the key by specifying the -c and -p options, which are the settings for the cipher and plain wheels. You should pass a permutation of the uppercase letters as arguments to those if you want to use other than the default key, which matches Rubin’s example.
#!/usr/bin/env python # # _ _ _ # ___| |__ __ _ ___ ___(_)_ __ | |__ ___ _ __ # / __| '_ \ / _` |/ _ \ / __| | '_ \| '_ \ / _ \ '__| # | (__| | | | (_| | (_) | (__| | |_) | | | | __/ | # \___|_| |_|\__,_|\___/ \___|_| .__/|_| |_|\___|_| # |_| # An implementation of John Byrnes' Chaocipher as described in papers # by Moshe Rubin. # # Written by Mark VandeWettering <mvandewettering@gmail.com> # No rights are reserved. No warranties are implied. # import sys import random import string import optparse p = optparse.OptionParser() p.add_option("-d", "--decrypt", action="store_true", dest="decrypt", default=False, help="decrypt instead of encrypt") p.add_option("-p", "--pwheel", dest="pw", default="PTLNBQDEOYSFAVZKGJRIHWXUMC", help="plaintext wheel setting") p.add_option("-c", "--cwheel", dest="cw", default="HXUCZVAMDSLKPEFJRIGTWOBNYQ", help="cipher wheel setting") opts, args = p.parse_args() # initialize the code machine... cnt = 0 def output(c): global cnt sys.stdout.write(c) cnt = cnt + 1 if cnt % 50 == 0: sys.stdout.write('\n') cnt = 0 elif cnt % 5 == 0: sys.stdout.write(' ') class Machine: def __init__(self, cw, pw): self.cw = cw # cipher wheel self.pw = pw # plaintext wheel pass def twizzle(self, idx): self.cw = self.cw[idx:] + self.cw[0:idx] self.cw = list(self.cw[0]) + \ self.cw[2:14] + \ list(self.cw[1]) + \ self.cw[14:] # and the plaintext wheel self.pw = self.pw[idx:] + self.pw[0:idx] self.pw = self.pw[1:] + list(self.pw[0]) self.pw = self.pw[0:2] + \ self.pw[3:14] + \ list(self.pw[2]) + \ self.pw[14:] def encrypt(self, d): # find where it is in the plain text wheel... idx = self.pw.index(d) r = self.cw[idx] self.twizzle(idx) return r def decrypt(self, d): idx = self.cw.index(d) r = self.pw[idx] self.twizzle(idx) return r random.seed(0) machine = Machine(list(opts.cw), list(opts.pw)) for arg in args: try: data = open(arg).read() except IOError, msg: print >> sys.stderr, "%s: %s" % (arg, msg) print >> sys.stderr, "continuing..." data = data.upper() # filter out all the non alpha characters... data = filter(lambda x : x in string.uppercase, data) if opts.decrypt: for d in data: output(machine.decrypt(d)) else: for d in data: output(machine.encrypt(d)) print
Addendum: Here is some cipher text that you can decode with the above program (or your own implementation):
TLMAG OONSK JBJYB QVGDQ CDUNW NMZPL OYCWP CWKWQ RBOYA DSLQB KYCDG XJOLO NKTTL RUZZJ QGJBQ NRQHQ RREUI YIDHZ OMVWZ MVYUF QOGSN NUVYT JGQPS QTBRW FHLTC LVVBP MYYQV
Hi Mark,
Your cipher text decoded fine with a version I wrote in Scheme as a learning exercise.
Regards
Thanks for the confirmation Dave. Glad to see other people are playing with this too. I’m making headway on recovering the exhibit one text, and have some ideas regarding a ciphertext only attack.