X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=cipher.py;h=59eb3a6223ccea3814256e6f05b5d0c30b000cfa;hb=49dc272d2fc91e7340e56e9e7b96da6ab63514bb;hp=7feaedf90fd289b9fda1da60f0dea9565e1108d1;hpb=d86a492d92a9dfa8f27db824ac67acd641b24714;p=cipher-tools.git diff --git a/cipher.py b/cipher.py index 7feaedf..59eb3a6 100644 --- a/cipher.py +++ b/cipher.py @@ -1,8 +1,9 @@ import string import collections import logging -from itertools import zip_longest, cycle - +import math +from itertools import zip_longest, cycle, chain +from language_models import * logger = logging.getLogger(__name__) logger.addHandler(logging.FileHandler('cipher.log')) @@ -11,32 +12,12 @@ logger.setLevel(logging.WARNING) #logger.setLevel(logging.DEBUG) -modular_division_table = [[0]*26 for x in range(26)] +modular_division_table = [[0]*26 for _ in range(26)] for a in range(26): for b in range(26): c = (a * b) % 26 modular_division_table[b][c] = a -def letters(text): - """Remove all non-alphabetic characters from a text - >>> letters('The Quick') - 'TheQuick' - >>> letters('The Quick BROWN fox jumped! over... the (9lazy) DOG') - 'TheQuickBROWNfoxjumpedoverthelazyDOG' - """ - return ''.join([c for c in text if c in string.ascii_letters]) - -def sanitise(text): - """Remove all non-alphabetic characters and convert the text to lowercase - - >>> sanitise('The Quick') - 'thequick' - >>> sanitise('The Quick BROWN fox jumped! over... the (9lazy) DOG') - 'thequickbrownfoxjumpedoverthelazydog' - """ - # sanitised = [c.lower() for c in text if c in string.ascii_letters] - # return ''.join(sanitised) - return letters(text).lower() def ngrams(text, n): """Returns all n-grams of a text @@ -237,8 +218,9 @@ def affine_decipher_letter(letter, multiplier=1, adder=0, one_based=True): alphabet_start = ord('a') cipher_number = ord(letter) - alphabet_start if one_based: cipher_number += 1 - plaintext_number = ( modular_division_table[multiplier] - [(cipher_number - adder) % 26] ) + plaintext_number = ( + modular_division_table[multiplier] + [(cipher_number - adder) % 26] ) if one_based: plaintext_number -= 1 return chr(plaintext_number % 26 + alphabet_start) else: @@ -343,46 +325,29 @@ def keyword_decipher(message, keyword, wrap_alphabet=0): cipher_translation = ''.maketrans(cipher_alphabet, string.ascii_lowercase) return message.lower().translate(cipher_translation) -def scytale_encipher(message, rows): - """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) - 'tubn hirf ecoo qkwx ' - >>> scytale_encipher('thequickbrownfox', 6) - 'tqcrnxhukof eibwo ' - >>> scytale_encipher('thequickbrownfox', 7) - 'tqcrnx hukof eibwo ' +def vigenere_encipher(message, keyword): + """Vigenere encipher + + >>> vigenere_encipher('hello', 'abc') + 'hfnlp' """ - if len(message) % rows != 0: - message += ' '*(rows - len(message) % rows) - row_length = round(len(message) / rows) - slices = [message[i:i+row_length] - for i in range(0, len(message), row_length)] - return ''.join([''.join(r) for r in zip_longest(*slices, fillvalue='')]) + 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 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('tubn hirf ecoo qkwx ', 5) - 'thequickbrownfox ' - >>> scytale_decipher('tqcrnxhukof eibwo ', 6) - 'thequickbrownfox ' - >>> scytale_decipher('tqcrnx hukof eibwo ', 7) - 'thequickbrownfox ' +def vigenere_decipher(message, keyword): + """Vigenere decipher + + >>> vigenere_decipher('hfnlp', 'abc') + 'hello' """ - 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='')]) + 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): @@ -407,69 +372,138 @@ def transpositions_of(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=' ', - columnwise=False): + 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') + >>> 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', 'clever', columnwise=True) - 'htleehoelr' + >>> column_transposition_encipher('hellothere', 'cleverly', fillvalue=lambda: '*') + 'hleolthre*e*' """ transpositions = transpositions_of(keyword) - columns = every_nth(message, len(transpositions), fillvalue=fillvalue) - transposed_columns = transpose(columns, transpositions) - if columnwise: - return ''.join(transposed_columns) + message += pad(len(message), len(transpositions), fillvalue) + if fillcolumnwise: + rows = every_nth(message, len(message) // len(transpositions)) else: - return combine_every_nth(transposed_columns) + 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=' ', - columnwise=False): + 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('hleolteher', 'clever') + >>> 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('hleolthre!e!', 'cleverly', fillvalue='?') - 'hellothere!!' - >>> column_transposition_decipher('htleehoelr', 'clever', columnwise=True) + >>> column_transposition_decipher('hleolteher', 'clever', fillcolumnwise=False, emptycolumnwise=False) 'hellothere' """ transpositions = transpositions_of(keyword) - if columnwise: - columns = chunks(message, int(len(message) / len(transpositions))) + message += pad(len(message), len(transpositions), '*') + if emptycolumnwise: + rows = every_nth(message, len(message) // len(transpositions)) else: - columns = every_nth(message, len(transpositions), fillvalue=fillvalue) - untransposed_columns = untranspose(columns, transpositions) - return combine_every_nth(untransposed_columns) - + 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 vigenere_encipher(message, keyword): - """Vigenere encipher +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. - >>> vigenere_encipher('hello', 'abc') - 'hfnlp' + >>> 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 ' """ - 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]) + transpositions = [i for i in range(math.ceil(len(message) / rows))] + return column_transposition_encipher(message, transpositions, + fillcolumnwise=False, emptycolumnwise=True) -def vigenere_decipher(message, keyword): - """Vigenere decipher - - >>> vigenere_decipher('hfnlp', 'abc') - 'hello' +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 ' """ - 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 + transpositions = [i for i in range(math.ceil(len(message) / rows))] + return column_transposition_decipher(message, transpositions, + fillcolumnwise=False, emptycolumnwise=True) if __name__ == "__main__":