From: Neil Smith Date: Tue, 10 Nov 2020 16:13:57 +0000 (+0000) Subject: Many tests done, still more to come. X-Git-Url: https://git.njae.me.uk/?a=commitdiff_plain;h=3350a462f460e81d96c587466f5b6a88cbba1f7e;p=szyfrow.git Many tests done, still more to come. --- diff --git a/LICENCE b/LICENCE index 9ec50e7..416de5f 100644 --- a/LICENCE +++ b/LICENCE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/szyfrow/amsco.py b/szyfrow/amsco.py index 48ab306..850861b 100644 --- a/szyfrow/amsco.py +++ b/szyfrow/amsco.py @@ -4,7 +4,7 @@ import itertools from szyfrow.support.utilities import * from szyfrow.support.language_models import * -from szyfrow.column_transposition import transpositions, transpositions_of +# from szyfrow.column_transposition import transpositions, transpositions_of # Where each piece of text ends up in the AMSCO transpositon cipher. # 'index' shows where the slice appears in the plaintext, with the slice @@ -16,7 +16,7 @@ class AmscoFillStyle(Enum): same_each_row = 2 reverse_each_row = 3 -def amsco_transposition_positions(message, keyword, +def amsco_positions(message, keyword, fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous, fillcolumnwise=False, @@ -25,7 +25,7 @@ def amsco_transposition_positions(message, keyword, grid shows the index of that slice and the start and end positions of the plaintext that go to make it up. - >>> amsco_transposition_positions(string.ascii_lowercase, 'freddy', \ + >>> amsco_positions(string.ascii_lowercase, 'freddy', \ fillpattern=(1, 2)) # doctest: +NORMALIZE_WHITESPACE [[AmscoSlice(index=3, start=4, end=6), AmscoSlice(index=2, start=3, end=4), @@ -72,64 +72,64 @@ def amsco_transposition_positions(message, keyword, current_fillpattern = list(reversed(current_fillpattern)) return [transpose(r, transpositions) for r in grid] -def amsco_transposition_encipher(message, keyword, +def amsco_encipher(message, keyword, fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row): """AMSCO transposition encipher. - >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(1, 2)) + >>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2)) 'hoteelhler' - >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(2, 1)) + >>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1)) 'hetelhelor' - >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(1, 2)) + >>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2)) 'hotelerelh' - >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(2, 1)) + >>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1)) 'hetelorlhe' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'encode') + >>> amsco_encipher('hereissometexttoencipher', 'encode') 'etecstthhomoerereenisxip' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) 'hetcsoeisterereipexthomn' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) 'hecsoisttererteipexhomen' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) 'heecisoosttrrtepeixhemen' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) 'hxtomephescieretoeisnter' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) 'hxomeiphscerettoisenteer' """ - grid = amsco_transposition_positions(message, keyword, + grid = amsco_positions(message, keyword, fillpattern=fillpattern, fillstyle=fillstyle) ct_as_grid = [[message[s.start:s.end] for s in r] for r in grid] return combine_every_nth(ct_as_grid) -def amsco_transposition_decipher(message, keyword, +def amsco_decipher(message, keyword, fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row): """AMSCO transposition decipher - >>> amsco_transposition_decipher('hoteelhler', 'abc', fillpattern=(1, 2)) + >>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2)) 'hellothere' - >>> amsco_transposition_decipher('hetelhelor', 'abc', fillpattern=(2, 1)) + >>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1)) 'hellothere' - >>> amsco_transposition_decipher('hotelerelh', 'acb', fillpattern=(1, 2)) + >>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2)) 'hellothere' - >>> amsco_transposition_decipher('hetelorlhe', 'acb', fillpattern=(2, 1)) + >>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1)) 'hellothere' - >>> amsco_transposition_decipher('etecstthhomoerereenisxip', 'encode') + >>> amsco_decipher('etecstthhomoerereenisxip', 'encode') 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2)) + >>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2)) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1)) + >>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1)) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2)) + >>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2)) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) 'hereissometexttoencipher' """ - grid = amsco_transposition_positions(message, keyword, + grid = amsco_positions(message, keyword, fillpattern=fillpattern, fillstyle=fillstyle) transposed_sections = [s for c in [l for l in zip(*grid)] for s in c] plaintext_list = [''] * len(transposed_sections) @@ -149,7 +149,7 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], """Breaks an AMSCO transposition cipher using a dictionary and n-gram frequency analysis - >>> amsco_break(amsco_transposition_encipher(sanitise( \ + >>> amsco_break(amsco_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 \ @@ -162,7 +162,7 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \ patterns=[(1, 2)]) # doctest: +ELLIPSIS (((2, 0, 5, 3, 1, 4, 6), (1, 2), ), -709.4646722...) - >>> amsco_break(amsco_transposition_encipher(sanitise( \ + >>> amsco_break(amsco_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 \ @@ -188,7 +188,7 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], def amsco_break_worker(message, transposition, pattern, fillstyle, fitness): - plaintext = amsco_transposition_decipher(message, transposition, + plaintext = amsco_decipher(message, transposition, fillpattern=pattern, fillstyle=fillstyle) fit = fitness(sanitise(plaintext)) return (transposition, pattern, fillstyle), fit diff --git a/szyfrow/autokey.py b/szyfrow/autokey.py index 36ffa85..11bfa6b 100644 --- a/szyfrow/autokey.py +++ b/szyfrow/autokey.py @@ -61,10 +61,8 @@ def autokey_sa_break( message def autokey_sa_break_worker(message, key, - t0, max_iterations, fitness): - + t0, max_iterations, fitness): temperature = t0 - dt = t0 / (0.9 * max_iterations) plaintext = autokey_decipher(message, key) diff --git a/szyfrow/bifid.py b/szyfrow/bifid.py index ed8462f..967bff4 100644 --- a/szyfrow/bifid.py +++ b/szyfrow/bifid.py @@ -28,14 +28,24 @@ def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, >>> bifid_encipher("indiacurry", 'iguana', period=4, fillvalue='x') 'ibnhgaqltzml' """ + + if period: + if not fillvalue: + raise ValueError("fillvalue must be given if period is given") + else: + p_message = message + pad(len(message), period, fillvalue) + else: + p_message = message + translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping) - t_message = message.translate(translation) + t_message = p_message.translate(translation) pairs0 = [f_grid[l] for l in sanitise(t_message)] if period: - chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)] - if len(chunked_pairs[-1]) < period and fillvalue: - chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1])) + # chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)] + # if len(chunked_pairs[-1]) < period and fillvalue: + # chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1])) + chunked_pairs = chunks(pairs0, period, fillvalue=None) else: chunked_pairs = [pairs0] @@ -59,14 +69,23 @@ def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, >>> bifid_decipher("ibnhgaqltzml", 'iguana', period=4) 'indiacurryxx' """ + if period: + if not fillvalue: + raise ValueError("fillvalue must be given if period is given") + else: + p_message = message + pad(len(message), period, fillvalue) + else: + p_message = message + translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping) t_message = message.translate(translation) pairs0 = [f_grid[l] for l in sanitise(t_message)] if period: - chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)] - if len(chunked_pairs[-1]) < period and fillvalue: - chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1])) + # chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)] + # if len(chunked_pairs[-1]) < period and fillvalue: + # chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1])) + chunked_pairs = chunks(pairs0, period, fillvalue=None) else: chunked_pairs = [pairs0] @@ -80,7 +99,7 @@ def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, return cat(r_grid[p] for p in pairs1) -def bifid_break_mp(message, wordlist=keywords, fitness=Pletters, max_period=10, +def bifid_break(message, wordlist=keywords, fitness=Pletters, max_period=10, number_of_solutions=1, chunksize=500): """Breaks a keyword substitution cipher using a dictionary and frequency analysis @@ -110,7 +129,8 @@ def bifid_break_mp(message, wordlist=keywords, fitness=Pletters, max_period=10, return sorted(breaks, key=lambda k: k[1], reverse=True)[:number_of_solutions] def bifid_break_worker(message, keyword, wrap_alphabet, period, fitness): - plaintext = bifid_decipher(message, keyword, wrap_alphabet, period=period) + plaintext = bifid_decipher(message, keyword, wrap_alphabet, + period=period, fillvalue='e') fit = fitness(plaintext) return (keyword, wrap_alphabet, period), fit diff --git a/szyfrow/cadenus.py b/szyfrow/cadenus.py index 0f33ac5..b56500c 100644 --- a/szyfrow/cadenus.py +++ b/szyfrow/cadenus.py @@ -97,7 +97,7 @@ def cadenus_break(message, words=keywords, doubled_letters='vw', fitness=Pbigrams): c = make_cadenus_keycolumn(reverse=True) valid_words = [w for w in words - if max(transpositions_of(w)) <= len(c)] + if len(transpositions_of(w)) == len(message) // 25] with multiprocessing.Pool() as pool: results = pool.starmap(cadenus_break_worker, [(message, w, @@ -107,16 +107,17 @@ def cadenus_break(message, words=keywords, for w in words for s in string.ascii_lowercase for r in [True, False] - if max(transpositions_of(w)) <= len( - make_cadenus_keycolumn( - doubled_letters=doubled_letters, start=s, reverse=r)) + # if max(transpositions_of(w)) <= len( + # make_cadenus_keycolumn( + # doubled_letters=doubled_letters, start=s, reverse=r)) ]) # return list(results) return max(results, key=lambda k: k[1]) def cadenus_break_worker(message, keyword, keycolumn, fitness): - message_chunks = chunks(message, 175) - plaintext = ''.join(cadenus_decipher(c, keyword, keycolumn) for c in message_chunks) + # message_chunks = chunks(message, 175) + # plaintext = ''.join(cadenus_decipher(c, keyword, keycolumn) for c in message_chunks) + plaintext = cadenus_decipher(message, keyword, keycolumn) fit = fitness(plaintext) return (keyword, keycolumn), fit diff --git a/szyfrow/column_transposition.py b/szyfrow/column_transposition.py index d81903e..6d6fe68 100644 --- a/szyfrow/column_transposition.py +++ b/szyfrow/column_transposition.py @@ -4,44 +4,51 @@ from itertools import chain from szyfrow.support.utilities import * from szyfrow.support.language_models import * -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. +# 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. +# 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 - >>> 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 +# transpositions = collections.defaultdict(list) +# for word in keywords: +# transpositions[transpositions_of(word)] += [word] -transpositions = collections.defaultdict(list) -for word in keywords: - transpositions[transpositions_of(word)] += [word] +# def pad(message_len, group_len, fillvalue): +# """Return the padding needed to extend a message to a multiple of group_len +# in length. -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 +# fillvalue can be a function or a literal value. If a function, it is called +# once for each padded character. Use this will fillvalue=random_english_letter +# to pad a message with random letters. +# """ +# 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, @@ -145,7 +152,7 @@ def scytale_encipher(message, rows, fillvalue=' '): # 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)] + transpositions = (i for i in range(rows)) return column_transposition_encipher(message, transpositions, fillvalue=fillvalue, fillcolumnwise=True, emptycolumnwise=False) @@ -177,6 +184,9 @@ def column_transposition_break_mp(message, translist=transpositions, """Breaks a column transposition cipher using a dictionary and n-gram frequency analysis + >>> len(keywords) + 20 + >>> 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 \ diff --git a/szyfrow/keyword_cipher.py b/szyfrow/keyword_cipher.py index b895b3f..446c6c4 100644 --- a/szyfrow/keyword_cipher.py +++ b/szyfrow/keyword_cipher.py @@ -148,7 +148,7 @@ def monoalphabetic_break_hillclimbing(message, cipher_alphabet=None, swap_index_finder=None, fitness=Pletters, chunksize=1): - return simulated_annealing_break(message, + return monoalphabetic_sa_break(message, workers=1, initial_temperature=0, max_iterations=max_iterations, @@ -165,7 +165,7 @@ def monoalphabetic_break_hillclimbing_mp(message, cipher_alphabet=None, swap_index_finder=None, fitness=Pletters, chunksize=1): - return simulated_annealing_break(message, + return monoalphabetic_sa_break(message, workers=workers, initial_temperature=0, max_iterations=max_iterations, @@ -181,7 +181,7 @@ def gaussian_swap_index(a): def uniform_swap_index(a): return random.randrange(26) -def simulated_annealing_break(message, workers=10, +def monoalphabetic_sa_break(message, workers=10, initial_temperature=200, max_iterations=20000, plain_alphabet=None, @@ -215,12 +215,12 @@ def simulated_annealing_break(message, workers=10, initial_temperature, max_iterations, fitness, i)) with multiprocessing.Pool() as pool: - breaks = pool.starmap(simulated_annealing_break_worker, + breaks = pool.starmap(monoalphabetic_sa_break_worker, worker_args, chunksize) return max(breaks, key=lambda k: k[1]) -def simulated_annealing_break_worker(message, plain_alphabet, cipher_alphabet, +def monoalphabetic_sa_break_worker(message, plain_alphabet, cipher_alphabet, swap_index_finder, t0, max_iterations, fitness, logID): diff --git a/szyfrow/railfence.py b/szyfrow/railfence.py index e81b066..ef3d266 100644 --- a/szyfrow/railfence.py +++ b/szyfrow/railfence.py @@ -128,19 +128,6 @@ def railfence_decipher(message, height, fillvalue=''): return cat(c for r in zip_longest(*(down_rows + up_rows), fillvalue='') for c in r) -def railfence_break(message, max_key_length=20, - fitness=Pletters, chunksize=500): - """Breaks a railfence cipher using a matrix of given rank and letter frequencies - - - """ - - sanitised_message = sanitise(message) - results = starmap(worker, [(sanitised_message, i, fitness) - for i in range(2, max_key_length+1)]) - return max(results, key=lambda k: k[1]) - - def railfence_break(message, max_key_length=20, fitness=Pbigrams, chunksize=500): """Breaks a railfence cipher using a range of lengths and diff --git a/szyfrow/support/language_models.py b/szyfrow/support/language_models.py index 6898eb4..95dbeb2 100644 --- a/szyfrow/support/language_models.py +++ b/szyfrow/support/language_models.py @@ -4,13 +4,10 @@ import collections import itertools from math import log10 import os - import importlib.resources as pkg_resources import szyfrow.support.norms -from szyfrow.support.utilities import sanitise - - +from szyfrow.support.utilities import sanitise, deduplicate from szyfrow import language_model_files @@ -36,6 +33,33 @@ with pkg_resources.open_text(language_model_files, 'words.txt') as f: keywords = [line.rstrip() for line in f] +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 + +transpositions = collections.defaultdict(list) +for word in keywords: + transpositions[transpositions_of(word)] += [word] + + def weighted_choice(d): """Generate random item from a dictionary of item counts """ diff --git a/szyfrow/support/plot_frequency_histogram.py b/szyfrow/support/plot_frequency_histogram.py deleted file mode 100644 index d4a9297..0000000 --- a/szyfrow/support/plot_frequency_histogram.py +++ /dev/null @@ -1,15 +0,0 @@ -import matplotlib.pyplot as plt - -def plot_frequency_histogram(freqs, sort_key=None): - x = range(len(freqs)) - y = [freqs[l] for l in sorted(freqs, key=sort_key)] - f = plt.figure() - ax = f.add_axes([0.1, 0.1, 0.9, 0.9]) - ax.bar(x, y, align='center') - ax.set_xticks(x) - ax.set_xticklabels(sorted(freqs, key=sort_key)) - f.show() - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/szyfrow/support/utilities.py b/szyfrow/support/utilities.py index 7319621..7a61ae7 100644 --- a/szyfrow/support/utilities.py +++ b/szyfrow/support/utilities.py @@ -25,6 +25,24 @@ def unpos(number): """Return the letter in the given position in the alphabet (mod 26)""" return chr(number % 26 + ord('a')) +def pad(message_len, group_len, fillvalue): + """Return the padding needed to extend a message to a multiple of group_len + in length. + + fillvalue can be a function or a literal value. If a function, it is called + once for each padded character. Use this with fillvalue=random_english_letter + to pad a message with random letters. + """ + padding_length = group_len - message_len % group_len + if padding_length == group_len: padding_length = 0 + padding = '' + if callable(fillvalue): + for i in range(padding_length): + padding += fillvalue() + else: + padding += fillvalue * padding_length + return padding + def every_nth(text, n, fillvalue=''): """Returns n strings, each of which consists of every nth character, starting with the 0th, 1st, 2nd, ... (n-1)th character @@ -66,10 +84,12 @@ def chunks(text, n, fillvalue=None): ['abcd', 'efgh', 'i!!!'] """ if fillvalue: - padding = fillvalue[0] * (n - len(text) % n) + # padding = fillvalue[0] * (n - len(text) % n) + padding = pad(len(text), n, fillvalue) + padded_text = text + padding else: - padding = '' - return [(text+padding)[i:i+n] for i in range(0, len(text), n)] + padded_text = text + return [(padded_text)[i:i+n] for i in range(0, len(text), n)] def transpose(items, transposition): """Moves items around according to the given transposition diff --git a/tests/test_amsco.py b/tests/test_amsco.py new file mode 100644 index 0000000..a09f701 --- /dev/null +++ b/tests/test_amsco.py @@ -0,0 +1,98 @@ +import pytest +import string + +from szyfrow.amsco import * +from szyfrow.support.utilities import * +from szyfrow.support.language_models import transpositions_of + +def test_positions(): + grid = amsco_positions(string.ascii_lowercase, 'freddy', fillpattern=(1, 2)) + assert grid == [[AmscoSlice(index=3, start=4, end=6), + AmscoSlice(index=2, start=3, end=4), + AmscoSlice(index=0, start=0, end=1), + AmscoSlice(index=1, start=1, end=3), + AmscoSlice(index=4, start=6, end=7)], + [AmscoSlice(index=8, start=12, end=13), + AmscoSlice(index=7, start=10, end=12), + AmscoSlice(index=5, start=7, end=9), + AmscoSlice(index=6, start=9, end=10), + AmscoSlice(index=9, start=13, end=15)], + [AmscoSlice(index=13, start=19, end=21), + AmscoSlice(index=12, start=18, end=19), + AmscoSlice(index=10, start=15, end=16), + AmscoSlice(index=11, start=16, end=18), + AmscoSlice(index=14, start=21, end=22)], + [AmscoSlice(index=18, start=27, end=28), + AmscoSlice(index=17, start=25, end=27), + AmscoSlice(index=15, start=22, end=24), + AmscoSlice(index=16, start=24, end=25), + AmscoSlice(index=19, start=28, end=30)]] + +def test_encipher_message(): + ciphertext = amsco_encipher('hellothere', 'abc', fillpattern=(1, 2)) + assert ciphertext == 'hoteelhler' + + ciphertext = amsco_encipher('hellothere', 'abc', fillpattern=(2, 1)) + assert ciphertext == 'hetelhelor' + + ciphertext = amsco_encipher('hellothere', 'acb', fillpattern=(1, 2)) + assert ciphertext == 'hotelerelh' + + ciphertext = amsco_encipher('hellothere', 'acb', fillpattern=(2, 1)) + assert ciphertext == 'hetelorlhe' + + ciphertext = amsco_encipher('hereissometexttoencipher', 'encode') + assert ciphertext == 'etecstthhomoerereenisxip' + + ciphertext = amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) + assert ciphertext == 'hetcsoeisterereipexthomn' + + ciphertext = amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + assert ciphertext == 'hecsoisttererteipexhomen' + + ciphertext = amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) + assert ciphertext == 'heecisoosttrrtepeixhemen' + + ciphertext = amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) + assert ciphertext == 'hxtomephescieretoeisnter' + + ciphertext = amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + assert ciphertext == 'hxomeiphscerettoisenteer' + + +def test_decipher_message(): + plaintext = 'hereissometexttoencipher' + for key in ['bayes', 'samplekey']: + for fillpattern in [(1, 2), (2, 1)]: + for fillstyle in AmscoFillStyle: + enciphered = amsco_encipher(plaintext, key, + fillpattern=fillpattern, fillstyle=fillstyle) + deciphered = amsco_decipher(enciphered, key, + fillpattern=fillpattern, fillstyle=fillstyle) + assert deciphered == plaintext + + +def test_amsco_break(): + plaintext = 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.''') + expected_key = 'encipher' + expected_fillpattern = (1, 2) + expected_fillsytle = AmscoFillStyle.continuous + expected_score = Pbigrams(plaintext) + ciphertext = amsco_encipher(plaintext, expected_key, + fillpattern=expected_fillpattern, fillstyle=expected_fillsytle) + used_translist = collections.defaultdict(list) + for word in 'encipher fourteen keyword'.split(): + used_translist[transpositions_of(word)] += [word] + + (key, fillpattern, fillstyle), score = amsco_break(ciphertext, + translist=used_translist) + + assert key == transpositions_of(expected_key) + assert fillpattern == expected_fillpattern + assert fillstyle == expected_fillsytle + assert score == pytest.approx(expected_score) diff --git a/tests/test_autokey.py b/tests/test_autokey.py new file mode 100644 index 0000000..c57c08e --- /dev/null +++ b/tests/test_autokey.py @@ -0,0 +1,34 @@ +import pytest +import string + +from szyfrow.autokey import * +from szyfrow.support.utilities import * + + +def test_encipher_message(): + enciphered = autokey_encipher('meetatthefountain', 'kilt') + expected = 'wmpmmxxaeyhbryoca' + assert enciphered == expected + +def test_decipher_message(): + deciphered = autokey_decipher('wmpmmxxaeyhbryoca', 'kilt') + expected = 'meetatthefountain' + assert deciphered == expected + +# def test_break(): +# plaintext = '''hours passed during which jerico tried every trick he +# could think of to prompt some fresh inspiration. he arranged the +# cryptograms chronologically then he arranged them by length. then +# he sorted them by frequency. he doodled on the pile of paper. he +# prowled around the hut, oblivious now to who was looking at him +# and who wasnt.''' +# # expected_key = 'samplekey' +# expected_key = 'abc' +# ciphertext = autokey_encipher(sanitise(plaintext), expected_key) +# expected_score = Ptrigrams(sanitise(plaintext)) +# actual_key, actual_score = autokey_sa_break(ciphertext, +# min_keylength=len(expected_key), max_keylength=len(expected_key), +# workers=10, max_iterations=1000, fitness=Ptrigrams +# ) +# assert expected_key == actual_key +# assert expected_score == pytest.approx(actual_score) diff --git a/tests/test_bifid.py b/tests/test_bifid.py new file mode 100644 index 0000000..1077411 --- /dev/null +++ b/tests/test_bifid.py @@ -0,0 +1,57 @@ +import pytest +import string + +from szyfrow.bifid import * +from szyfrow.support.utilities import * + +def test_grid(): + trans, f_grid, r_grid = bifid_grid('bayes', KeywordWrapAlphabet.from_a, None) + assert len(f_grid) == 25 + assert len(r_grid) == 25 + for l in f_grid: + assert l == r_grid[f_grid[l]] + assert r_grid[1, 1] == 'b' + assert r_grid[2, 1] == 'c' + + +def test_encipher_message(): + assert bifid_encipher("indiajelly", 'iguana') == 'ibidonhprm' + assert bifid_encipher("indiacurry", 'iguana', period=4, fillvalue='x') == 'ibnhgaqltzml' + with pytest.raises(ValueError): + bifid_encipher("indiacurry", 'iguana', period=4) + + +def test_decipher_message(): + plaintext = 'hereissometexttoencipher' + for key in ['bayes', 'samplekey']: + enciphered = bifid_encipher(plaintext, key) + deciphered = bifid_decipher(enciphered, key) + assert deciphered == plaintext + + for key in ['bayes', 'samplekey']: + enciphered = bifid_encipher(plaintext, key, period=5, fillvalue='a') + deciphered = bifid_decipher(enciphered, key, period=5, fillvalue='a') + assert deciphered == plaintext + pad(len(plaintext), 5, 'a') + + +def test_bifid_break(): + plaintext = 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.''') + expected_key = 'encipher' + expected_wrap = KeywordWrapAlphabet.from_a + expected_period = 0 + expected_score = Pletters(plaintext) + ciphertext = bifid_encipher(plaintext, expected_key, + wrap_alphabet=expected_wrap) + + (key, wrap, period), score = bifid_break(ciphertext, + wordlist='encipher fourteen keyword'.split()) + + assert key == expected_key + assert wrap == expected_wrap + assert period == expected_period + assert score == pytest.approx(expected_score) diff --git a/tests/test_cadenus.py b/tests/test_cadenus.py new file mode 100644 index 0000000..c7b48ec --- /dev/null +++ b/tests/test_cadenus.py @@ -0,0 +1,83 @@ +import pytest +import string + +from szyfrow.cadenus import * +from szyfrow.support.utilities import * + +def test_keycolumn(): + assert make_cadenus_keycolumn()['a'] == 0 + assert make_cadenus_keycolumn()['b'] == 1 + assert make_cadenus_keycolumn()['c'] == 2 + assert make_cadenus_keycolumn()['v'] == 21 + assert make_cadenus_keycolumn()['w'] == 21 + assert make_cadenus_keycolumn()['z'] == 24 + assert make_cadenus_keycolumn(doubled_letters='ij', start='b', + reverse=True)['a'] == 1 + assert make_cadenus_keycolumn(doubled_letters='ij', start='b', + reverse=True)['b'] == 0 + assert make_cadenus_keycolumn(doubled_letters='ij', start='b', + reverse=True)['c'] == 24 + assert make_cadenus_keycolumn(doubled_letters='ij', start='b', + reverse=True)['i'] == 18 + assert make_cadenus_keycolumn(doubled_letters='ij', start='b', + reverse=True)['j'] == 18 + assert make_cadenus_keycolumn(doubled_letters='ij', start='b', + reverse=True)['v'] == 6 + assert make_cadenus_keycolumn(doubled_letters='ij', start='b', + reverse=True)['z'] == 2 + + +def test_encipher_message(): + plaintext = sanitise('''Whoever has made a voyage up the Hudson must + remember the Kaatskill mountains. They are a dismembered branch of + the great''') + keycol = make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True) + ciphertext = cadenus_encipher(plaintext, 'wink', keycol) + expected = 'antodeleeeuhrsidrbhmhdrrhnimefmthgeaetakseomehetyaasuvoyegrastmmuuaeenabbtpchehtarorikswosmvaleatned' + assert ciphertext == expected + + plaintext = sanitise('''a severe limitation on the usefulness of the + cadenus is that every message must be a multiple of twenty-five + letters long''') + ciphertext = cadenus_encipher(plaintext, 'easy', keycol) + expected = 'systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtofarenuseieeieltarlmentieetogevesitfaisltngeeuvowul' + assert ciphertext == expected + + +def test_decipher_message(): + plaintext = sanitise('''Whoever has made a voyage up the Hudson must + remember the Kaatskill mountains. They are a dismembered branch of + the great''') + keycol = make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True) + for key in ['wink', 'easy']: + enciphered = cadenus_encipher(plaintext, key, keycol) + deciphered = cadenus_decipher(enciphered, key, keycol) + assert deciphered == plaintext + + +def test_cadenus_break(): + plaintext = 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.''') + expected_key = 'swashbuckling' + expected_score = Ptrigrams(plaintext) + expected_keycol = make_cadenus_keycolumn(doubled_letters='vw', start='a', + reverse=True) + + ciphertext = cadenus_encipher(plaintext, expected_key, expected_keycol) + + # dictionary = ['clearinghouse', 'computerising', 'counterclaims', + # 'housewarmings', 'intravenously', 'liquefactions', 'somersaulting', + # 'sportsmanlike', 'swashbuckling'] + + dictionary = ['swashbuckling'] + + (key, keycol), score = cadenus_break(ciphertext, words=dictionary, + fitness=Ptrigrams) + + assert key == expected_key + assert keycol == expected_keycol + assert score == pytest.approx(expected_score) diff --git a/tests/test_column_transposition.py b/tests/test_column_transposition.py new file mode 100644 index 0000000..31b3417 --- /dev/null +++ b/tests/test_column_transposition.py @@ -0,0 +1,105 @@ +import pytest +import string + +from szyfrow.column_transposition import * +from szyfrow.support.utilities import * + +def test_encipher_message(): + ciphertext = column_transposition_encipher('hellothere', 'abcdef', fillcolumnwise=True) + assert ciphertext == 'hlohr eltee ' + ciphertext = column_transposition_encipher('hellothere', 'abcdef', fillcolumnwise=True, emptycolumnwise=True) + assert ciphertext == 'hellothere ' + ciphertext = column_transposition_encipher('hellothere', 'abcdef') + assert ciphertext == 'hellothere ' + ciphertext = column_transposition_encipher('hellothere', 'abcde') + assert ciphertext == 'hellothere' + ciphertext = column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=True) + assert ciphertext == 'hellothere' + ciphertext = column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=True, emptycolumnwise=False) + assert ciphertext == 'hlohreltee' + ciphertext = column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=True) + assert ciphertext == 'htehlelroe' + ciphertext = column_transposition_encipher('hellothere', 'abcde', fillcolumnwise=False, emptycolumnwise=False) + assert ciphertext == 'hellothere' + ciphertext = column_transposition_encipher('hellothere', 'clever', fillcolumnwise=True, emptycolumnwise=True) + assert ciphertext == 'heotllrehe' + ciphertext = column_transposition_encipher('hellothere', 'clever', fillcolumnwise=True, emptycolumnwise=False) + assert ciphertext == 'holrhetlee' + ciphertext = column_transposition_encipher('hellothere', 'clever', fillcolumnwise=False, emptycolumnwise=True) + assert ciphertext == 'htleehoelr' + ciphertext = column_transposition_encipher('hellothere', 'clever', fillcolumnwise=False, emptycolumnwise=False) + assert ciphertext == 'hleolteher' + ciphertext = column_transposition_encipher('hellothere', 'cleverly') + assert ciphertext == 'hleolthre e ' + ciphertext = column_transposition_encipher('hellothere', 'cleverly', fillvalue='!') + assert ciphertext == 'hleolthre!e!' + ciphertext = column_transposition_encipher('hellothere', 'cleverly', fillvalue=lambda: '*') + assert ciphertext == 'hleolthre*e*' + +def test_decipher_message(): + plaintext = 'hereissometexttoencipher' + for key in ['bayes', 'samplekey']: + for fillcolumnwise in [True, False]: + for emptycolumnwise in [True, False]: + enciphered = column_transposition_encipher(plaintext, key, + fillcolumnwise=fillcolumnwise, emptycolumnwise=emptycolumnwise) + deciphered = column_transposition_decipher(enciphered, key, + fillcolumnwise=fillcolumnwise, emptycolumnwise=emptycolumnwise) + assert deciphered.strip() == plaintext + + +def test_encipher_scytale(): + assert scytale_encipher('thequickbrownfox', 3) == 'tcnhkfeboqrxuo iw ' + assert scytale_encipher('thequickbrownfox', 4) == 'tubnhirfecooqkwx' + assert scytale_encipher('thequickbrownfox', 5) == 'tubn hirf ecoo qkwx ' + assert scytale_encipher('thequickbrownfox', 6) == 'tqcrnxhukof eibwo ' + assert scytale_encipher('thequickbrownfox', 7) == 'tqcrnx hukof eibwo ' + +def test_decipher_scytale(): + plaintext = 'hereissometexttoencipher' + for key in range(3, 8): + enciphered = scytale_encipher(plaintext, key) + deciphered = scytale_decipher(enciphered, key) + assert deciphered.strip() == plaintext + +def test_column_transposition_break(): + plaintext = 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.''') + expected_key = 'encipher' + expected_fill = False + expected_empty = True + expected_score = Pbigrams(plaintext) + ciphertext = column_transposition_encipher(plaintext, expected_key, + fillcolumnwise=expected_fill, emptycolumnwise=expected_empty) + used_translist = collections.defaultdict(list) + for word in 'encipher fourteen keyword'.split(): + used_translist[transpositions_of(word)] += [word] + + (key, fill, empty), score = column_transposition_break_mp(ciphertext, + translist=used_translist) + + assert key == transpositions_of(expected_key) + assert fill == expected_fill + assert empty == expected_empty + assert score == pytest.approx(expected_score) + +def test_scytale_break(): + plaintext = 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.''') + expected_key = 5 + expected_score = Pbigrams(plaintext) + ciphertext = scytale_encipher(plaintext, expected_key) + + key, score = scytale_break_mp(ciphertext) + + assert key == expected_key + assert score == pytest.approx(expected_score) + \ No newline at end of file diff --git a/tests/test_keyword_cipher.py b/tests/test_keyword_cipher.py new file mode 100644 index 0000000..5959902 --- /dev/null +++ b/tests/test_keyword_cipher.py @@ -0,0 +1,121 @@ +import pytest +import string + +from szyfrow.keyword_cipher import * +from szyfrow.support.utilities import * + +def test_cipher_alphabet(): + assert keyword_cipher_alphabet_of('bayes') == 'bayescdfghijklmnopqrtuvwxz' + assert keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_a) == 'bayescdfghijklmnopqrtuvwxz' + assert keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_last) == 'bayestuvwxzcdfghijklmnopqr' + assert keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_largest) == 'bayeszcdfghijklmnopqrtuvwx' + + +def test_encipher_message(): + enciphered = keyword_encipher('test message', 'bayes') + expected = 'rsqr ksqqbds' + assert enciphered == expected + + enciphered = keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_a) + expected = 'rsqr ksqqbds' + assert enciphered == expected + + enciphered = keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_last) + expected = 'lskl dskkbus' + assert enciphered == expected + + enciphered = keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_largest) + expected = 'qspq jsppbcs' + assert enciphered == expected + + +def test_decipher_message(): + plaintext = 'test message' + for key in ['bayes', 'samplekey']: + for wrap in KeywordWrapAlphabet: + enciphered = keyword_encipher(plaintext, key, wrap) + deciphered = keyword_decipher(enciphered, key, wrap) + assert deciphered == plaintext + + +def test_keyword_break(): + plaintext = 'this is a test message for the keyword breaking routine' + expected_key = 'elephant' + expected_wrap = KeywordWrapAlphabet.from_last + ciphertext = keyword_encipher(plaintext, expected_key, expected_wrap) + (key, wrap), score = keyword_break(ciphertext, + wordlist=['cat', 'elephant', 'kangaroo']) + assert key == expected_key + assert wrap == expected_wrap + assert score == pytest.approx(Pletters(sanitise(plaintext))) + +def test_keyword_break_mp(): + plaintext = 'this is a test message for the keyword breaking routine' + expected_key = 'elephant' + expected_wrap = KeywordWrapAlphabet.from_last + ciphertext = keyword_encipher(plaintext, expected_key, expected_wrap) + (key, wrap), score = keyword_break_mp(ciphertext, + wordlist=['cat', 'elephant', 'kangaroo']) + assert key == expected_key + assert wrap == expected_wrap + assert score == pytest.approx(Pletters(sanitise(plaintext))) + + +# def test_simulated_annealing_break(): +# # random.seed(0) +# plaintext = '''You will rejoice to hear that no disaster has accompanied the +# commencement of an enterprise which you have regarded with such evil +# forebodings. I arrived here yesterday, and my first task is to assure +# my dear sister of my welfare and increasing confidence in the success +# of my undertaking. + +# I am already far north of London, and as I walk in the streets of +# Petersburgh, I feel a cold northern breeze play upon my cheeks, which +# braces my nerves and fills me with delight. Do you understand this +# feeling? This breeze, which has travelled from the regions towards +# which I am advancing, gives me a foretaste of those icy climes. +# Inspirited by this wind of promise, my daydreams become more fervent +# and vivid. + +# I try in vain to be persuaded that the pole is the seat of +# frost and desolation; it ever presents itself to my imagination as the +# region of beauty and delight. There, Margaret, the sun is for ever +# visible, its broad disk just skirting the horizon and diffusing a +# perpetual splendour. There—for with your leave, my sister, I will put +# some trust in preceding navigators—there snow and frost are banished; +# and, sailing over a calm sea, we may be wafted to a land surpassing in +# wonders and in beauty every region hitherto discovered on the habitable +# globe. + +# Its productions and features may be without example, as the +# phenomena of the heavenly bodies undoubtedly are in those undiscovered +# solitudes. What may not be expected in a country of eternal light? I +# may there discover the wondrous power which attracts the needle and may +# regulate a thousand celestial observations that require only this +# voyage to render their seeming eccentricities consistent for ever. I +# shall satiate my ardent curiosity with the sight of a part of the world +# never before visited, and may tread a land never before imprinted by +# the foot of man. These are my enticements, and they are sufficient to +# conquer all fear of danger or death and to induce me to commence this +# laborious voyage with the joy a child feels when he embarks in a little +# boat, with his holiday mates, on an expedition of discovery up his +# native river. + +# But supposing all these conjectures to be false, you cannot contest the +# inestimable benefit which I shall confer on all mankind, to the last +# generation, by discovering a passage near the pole to those countries, to +# reach which at present so many months are requisite; or by ascertaining the +# secret of the magnet, which, if at all possible, can only be effected by an +# undertaking such as mine.''' + +# expected_key = keyword_cipher_alphabet_of('abced') +# ciphertext = keyword_encipher(sanitise(plaintext), expected_key) +# expected_score = Ptrigrams(sanitise(plaintext)) +# actual_key, actual_score = monoalphabetic_sa_break(ciphertext, +# plain_alphabet=string.ascii_lowercase, +# cipher_alphabet=string.ascii_lowercase, +# workers=10, max_iterations=1000, +# fitness=Ptrigrams +# ) +# assert expected_key == actual_key +# assert expected_score == pytest.approx(actual_score) diff --git a/tests/test_languge_models.py b/tests/test_languge_models.py new file mode 100644 index 0000000..0e00104 --- /dev/null +++ b/tests/test_languge_models.py @@ -0,0 +1,18 @@ +import pytest +import string + +from szyfrow.support.language_models import * + +def test_transpositions_of(): + assert transpositions_of('clever') == (0, 2, 1, 4, 3) + assert transpositions_of('fred') == (3, 2, 0, 1) + assert transpositions_of((3, 2, 0, 1)) == (3, 2, 0, 1) + +def test_ngrams(): + assert ngrams(sanitise('the quick brown fox'), 2) == ['th', 'he', 'eq', + 'qu', 'ui', 'ic', 'ck', 'kb', 'br', 'ro', 'ow', 'wn', + 'nf', 'fo', 'ox'] + assert ngrams(sanitise('the quick brown fox'), 4) == ['theq', 'hequ', + 'equi', 'quic', 'uick', 'ickb', 'ckbr', 'kbro', 'brow', + 'rown', 'ownf', 'wnfo', 'nfox'] + \ No newline at end of file diff --git a/tests/test_railfence.py b/tests/test_railfence.py new file mode 100644 index 0000000..5e43f46 --- /dev/null +++ b/tests/test_railfence.py @@ -0,0 +1,55 @@ +import pytest +import string + +from szyfrow.railfence import * +from szyfrow.support.utilities import * + + +def test_encipher_message(): + plaintext = 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers' + ciphertext = railfence_encipher(plaintext, 2, fillvalue='!') + expected = 'hlohraateerishsslnpeefetotsigaleccpeselteevsmhatetiiaogicotxfretnrifneihr!' + assert ciphertext == expected + ciphertext = railfence_encipher(plaintext, 3, fillvalue='!') + expected = 'horaersslpeeosglcpselteevsmhatetiiaogicotxfretnrifneihr!!lhateihsnefttiaece!' + assert ciphertext == expected + ciphertext = railfence_encipher(plaintext, 5, fillvalue='!') + expected = 'hresleogcseeemhetaocofrnrner!!lhateihsnefttiaece!!ltvsatiigitxetifih!!oarspeslp!' + assert ciphertext == expected + ciphertext = railfence_encipher(plaintext, 10, fillvalue='!') + expected = 'hepisehagitnr!!lernesge!!lmtocerh!!otiletap!!tseaorii!!hassfolc!!evtitffe!!rahsetec!!eixn!' + assert ciphertext == expected + ciphertext = railfence_encipher(plaintext, 3) + expected = 'horaersslpeeosglcpselteevsmhatetiiaogicotxfretnrifneihrlhateihsnefttiaece' + assert ciphertext == expected + ciphertext = railfence_encipher(plaintext, 5) + expected = 'hresleogcseeemhetaocofrnrnerlhateihsnefttiaeceltvsatiigitxetifihoarspeslp' + assert ciphertext == expected + ciphertext = railfence_encipher(plaintext, 7) + expected = 'haspolsevsetgifrifrlatihnettaeelemtiocxernhorersleesgcptehaiaottneihesfic' + assert ciphertext == expected + + +def test_decipher_message(): + plaintext = 'hellothereavastmeheartiesthisisalongpieceoftextfortestingrailfenceciphers' + for key in range(2, 11): + enciphered = railfence_encipher(plaintext, key) + deciphered = railfence_decipher(enciphered, key) + assert deciphered == plaintext + + +def test_railfence_break(): + plaintext = 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.''') + expected_key = 7 + expected_score = Ptrigrams(plaintext) + ciphertext = railfence_encipher(plaintext, expected_key) + + key, score = railfence_break(ciphertext, fitness=Ptrigrams) + + assert key == expected_key + assert score == pytest.approx(expected_score) diff --git a/tests/test_utilities.py b/tests/test_utilities.py new file mode 100644 index 0000000..9b84283 --- /dev/null +++ b/tests/test_utilities.py @@ -0,0 +1,74 @@ +import pytest +import string + +from szyfrow.support.utilities import * + +def test_pos(): + for n, l in enumerate(string.ascii_lowercase): + assert n == pos(l) + for n, l in enumerate(string.ascii_uppercase): + assert n == pos(l) + with pytest.raises(ValueError): + pos('9') + +def test_unpos(): + for n, l in enumerate(string.ascii_lowercase): + assert l == unpos(n) + assert l == unpos(n + 26) + for n, l in enumerate(string.ascii_uppercase): + assert l.lower() == unpos(n) + with pytest.raises(ValueError): + pos('9') + +def test_pad(): + assert pad(10, 3, '*') == '**' + assert pad(10, 5, '*') == '' + assert pad(10, 3, lambda: '!') == '!!' + +def test_every_nth(): + assert every_nth(string.ascii_lowercase, 5) == ['afkpuz', 'bglqv', 'chmrw', 'dinsx', 'ejoty'] + assert every_nth(string.ascii_lowercase, 1) == ['abcdefghijklmnopqrstuvwxyz'] + assert every_nth(string.ascii_lowercase, 26) == ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] + assert every_nth(string.ascii_lowercase, 5, fillvalue='!') == ['afkpuz', 'bglqv!', 'chmrw!', 'dinsx!', 'ejoty!'] + +def test_combine_every_nth(): + for i in range(1, 27): + assert combine_every_nth(every_nth(string.ascii_lowercase, 5)) == string.ascii_lowercase + + +def test_chunks_on_text(): + assert chunks('abcdefghi', 3) == ['abc', 'def', 'ghi'] + assert chunks('abcdefghi', 4) == ['abcd', 'efgh', 'i'] + assert chunks('abcdefghi', 4, fillvalue='!') == ['abcd', 'efgh', 'i!!!'] + +def test_chunks_on_nontext(): + ns = [1,2,3,4,5,6,7,8,9] + assert chunks(ns, 3) == [[1,2,3],[4,5,6],[7,8,9]] + +def test_transpose(): + assert transpose(['a', 'b', 'c', 'd'], (0,1,2,3)) == ['a', 'b', 'c', 'd'] + assert transpose(['a', 'b', 'c', 'd'], (3,1,2,0)) == ['d', 'b', 'c', 'a'] + assert transpose([10,11,12,13,14,15], (3,2,4,1,5,0)) == [13, 12, 14, 11, 15, 10] + +def test_untranspose(): + assert untranspose(['a', 'b', 'c', 'd'], [0,1,2,3]) == ['a', 'b', 'c', 'd'] + assert untranspose(['d', 'b', 'c', 'a'], [3,1,2,0]) == ['a', 'b', 'c', 'd'] + assert untranspose([13, 12, 14, 11, 15, 10], [3,2,4,1,5,0]) == [10, 11, 12, 13, 14, 15] + +def test_letters(): + assert letters('The Quick') == 'TheQuick' + assert letters('The Quick BROWN fox jumped! over... the (9lazy) DOG') == 'TheQuickBROWNfoxjumpedoverthelazyDOG' + +def test_unaccent(): + assert unaccent('hello') == 'hello' + assert unaccent('HELLO') == 'HELLO' + assert unaccent('héllo') == 'hello' + assert unaccent('héllö') == 'hello' + assert unaccent('HÉLLÖ') == 'HELLO' + +def test_sanitise(): + assert sanitise('The Quick') == 'thequick' + assert sanitise('The Quick BROWN fox jumped! over... the (9lazy) DOG') == 'thequickbrownfoxjumpedoverthelazydog' + assert sanitise('HÉLLÖ') == 'hello' + + \ No newline at end of file