Cory Wedding Ring
From YobiWiki
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
- Cory's post on Boing Boing
- Pictures of the ring
- Bruce's post on his blog
- Cory's announcement of the winners
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 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()

