+
+def vigenere_frequency_break(message,
+ metric=norms.euclidean_distance,
+ target_counts=normalised_english_counts,
+ message_frequency_scaling=norms.normalise):
+ """Breaks a Vigenere cipher with frequency analysis
+
+ >>> vigenere_frequency_break(vigenere_encipher(sanitise("It is time to " \
+ "run. She is ready and so am I. I stole Daniel's pocketbook this " \
+ "afternoon when he left his jacket hanging on the easel in the " \
+ "attic."), 'florence')) # doctest: +ELLIPSIS
+ ('florence', 0.077657073...)
+ """
+ best_fit = float("inf")
+ best_key = ''
+ sanitised_message = sanitise(message)
+ for trial_length in range(1, 20):
+ splits = every_nth(sanitised_message, trial_length)
+ key = ''.join([chr(caesar_break(s, target_counts=target_counts)[0] + ord('a')) for s in splits])
+ plaintext = vigenere_decipher(sanitised_message, key)
+ counts = message_frequency_scaling(frequencies(plaintext))
+ fit = metric(target_counts, counts)
+ logger.debug('Vigenere key length of {0} ({1}) gives fit of {2}'.
+ format(trial_length, key, fit))
+ if fit < best_fit:
+ best_fit = fit
+ best_key = key
+ logger.info('Vigenere break best fit with key {0} gives fit '
+ 'of {1} and decrypt starting: {2}'.format(best_key,
+ best_fit, sanitise(
+ vigenere_decipher(message, best_key))[:50]))
+ return best_key, best_fit
+
+
+