Cory Wedding Ring

From YobiWiki
Jump to navigation Jump to search

This is my response to a small challenge proposed by Cory Doctorow and Bruce Schneier.

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()