- >>> 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
+ >>> 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 '
+ """
+ cols = round(len(message) / rows)
+ columns = [message[i:i+rows] for i in range(0, cols * rows, rows)]
+ return ''.join([''.join(c) for c in zip_longest(*columns, fillvalue='')])
+
+
+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', 'clever')
+ # 'hleolteher'
+ # >>> column_transposition_encipher('hellothere', 'cleverly', fillvalue='!')
+ # 'hleolthre!e!'
+ # >>> column_transposition_encipher('hellothere', 'clever', columnwise=True)
+ # 'htleehoelr'
+ # """
+ transpositions = transpositions_of(keyword)
+ message += pad(len(message), len(transpositions), fillvalue)
+ if fillcolumnwise:
+ rows = every_nth(message, len(transpositions))
+ else:
+ rows = chunks(mesage, len(transpositions))
+ columns = every_nth(message, len(transpositions), fillvalue=fillvalue)
+ 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=' ',
+ columnwise=False):
+ """Deciphers using the column transposition cipher.
+ Message is padded to allow all rows to be the same length.
+ """
+ # >>> column_transposition_decipher('hleolteher', 'clever')
+ # 'hellothere'
+ # >>> column_transposition_decipher('hleolthre!e!', 'cleverly', fillvalue='?')
+ # 'hellothere!!'
+ # >>> column_transposition_decipher('htleehoelr', 'clever', columnwise=True)
+ # 'hellothere'
+ # """
+ transpositions = transpositions_of(keyword)
+ if columnwise:
+ columns = chunks(message, int(len(message) / len(transpositions)))
+ else:
+ columns = every_nth(message, len(transpositions), fillvalue=fillvalue)
+ untransposed_columns = untranspose(columns, transpositions)
+ return combine_every_nth(untransposed_columns)
+
+
+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