Module szyfrow.bifid
Enciphering and deciphering using the Bifid cipher. Also attempts to break messages that use a Bifid cipher.
Expand source code
"""Enciphering and deciphering using the [Bifid cipher](https://en.wikipedia.org/wiki/Bifid_cipher).
Also attempts to break messages that use a Bifid cipher.
"""
import multiprocessing
from szyfrow.support.utilities import *
from szyfrow.support.language_models import *
from szyfrow.keyword_cipher import KeywordWrapAlphabet, keyword_cipher_alphabet_of
def bifid_grid(keyword, wrap_alphabet, letter_mapping):
"""Create the grids for a Bifid cipher
"""
cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
if letter_mapping is None:
letter_mapping = {'j': 'i'}
translation = ''.maketrans(letter_mapping)
cipher_alphabet = cat(collections.OrderedDict.fromkeys(cipher_alphabet.translate(translation)))
f_grid = {k: ((i // 5) + 1, (i % 5) + 1)
for i, k in enumerate(cipher_alphabet)}
r_grid = {((i // 5) + 1, (i % 5) + 1): k
for i, k in enumerate(cipher_alphabet)}
return translation, f_grid, r_grid
def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
letter_mapping=None, period=None, fillvalue=None):
"""Bifid cipher
>>> bifid_encipher("indiajelly", 'iguana')
'ibidonhprm'
>>> bifid_encipher("indiacurry", 'iguana', period=4)
'ibnhgaqltm'
>>> 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 = 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 = chunks(pairs0, period, fillvalue=None)
else:
chunked_pairs = [pairs0]
pairs1 = []
for c in chunked_pairs:
items = sum(list(list(i) for i in zip(*c)), [])
p = [(items[i], items[i+1]) for i in range(0, len(items), 2)]
pairs1 += p
return cat(r_grid[p] for p in pairs1)
def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
letter_mapping=None, period=None, fillvalue=None):
"""Decipher with bifid cipher
>>> bifid_decipher('ibidonhprm', 'iguana')
'indiaielly'
>>> bifid_decipher("ibnhgaqltm", 'iguana', period=4)
'indiacurry'
>>> 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 = chunks(pairs0, period, fillvalue=None)
else:
chunked_pairs = [pairs0]
pairs1 = []
for c in chunked_pairs:
items = [j for i in c for j in i]
gap = len(c)
p = [(items[i], items[i+gap]) for i in range(gap)]
pairs1 += p
return cat(r_grid[p] for p in pairs1)
def bifid_break(message, wordlist=None, fitness=Pletters, max_period=10,
number_of_solutions=1, chunksize=500):
"""Breaks a keyword substitution cipher using a dictionary and
frequency analysis
If `wordlist` is not specified, use
[`szyfrow.support.langauge_models.keywords`](support/language_models.html#szyfrow.support.language_models.keywords).
>>> bifid_break(bifid_encipher('this is a test message for the ' \
'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
(('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...)
>>> bifid_break(bifid_encipher('this is a test message for the ' \
'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
wordlist=['cat', 'elephant', 'kangaroo'], \
number_of_solutions=2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
[(('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...),
(('elephant', <KeywordWrapAlphabet.from_largest: 3>, 0), -52.834575011...)]
"""
if wordlist is None:
wordlist = keywords
with multiprocessing.Pool() as pool:
helper_args = [(message, word, wrap, period, fitness)
for word in wordlist
for wrap in KeywordWrapAlphabet
for period in range(max_period+1)]
# Gotcha: the helper function here needs to be defined at the top level
# (limitation of Pool.starmap)
breaks = pool.starmap(bifid_break_worker, helper_args, chunksize)
if number_of_solutions == 1:
return max(breaks, key=lambda k: k[1])
else:
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, fillvalue='e')
fit = fitness(plaintext)
return (keyword, wrap_alphabet, period), fit
if __name__ == "__main__":
import doctest
Functions
def bifid_break(message, wordlist=None, fitness=<function Pletters>, max_period=10, number_of_solutions=1, chunksize=500)
-
Breaks a keyword substitution cipher using a dictionary and frequency analysis
If
wordlist
is not specified, useszyfrow.support.langauge_models.keywords
.>>> bifid_break(bifid_encipher('this is a test message for the ' 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS (('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...) >>> bifid_break(bifid_encipher('this is a test message for the ' 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), wordlist=['cat', 'elephant', 'kangaroo'], number_of_solutions=2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE [(('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...), (('elephant', <KeywordWrapAlphabet.from_largest: 3>, 0), -52.834575011...)]
Expand source code
def bifid_break(message, wordlist=None, fitness=Pletters, max_period=10, number_of_solutions=1, chunksize=500): """Breaks a keyword substitution cipher using a dictionary and frequency analysis If `wordlist` is not specified, use [`szyfrow.support.langauge_models.keywords`](support/language_models.html#szyfrow.support.language_models.keywords). >>> bifid_break(bifid_encipher('this is a test message for the ' \ 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \ wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS (('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...) >>> bifid_break(bifid_encipher('this is a test message for the ' \ 'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \ wordlist=['cat', 'elephant', 'kangaroo'], \ number_of_solutions=2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE [(('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...), (('elephant', <KeywordWrapAlphabet.from_largest: 3>, 0), -52.834575011...)] """ if wordlist is None: wordlist = keywords with multiprocessing.Pool() as pool: helper_args = [(message, word, wrap, period, fitness) for word in wordlist for wrap in KeywordWrapAlphabet for period in range(max_period+1)] # Gotcha: the helper function here needs to be defined at the top level # (limitation of Pool.starmap) breaks = pool.starmap(bifid_break_worker, helper_args, chunksize) if number_of_solutions == 1: return max(breaks, key=lambda k: k[1]) else: return sorted(breaks, key=lambda k: k[1], reverse=True)[:number_of_solutions]
def bifid_break_worker(message, keyword, wrap_alphabet, period, fitness)
-
Expand source code
def bifid_break_worker(message, keyword, wrap_alphabet, period, fitness): plaintext = bifid_decipher(message, keyword, wrap_alphabet, period=period, fillvalue='e') fit = fitness(plaintext) return (keyword, wrap_alphabet, period), fit
def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, letter_mapping=None, period=None, fillvalue=None)
-
Decipher with bifid cipher
>>> bifid_decipher('ibidonhprm', 'iguana') 'indiaielly' >>> bifid_decipher("ibnhgaqltm", 'iguana', period=4) 'indiacurry' >>> bifid_decipher("ibnhgaqltzml", 'iguana', period=4) 'indiacurryxx'
Expand source code
def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, letter_mapping=None, period=None, fillvalue=None): """Decipher with bifid cipher >>> bifid_decipher('ibidonhprm', 'iguana') 'indiaielly' >>> bifid_decipher("ibnhgaqltm", 'iguana', period=4) 'indiacurry' >>> 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 = chunks(pairs0, period, fillvalue=None) else: chunked_pairs = [pairs0] pairs1 = [] for c in chunked_pairs: items = [j for i in c for j in i] gap = len(c) p = [(items[i], items[i+gap]) for i in range(gap)] pairs1 += p return cat(r_grid[p] for p in pairs1)
def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, letter_mapping=None, period=None, fillvalue=None)
-
Bifid cipher
>>> bifid_encipher("indiajelly", 'iguana') 'ibidonhprm' >>> bifid_encipher("indiacurry", 'iguana', period=4) 'ibnhgaqltm' >>> bifid_encipher("indiacurry", 'iguana', period=4, fillvalue='x') 'ibnhgaqltzml'
Expand source code
def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a, letter_mapping=None, period=None, fillvalue=None): """Bifid cipher >>> bifid_encipher("indiajelly", 'iguana') 'ibidonhprm' >>> bifid_encipher("indiacurry", 'iguana', period=4) 'ibnhgaqltm' >>> 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 = 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 = chunks(pairs0, period, fillvalue=None) else: chunked_pairs = [pairs0] pairs1 = [] for c in chunked_pairs: items = sum(list(list(i) for i in zip(*c)), []) p = [(items[i], items[i+1]) for i in range(0, len(items), 2)] pairs1 += p return cat(r_grid[p] for p in pairs1)
def bifid_grid(keyword, wrap_alphabet, letter_mapping)
-
Create the grids for a Bifid cipher
Expand source code
def bifid_grid(keyword, wrap_alphabet, letter_mapping): """Create the grids for a Bifid cipher """ cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet) if letter_mapping is None: letter_mapping = {'j': 'i'} translation = ''.maketrans(letter_mapping) cipher_alphabet = cat(collections.OrderedDict.fromkeys(cipher_alphabet.translate(translation))) f_grid = {k: ((i // 5) + 1, (i % 5) + 1) for i, k in enumerate(cipher_alphabet)} r_grid = {((i // 5) + 1, (i % 5) + 1): k for i, k in enumerate(cipher_alphabet)} return translation, f_grid, r_grid
def cat(iterable, /)
-
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
def lcat(iterable, /)
-
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
def wcat(iterable, /)
-
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'