- return (keyword, wrap_alphabet), fit
-
-def scytale_break(message,
- metric=norms.euclidean_distance,
- target_counts=normalised_english_bigram_counts,
- message_frequency_scaling=norms.normalise):
- """Breaks a Scytale cipher
-
- >>> scytale_break('tfeulchtrtteehwahsdehneoifeayfsondmwpltmaoalhikotoere' \
- 'dcweatehiplwxsnhooacgorrcrcraotohsgullasenylrendaianeplscdriioto' \
- 'aek') # doctest: +ELLIPSIS
- (6, 0.092599933059...)
- """
- best_key = 0
- best_fit = float("inf")
- ngram_length = len(next(iter(target_counts.keys())))
- for key in range(1, 20):
- if len(message) % key == 0:
- plaintext = scytale_decipher(message, key)
- counts = message_frequency_scaling(frequencies(
- ngrams(sanitise(plaintext), ngram_length)))
- fit = metric(target_counts, counts)
- logger.debug('Scytale break attempt using key {0} gives fit of '
- '{1} and decrypt starting: {2}'.format(key,
- fit, sanitise(plaintext)[:50]))
- if fit < best_fit:
- best_fit = fit
- best_key = key
- logger.info('Scytale break best fit with key {0} gives fit of {1} and '
- 'decrypt starting: {2}'.format(best_key, best_fit,
- sanitise(scytale_decipher(message, best_key))[:50]))
- return best_key, best_fit
-
-
-def column_transposition_break_mp(message,
- translist=transpositions,
- metric=norms.euclidean_distance,
- target_counts=normalised_english_bigram_counts,
- message_frequency_scaling=norms.normalise,
- chunksize=500):
- """Breaks a column transposition cipher using a dictionary and
- n-gram frequency analysis
- """
- # >>> column_transposition_break_mp(column_transposition_encipher(sanitise( \
- # "It is a truth universally acknowledged, that a single man in \
- # possession of a good fortune, must be in want of a wife. However \
- # little known the feelings or views of such a man may be on his \
- # first entering a neighbourhood, this truth is so well fixed in the \
- # minds of the surrounding families, that he is considered the \
- # rightful property of some one or other of their daughters."), \
- # 'encipher'), \
- # translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
- # (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \
- # (6, 1, 0, 4, 5, 3, 2): ['keyword']}) # doctest: +ELLIPSIS
- # (((2, 0, 5, 3, 1, 4, 6), False), 0.0628106372...)
- # >>> column_transposition_break_mp(column_transposition_encipher(sanitise( \
- # "It is a truth universally acknowledged, that a single man in \
- # possession of a good fortune, must be in want of a wife. However \
- # little known the feelings or views of such a man may be on his \
- # first entering a neighbourhood, this truth is so well fixed in the \
- # minds of the surrounding families, that he is considered the \
- # rightful property of some one or other of their daughters."), \
- # 'encipher'), \
- # translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
- # (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \
- # (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \
- # target_counts=normalised_english_trigram_counts) # doctest: +ELLIPSIS
- # (((2, 0, 5, 3, 1, 4, 6), False), 0.0592259560...)
- # """
- ngram_length = len(next(iter(target_counts.keys())))
- with Pool() as pool:
- helper_args = [(message, trans, columnwise, metric, target_counts, ngram_length,
- message_frequency_scaling)
- for trans in translist.keys() for columnwise in [True, False]]
- # Gotcha: the helper function here needs to be defined at the top level
- # (limitation of Pool.starmap)
- breaks = pool.starmap(column_transposition_break_worker, helper_args, chunksize)
- return min(breaks, key=lambda k: k[1])
-column_transposition_break = column_transposition_break_mp
-
-def column_transposition_break_worker(message, transposition, columnwise, metric, target_counts,
- ngram_length, message_frequency_scaling):
- plaintext = column_transposition_decipher(message, transposition, columnwise=columnwise)
- counts = message_frequency_scaling(frequencies(
- ngrams(sanitise(plaintext), ngram_length)))
- fit = metric(target_counts, counts)
- logger.debug('Column transposition break attempt using key {0} '
- 'gives fit of {1} and decrypt starting: {2}'.format(
- transposition, fit,
- sanitise(plaintext)[:50]))
- return (transposition, columnwise), fit
-
-
-def transposition_break_exhaustive(message):
- best_transposition = ''
- best_pw = -float('inf')
- for keylength in range(1, 21):
- if len(message) % keylength == 0:
- for transposition in permutations(range(keylength)):
- for columnwise in [True, False]:
- plaintext = column_transposition_decipher(message,
- transposition, columnwise=columnwise)
- # pw = Pwords(segment(plaintext))
- pw = sum([log10(bigram_likelihood(b,
- normalised_english_bigram_counts,
- normalised_english_counts))
- for b in ngrams(plaintext, 2)])
- logger.debug('Column transposition break attempt using key {0} {1} '
- 'gives fit of {2} and decrypt starting: {3}'.format(
- transposition, columnwise, pw,
- sanitise(plaintext)[:50]))
- if pw > best_pw:
- best_transposition = transposition
- best_columnwise = columnwise
- best_pw = pw
- return (best_transposition, best_columnwise), best_pw
-
-
-def vigenere_keyword_break(message,
- wordlist=keywords,
- metric=norms.euclidean_distance,
- target_counts=normalised_english_counts,
- message_frequency_scaling=norms.normalise):
- """Breaks a vigenere cipher using a dictionary and
- frequency analysis
-
- >>> vigenere_keyword_break(vigenere_encipher(sanitise('this is a test ' \
- 'message for the vigenere decipherment'), 'cat'), \
- wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
- ('cat', 0.15965224935...)
- """
- best_keyword = ''
- best_fit = float("inf")
- for keyword in wordlist:
- plaintext = vigenere_decipher(message, keyword)
- counts = message_frequency_scaling(frequencies(plaintext))
- fit = metric(target_counts, counts)
- logger.debug('Vigenere break attempt using key {0} '
- 'gives fit of {1} and decrypt starting: {2}'.format(
- keyword, fit,
- sanitise(plaintext)[:50]))
- if fit < best_fit:
- best_fit = fit
- best_keyword = keyword
- logger.info('Vigenere break best fit with key {0} gives fit '
- 'of {1} and decrypt starting: {2}'.format(best_keyword,
- best_fit, sanitise(
- vigenere_decipher(message, best_keyword))[:50]))
- return best_keyword, best_fit
-
-def vigenere_keyword_break_mp(message,
- wordlist=keywords,
- metric=norms.euclidean_distance,
- target_counts=normalised_english_counts,
- message_frequency_scaling=norms.normalise,
- chunksize=500):
- """Breaks a vigenere cipher using a dictionary and
- frequency analysis
-
- >>> vigenere_keyword_break_mp(vigenere_encipher(sanitise('this is a test ' \
- 'message for the vigenere decipherment'), 'cat'), \
- wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
- ('cat', 0.159652249358...)