-
-def vigenere_encipher(message, keyword):
- """Vigenere encipher
-
- >>> vigenere_encipher('hello', 'abc')
- 'hfnlp'
- """
- shifts = [ord(l) - ord('a') for l in sanitise(keyword)]
- pairs = zip(message, cycle(shifts))
- return ''.join([caesar_encipher_letter(l, k) for l, k in pairs])
-
-def vigenere_decipher(message, keyword):
- """Vigenere decipher
-
- >>> vigenere_decipher('hfnlp', 'abc')
- 'hello'
- """
- shifts = [ord(l) - ord('a') for l in sanitise(keyword)]
- pairs = zip(message, cycle(shifts))
- return ''.join([caesar_decipher_letter(l, k) for l, k in pairs])
-
-beaufort_encipher=vigenere_decipher
-beaufort_decipher=vigenere_encipher
-
-
-def transpositions_of(keyword):
- """Finds the transpostions given by a keyword. For instance, the keyword
- 'clever' rearranges to 'celrv', so the first column (0) stays first, the
- second column (1) moves to third, the third column (2) moves to second,
- and so on.
-
- If passed a tuple, assume it's already a transposition and just return it.
-
- >>> transpositions_of('clever')
- (0, 2, 1, 4, 3)
- >>> transpositions_of('fred')
- (3, 2, 0, 1)
- >>> transpositions_of((3, 2, 0, 1))
- (3, 2, 0, 1)
- """
- if isinstance(keyword, tuple):
- return keyword
- else:
- key = deduplicate(keyword)
- transpositions = tuple(key.index(l) for l in sorted(key))
- return transpositions
-
-def pad(message_len, group_len, fillvalue):
- padding_length = group_len - message_len % group_len
- if padding_length == group_len: padding_length = 0
- padding = ''
- for i in range(padding_length):
- if callable(fillvalue):
- padding += fillvalue()
- else:
- padding += fillvalue
- return padding
-
-def column_transposition_encipher(message, keyword, fillvalue=' ',
- fillcolumnwise=False,
- emptycolumnwise=False):
- """Enciphers using the column transposition cipher.
- Message is padded to allow all rows to be the same length.
-
- >>> column_transposition_encipher('hellothere', 'abcdef', fillcolumnwise=True)
- 'hlohr eltee '
- >>> column_transposition_encipher('hellothere', 'abcdef', fillcolumnwise=True, emptycolumnwise=True)
- 'hellothere '
- >>> column_transposition_encipher('hellothere', 'abcdef')
- 'hellothere '
- >>> column_transposition_encipher('hellothere', 'abcde')
- 'hellothere'
- >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=True)
- 'hellothere'
- >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=False)
- 'hlohreltee'
- >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=True)
- 'htehlelroe'
- >>> column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=False)
- 'hellothere'
- >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=True, emptycolumnwise=True)
- 'heotllrehe'
- >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=True, emptycolumnwise=False)
- 'holrhetlee'
- >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=False, emptycolumnwise=True)
- 'htleehoelr'
- >>> column_transposition_encipher('hellothere', 'clever', fillcolumnwise=False, emptycolumnwise=False)
- 'hleolteher'
- >>> column_transposition_encipher('hellothere', 'cleverly')
- 'hleolthre e '
- >>> column_transposition_encipher('hellothere', 'cleverly', fillvalue='!')
- 'hleolthre!e!'
- >>> column_transposition_encipher('hellothere', 'cleverly', fillvalue=lambda: '*')
- 'hleolthre*e*'
- """
- transpositions = transpositions_of(keyword)
- message += pad(len(message), len(transpositions), fillvalue)
- if fillcolumnwise:
- rows = every_nth(message, len(message) // len(transpositions))
- else:
- rows = chunks(message, len(transpositions))
- transposed = [transpose(r, transpositions) for r in rows]
- if emptycolumnwise:
- return combine_every_nth(transposed)
- else:
- return ''.join(chain(*transposed))
-
-def column_transposition_decipher(message, keyword, fillvalue=' ',
- fillcolumnwise=False,
- emptycolumnwise=False):
- """Deciphers using the column transposition cipher.
- Message is padded to allow all rows to be the same length.
-
- >>> column_transposition_decipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=True)
- 'hellothere'
- >>> column_transposition_decipher('hlohreltee', 'abcde', fillcolumnwise=True, emptycolumnwise=False)
- 'hellothere'
- >>> column_transposition_decipher('htehlelroe', 'abcde', fillcolumnwise=False, emptycolumnwise=True)
- 'hellothere'
- >>> column_transposition_decipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=False)
- 'hellothere'
- >>> column_transposition_decipher('heotllrehe', 'clever', fillcolumnwise=True, emptycolumnwise=True)
- 'hellothere'
- >>> column_transposition_decipher('holrhetlee', 'clever', fillcolumnwise=True, emptycolumnwise=False)
- 'hellothere'
- >>> column_transposition_decipher('htleehoelr', 'clever', fillcolumnwise=False, emptycolumnwise=True)
- 'hellothere'
- >>> column_transposition_decipher('hleolteher', 'clever', fillcolumnwise=False, emptycolumnwise=False)
- 'hellothere'
- """
- transpositions = transpositions_of(keyword)
- message += pad(len(message), len(transpositions), '*')
- if emptycolumnwise:
- rows = every_nth(message, len(message) // len(transpositions))
- else:
- rows = chunks(message, len(transpositions))
- untransposed = [untranspose(r, transpositions) for r in rows]
- if fillcolumnwise:
- return combine_every_nth(untransposed)
- else:
- return ''.join(chain(*untransposed))
-
-def scytale_encipher(message, rows, fillvalue=' '):
- """Enciphers using the scytale transposition cipher.
- Message is padded with spaces to allow all rows to be the same length.
-
- >>> scytale_encipher('thequickbrownfox', 3)
- 'tcnhkfeboqrxuo iw '
- >>> scytale_encipher('thequickbrownfox', 4)
- 'tubnhirfecooqkwx'
- >>> scytale_encipher('thequickbrownfox', 5)
- 'tubn hirf ecoo qkwx '
- >>> scytale_encipher('thequickbrownfox', 6)
- 'tqcrnxhukof eibwo '
- >>> scytale_encipher('thequickbrownfox', 7)
- 'tqcrnx hukof eibwo '
- """
- # transpositions = [i for i in range(math.ceil(len(message) / rows))]
- # return column_transposition_encipher(message, transpositions,
- # fillvalue=fillvalue, fillcolumnwise=False, emptycolumnwise=True)
- transpositions = [i for i in range(rows)]
- return column_transposition_encipher(message, transpositions,
- fillvalue=fillvalue, fillcolumnwise=True, emptycolumnwise=False)
-
-def scytale_decipher(message, rows):
- """Deciphers using the scytale transposition cipher.
- Assumes the message is padded so that all rows are the same length.
-
- >>> scytale_decipher('tcnhkfeboqrxuo iw ', 3)
- 'thequickbrownfox '
- >>> scytale_decipher('tubnhirfecooqkwx', 4)
- 'thequickbrownfox'
- >>> scytale_decipher('tubn hirf ecoo qkwx ', 5)
- 'thequickbrownfox '
- >>> scytale_decipher('tqcrnxhukof eibwo ', 6)
- 'thequickbrownfox '
- >>> scytale_decipher('tqcrnx hukof eibwo ', 7)
- 'thequickbrownfox '
- """
- # transpositions = [i for i in range(math.ceil(len(message) / rows))]
- # return column_transposition_decipher(message, transpositions,
- # fillcolumnwise=False, emptycolumnwise=True)
- transpositions = [i for i in range(rows)]
- return column_transposition_decipher(message, transpositions,
- fillcolumnwise=True, emptycolumnwise=False)
-
-
-class PocketEnigma(object):
- """A pocket enigma machine
- The wheel is internally represented as a 26-element list self.wheel_map,
- where wheel_map[i] == j shows that the position i places on from the arrow
- maps to the position j places on.
- """
- def __init__(self, wheel=1, position='a'):
- """initialise the pocket enigma, including which wheel to use and the
- starting position of the wheel.
-
- The wheel is either 1 or 2 (the predefined wheels) or a list of letter
- pairs.
-
- The position is the letter pointed to by the arrow on the wheel.
-
- >>> pe.wheel_map
- [25, 4, 23, 10, 1, 7, 9, 5, 12, 6, 3, 17, 8, 14, 13, 21, 19, 11, 20, 16, 18, 15, 24, 2, 22, 0]
- >>> pe.position
- 0
- """
- self.wheel1 = [('a', 'z'), ('b', 'e'), ('c', 'x'), ('d', 'k'),
- ('f', 'h'), ('g', 'j'), ('i', 'm'), ('l', 'r'), ('n', 'o'),
- ('p', 'v'), ('q', 't'), ('s', 'u'), ('w', 'y')]
- self.wheel2 = [('a', 'c'), ('b', 'd'), ('e', 'w'), ('f', 'i'),
- ('g', 'p'), ('h', 'm'), ('j', 'k'), ('l', 'n'), ('o', 'q'),
- ('r', 'z'), ('s', 'u'), ('t', 'v'), ('x', 'y')]
- if wheel == 1:
- self.make_wheel_map(self.wheel1)
- elif wheel == 2:
- self.make_wheel_map(self.wheel2)
- else:
- self.validate_wheel_spec(wheel)
- self.make_wheel_map(wheel)
- if position in string.ascii_lowercase:
- self.position = ord(position) - ord('a')
- else:
- self.position = position
-
- def make_wheel_map(self, wheel_spec):
- """Expands a wheel specification from a list of letter-letter pairs
- into a full wheel_map.
-
- >>> pe.make_wheel_map(pe.wheel2)
- [2, 3, 0, 1, 22, 8, 15, 12, 5, 10, 9, 13, 7, 11, 16, 6, 14, 25, 20, 21, 18, 19, 4, 24, 23, 17]
- """
- self.validate_wheel_spec(wheel_spec)
- self.wheel_map = [0] * 26
- for p in wheel_spec:
- self.wheel_map[ord(p[0]) - ord('a')] = ord(p[1]) - ord('a')
- self.wheel_map[ord(p[1]) - ord('a')] = ord(p[0]) - ord('a')
- return self.wheel_map
-
- def validate_wheel_spec(self, wheel_spec):
- """Validates that a wheel specificaiton will turn into a valid wheel
- map.
-
- >>> pe.validate_wheel_spec([])
- Traceback (most recent call last):
- ...
- ValueError: Wheel specification has 0 pairs, require 13
- >>> pe.validate_wheel_spec([('a', 'b', 'c')]*13)
- Traceback (most recent call last):
- ...
- ValueError: Not all mappings in wheel specificationhave two elements
- >>> pe.validate_wheel_spec([('a', 'b')]*13)
- Traceback (most recent call last):
- ...
- ValueError: Wheel specification does not contain 26 letters
- """
- if len(wheel_spec) != 13:
- raise ValueError("Wheel specification has {} pairs, requires 13".
- format(len(wheel_spec)))
- for p in wheel_spec:
- if len(p) != 2:
- raise ValueError("Not all mappings in wheel specification"
- "have two elements")
- if len(set([p[0] for p in wheel_spec] +
- [p[1] for p in wheel_spec])) != 26:
- raise ValueError("Wheel specification does not contain 26 letters")
-
- def encipher_letter(self, letter):
- """Enciphers a single letter, by advancing the wheel before looking up
- the letter on the wheel.
-
- >>> pe.set_position('f')
- 5
- >>> pe.encipher_letter('k')
- 'h'
- """
- self.advance()
- return self.lookup(letter)
- decipher_letter = encipher_letter
-
- def lookup(self, letter):
- """Look up what a letter enciphers to, without turning the wheel.
-
- >>> pe.set_position('f')
- 5
- >>> ''.join([pe.lookup(l) for l in string.ascii_lowercase])
- 'udhbfejcpgmokrliwntsayqzvx'
- >>> pe.lookup('A')
- ''
- """
- if letter in string.ascii_lowercase:
- return chr(
- (self.wheel_map[(ord(letter) - ord('a') - self.position) % 26] +
- self.position) % 26 +
- ord('a'))
- else:
- return ''
-
- def advance(self):
- """Advances the wheel one position.
-
- >>> pe.set_position('f')
- 5
- >>> pe.advance()
- 6
- """
- self.position = (self.position + 1) % 26
- return self.position
-
- def encipher(self, message, starting_position=None):
- """Enciphers a whole message.
-
- >>> pe.set_position('f')
- 5
- >>> pe.encipher('helloworld')
- 'kjsglcjoqc'
- >>> pe.set_position('f')
- 5
- >>> pe.encipher('kjsglcjoqc')
- 'helloworld'
- >>> pe.encipher('helloworld', starting_position = 'x')
- 'egrekthnnf'
- """
- if starting_position:
- self.set_position(starting_position)
- transformed = ''
- for l in message:
- transformed += self.encipher_letter(l)
- return transformed
- decipher = encipher
-
- def set_position(self, position):
- """Sets the position of the wheel, by specifying the letter the arrow
- points to.
-
- >>> pe.set_position('a')
- 0
- >>> pe.set_position('m')
- 12
- >>> pe.set_position('z')
- 25
- """
- self.position = ord(position) - ord('a')
- return self.position
-
-