Cory Wedding Ring
This is my response to a small challenge proposed by Cory Doctorow and Bruce Schneier.
UPDATE: I'm the runner-up of the challenge, woo-hooo!
The first prize goes to Chris Smith with The Fidget Protocol (zip), congrats to him!
Links
Response
Hello,
I wrote an example of polyalphabetic substitution cipher, using your ring. Maybe it's worthless but at least you'll get a Python code easily hackable to try out the ciphers of other concurrents as well ;-)
I tried to have some features such as
- arbitrary long secret key
- autokeying: the secret doesn't repeat but its transformation depends on the message
- not trivial to recover the secret from a plaintext attack (but slightly harder)
Globally the idea is that out of the 3 rings, for each step, one ring will be designed as the message ring and used to substitute a char of the message by the char of the ring below (mod 3: below the last is the first) and one ring (can happen to be the same) will be designed as the secret ring and used to substitute a char of the secret by the one below. The secret is extended by appending the substitution of the first secret char at each step, think about circular buffer. Dots are used to choose the secret ring and the message ring for the next step. Dots on the message char will design the secret ring and vice versa.
Here is a more formal description of the cipher:
Vocabulary:
- first ring = the one with dots above, none, below, above...
- second ring = the middle one
- third ring = ...
- ring above/below another one = modulo 3:
- above first ring -> third ring
- below first ring -> second ring
- etc
- align a ring with a char = align the char on that ring with the "A" of the ring *above*
- dots indicate a ring = dots on a specific char will tell which ring to consider:
- actually several interpretations can be chosen
- absolute -> above = first ring / none = second ring / below = third ring
- relative to current -> A = ring above / N = same ring / B = ring below
- relative to the other ring secret<>message ->
A = ring above / N = same ring / B = ring below
- substitute a char = replace with char of the ring *below*
- secret ring = ring where to look for the secret char
- message ring = ring where to look for the message char
Init:
- Start with rings aligned to a chosen position
- Pick up a secret string (password)
- Choose a ring as the secret ring
These 3 elements constitute the secret key material
Step:
- Align secret ring with first char of the secret
- Dots on secret char will indicate the message ring
- Substitute message char
- Align message ring with first char of the message
- Dots on message char will indicate the secret ring
- Substitute secret char
Loop on steps...
And now the [{{#file:corycipher.py}} Python code]:
#!/usr/bin/env python
# -*- coding: latin1 -*-
# Copyright Philippe Teuwen 2008, CC-BY-SA license
class cipher:
def __init__(self, secret, message,
mode=0, Sring_idx=0, Mring_idx=0, shifts=(0,0,0),
dotmode=0):
"""Creates an initialized instance of the cipher
secret: string which will serve as a key
message: string to encode/decode
mode: [0]=encryption / 1=decryption
Sring_idx: [0]/1/2 which ring will be the secret ring at start
Mring_idx: [0]/1/2 which ring will be the message ring at start
this is only relevant when dots indicate a relative ring
i.e. dotmode=1
shifts: ([0]..25,[0]..25,[0]..25) initial positions of the rings
only relative positions are important, (1,2,3)==(4,5,6)
The following parameters are to try out different ciphers
dotmode: [0]: absolute above=ring0 none=ring1 below=ring2
1 : relative above=ring-1 none=ring below=ring+1
2 : relative to the other ring (secretring<>messagering)
Example:
>>> c=cipher(secret='CORY', message='HELLOWORLD', shifts=(1,2,3))
>>> c.step()
>>> c
FGHIJKLMNOPQRSTUVWXYZABCDE
CDEFGHIJKLMNOPQRSTUVWXYZAB
KLMNOPQRSTUVWXYZABCDEFGHIJ <-Sring <-Mring
<BLANKLINE>
S: C|ORYX
M: H|ELLOWORLD
C: J|
>>> c.step()
>>> c
FGHIJKLMNOPQRSTUVWXYZABCDE
GHIJKLMNOPQRSTUVWXYZABCDEF <-Mring
QRSTUVWXYZABCDEFGHIJKLMNOP <-Sring
<BLANKLINE>
S: CO|RYXD
M: HE|LLOWORLD
C: JS|
>>> c.run()
'JSTCLUKDWF'
>>> c
LMNOPQRSTUVWXYZABCDEFGHIJK
YZABCDEFGHIJKLMNOPQRSTUVWX <-Sring
MNOPQRSTUVWXYZABCDEFGHIJKL <-Mring
<BLANKLINE>
S: CORYXDSJGC|ORPQ
M: HELLOWORLD|
C: JSTCLUKDWF|
>>> c=cipher(secret='CORY', message='JSTCLUKDWF', mode=1, shifts=(1,2,3))
>>> c.run()
'HELLOWORLD'
"""
self.rings=[ring(1, shifts[0]),
ring(2, shifts[1]),
ring(3, shifts[2])]
self.Sring_idx=Sring_idx
self.Mring_idx=Mring_idx
self.mode=mode
self.dotmode=dotmode
# Those Xhist are just for keeping history of processed chars:
self.Shist=string('')
self.Mhist=string('')
self.Chist=string('')
self.S=string(secret)
if self.mode==0:
# encryption
self.M=string(message)
self.C=string('')
else:
# decryption
self.M=string('')
self.C=string(message)
self.steps=0
def __repr__(self):
"""Comprehensive representation of the system:
its 3 rings with flags on secretring & messagering
and its secret string, message string and enciphered string
"""
s=''
for i in range(0,3):
s+=repr(self.rings[i])
if self.Sring_idx == i:
s+=' <-Sring'
if self.Mring_idx == i:
s+=' <-Mring'
s+='\n'
s+='\nS: '+repr(self.Shist)+'|'+repr(self.S)
if self.mode==0:
s+='\nM: '+repr(self.Mhist)+'|'+repr(self.M)
s+='\nC: '+repr(self.C)+'|'
else:
s+='\nM: '+repr(self.M)+'|'
s+='\nC: '+repr(self.Chist)+'|'+repr(self.C)
return s
def Sring(self):
"""The secret ring"""
return self.rings[self.Sring_idx]
def aboveSring(self):
"""The ring above the secret ring (mod 3)
So the one used for aligning the secret ring"""
return self.rings[(self.Sring_idx-1)%3]
def belowSring(self):
"""The ring below the secret ring (mod 3)
So the one used for substitution with the secret ring"""
return self.rings[(self.Sring_idx+1)%3]
def Mring(self):
"""The message ring"""
return self.rings[self.Mring_idx]
def aboveMring(self):
"""The ring above the message ring (mod 3)
So the one used for aligning the message ring"""
return self.rings[(self.Sring_idx-1)%3]
def belowMring(self):
"""The ring below the message ring (mod 3)
So the one used for substitution with the message ring"""
return self.rings[(self.Mring_idx+1)%3]
def step(self):
"""Performs one step to encrypt/decrypt"""
self.steps+=1
Schar=self.S.pop()
self.Shist.push(Schar)
# Align secret ring with first char of the secret
shift=(self.Sring().ord(Schar)-self.aboveSring().ord('A'))%26
self.Sring().rotate(shift)
# Dots on secret char will indicate the message ring
if self.dotmode == 0:
# absolute:
self.Mring_idx=self.Sring().dot(Schar)
elif self.dotmode == 1:
# relative:
self.Mring_idx=(self.Mring_idx+self.Sring().dot(Schar)-1)%3
else:
# relative to the other:
self.Mring_idx=(self.Sring_idx+self.Sring().dot(Schar)-1)%3
# Substitute message char
if self.mode==0:
# encryption
Mchar=self.M.pop()
self.Mhist.push(Mchar)
Cchar=self.belowMring().char(self.Mring().ord(Mchar))
self.C.push(Cchar)
else:
# decryption
Cchar=self.C.pop()
self.Chist.push(Cchar)
Mchar=self.Mring().char(self.belowMring().ord(Cchar))
self.M.push(Mchar)
# Align message ring with first char of the message
shift=(self.Mring().ord(Mchar)-self.aboveMring().ord('A'))%26
self.Mring().rotate(shift)
# Dots on message char will indicate the secret ring
if self.dotmode == 0:
# absolute:
self.Sring_idx=self.Mring().dot(Mchar)
elif self.dotmode == 1:
# relative:
self.Mring_idx=(self.Mring_idx+self.Sring().dot(Schar)-1)%3
else:
# relative to the other:
self.Mring_idx=(self.Sring_idx+self.Sring().dot(Schar)-1)%3
# Substitute secret char
self.S.push(self.belowSring().char(self.Sring().ord(Schar)))
def run(self):
"""Performs the required nr of steps to encrypt/decrypt
Takes into account the nr of steps already done, if any"""
for i in range(0,self.M.len()+self.C.len()-self.steps):
self.step()
if self.mode==0:
return repr(self.C)
else:
return repr(self.M)
class string:
def __init__(self, string):
"""Simple string class with operators to queue chars"""
self.it=string.upper().replace(' ','')
def pop(self):
"""Pops out the first char of the string"""
first=self.it[0]
self.it=self.it[1:]
return first
def push(self, char):
"""Pushes a char at the end of the string"""
self.it=self.it+char
def len(self):
"""Returns the length of the string"""
return len(self.it)
def __repr__(self):
return self.it
class ring:
def __init__(self, period, shift=0):
"""Object representing one ring
period: when to change dots
set to 1 for first ring, 2 for second, 3 for third
shift: initial rotation of the ring, if any, otherwise start from 'A'
"""
self.L=[chr(65+i) for i in range(0,26)]
self.D=dict([(chr(65+i),(i//period)%3) for i in range(0,26)])
# Trying alternate dots: 0,1,2,0,1,2 / 1,2,0,1,2,0 / 2,0,1,2,0,1
# self.D=dict([(chr(65+i),(i+period)%3) for i in range(0,26)])
self.rotate(shift)
def rotate(self,n):
"""Rotate the ring towards the left by n
So 'ABC...' rotated by n=1 becomes 'BCD...' """
self.L.extend(self.L[:n])
del self.L[:n]
def dot(self, char):
"""Returns the dot of a char of the ring
0 = dot above
1 = no dot
2 = dot below
"""
return self.D[char]
def ord(self, char='A'):
"""Returns position of a char, of 'A' by default"""
return self.L.index(char)
def char(self, index=0):
"""Returns the char at a given position, at the first by default"""
return self.L[index]
def __repr__(self):
return ''.join(self.L)
def _test():
import doctest
doctest.testmod()
if __name__ == "__main__":
_test()