- >>> caesar_break('ibxcsyorsaqcheyklxivoexlevmrimwxsfiqevvmihrsasrxliwyrhecjsppsamrkwleppfmergefifvmhixscsymjcsyqeoixlm')
- (4, 0.3186395289018361)
- >>> caesar_break('wxwmaxdgheetgwuxztgptedbgznitgwwhpguxyhkxbmhvvtlbhgteeraxlmhiixweblmxgxwmhmaxybkbgztgwztsxwbgmxgmert')
- (19, 0.4215290123583277)
- >>> caesar_break('yltbbqnqnzvguvaxurorgenafsbezqvagbnornfgsbevpnaabjurersvaquvzyvxrnznazlybequrvfohgriraabjtbaruraprur')
- (13, 0.31602920807545154)
- """
- sanitised_message = sanitise(message)
- best_shift = 0
- best_fit = float("inf")
- for shift in range(26):
- plaintext = caesar_decipher(sanitised_message, shift)
- frequencies = message_frequency_scaling(letter_frequencies(plaintext))
- fit = metric(target_frequencies, frequencies)
- logger.info('Caesar break attempt using key {0} gives fit of {1} and decrypt starting: {2}'.format(shift, fit, plaintext[:50]))
- if fit < best_fit:
- best_fit = fit
- best_shift = shift
- logger.info('Caesar break best fit: key {0} gives fit of {1} and decrypt starting: {2}'.format(best_shift, best_fit, caesar_decipher(sanitised_message, best_shift)[:50]))
- return best_shift, best_fit
-
-def affine_break(message, metric=norms.euclidean_distance, target_frequencies=normalised_english_counts, message_frequency_scaling=norms.normalise):
- """Breaks an affine cipher using frequency analysis
- """
- sanitised_message = sanitise(message)
- best_multiplier = 0
- best_adder = 0
- best_multiply_then_add = True
- best_fit = float("inf")
- for multiply_then_add in [True, False]:
- for multiplier in range(1, 26, 2):
- for adder in range(26):
- plaintext = affine_decipher(sanitised_message, multiplier, adder, multiply_then_add)
- frequencies = message_frequency_scaling(letter_frequencies(plaintext))
- fit = metric(target_frequencies, frequencies)
- logger.info('Affine break attempt using key {0}x+{1} ({2}) gives fit of {3} and decrypt starting: {4}'.format(multiplier, adder, multiply_then_add, fit, plaintext[:50]))
- if fit < best_fit:
- best_fit = fit
- best_multiplier = multiplier
- best_adder = adder
- best_multiply_then_add = multiply_then_add
- logger.info('Affine break best fit with key {0}x+{1} ({2}) gives fit of {3} and decrypt starting: {4}'.format(best_multiplier, best_adder, best_multiply_then_add, best_fit, affine_decipher(sanitised_message, best_multiplier, best_adder, best_multiply_then_add)[:50]))
- return (best_multiplier, best_adder, best_multiply_then_add), best_fit
+ >>> keyword_decipher('rsqr ksqqbds', 'bayes')
+ 'test message'
+ >>> keyword_decipher('rsqr ksqqbds', 'bayes', 0)
+ 'test message'
+ >>> keyword_decipher('lskl dskkbus', 'bayes', 1)
+ 'test message'
+ >>> keyword_decipher('qspq jsppbcs', 'bayes', 2)
+ 'test message'
+ """
+ cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
+ cipher_translation = ''.maketrans(cipher_alphabet, string.ascii_lowercase)
+ return message.lower().translate(cipher_translation)
+
+
+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)
+ 'tubnhirfecooqkwx'
+ >>> scytale_encipher('thequickbrownfox', 6)
+ 'tqcrnxhukof eibwo '
+ >>> scytale_encipher('thequickbrownfox', 7)
+ 'tqcrnxhukof eibwo '
+ """
+ transpositions = [i for i in range(math.ceil(len(message) / rows))]
+ return column_transposition_encipher(message, transpositions,
+ fillcolumnwise=False, emptycolumnwise=True)
+
+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('tubnhirfecooqkwx', 5)
+ 'thequickbrownfox'
+ >>> scytale_decipher('tqcrnxhukof eibwo ', 6)
+ 'thequickbrownfox '
+ >>> scytale_decipher('tqcrnxhukof eibwo ', 7)
+ 'thequickbrownfox '
+ """
+ transpositions = [i for i in range(math.ceil(len(message) / rows))]
+ return column_transposition_decipher(message, transpositions,
+ fillcolumnwise=False, emptycolumnwise=True)