From b932174e34f2d44a348cc40f651b5933f77bed69 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 28 Nov 2009 14:11:40 -0500 Subject: [PATCH] beginning process of "making it right", enclosing mod 25 one-time pad stuff in its own class --- itest_onetimepad.py | 261 ++++++++++++++++----------------- onetimepad.py | 343 +++++++++++++++++++++++--------------------- 2 files changed, 312 insertions(+), 292 deletions(-) diff --git a/itest_onetimepad.py b/itest_onetimepad.py index 0c45e53..45a6180 100644 --- a/itest_onetimepad.py +++ b/itest_onetimepad.py @@ -2,184 +2,189 @@ import unittest import onetimepad as OT -class TestOneTimePad(unittest.TestCase): +class TestMod25(unittest.TestCase): msg = ('HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFA' 'WOODCHUCKCOULDCHUCKWOOD' * 50) _padsize = 2000 + PADLINES = (1, 4, 7, 10, 13, 16, 19, 22, 25) + TEXTLINES = (2, 5, 8, 11, 14, 17, 20, 23, 26) + CIPHERLINES = (3, 6, 9, 12, 15, 18, 21, 24, 27) - def test_mk_as_alpha_excludes_j(self): - self.assertTrue('J' not in OT._AS_ALPHA.values()) + TEST_PADLINE = 'HUCHUGGHOBUXADDHOHPGQBKXQKAOLRL' + TEST_TEXTLINE = 'THISCAKEISUNSATISFACTORYTOMEYES' + TEST_CIPHERLINE = 'BCMAXHRNXUPLTEXRGOQKKQBWKYNTKWD' - def test_mk_as_alpha_dict_has_25_members(self): - self.assertEqual(25, len(OT._AS_ALPHA.items())) + TEST_MSG = \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ + 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOO' - def test_creates_pad_of_desired_length(self): - for width in (72, 33, 99, 111): - pad = OT.create_pad(self._padsize, width=width) - lines = [line.strip('.') for line in pad.split()] - actual = len(''.join(lines)) - self.assertEqual(self._padsize, len(''.join(lines)), - 'pad of {0} chars created at width ' - '{1}, actual={2}'.format(self._padsize, - width, actual)) + TEST_CIPHER = \ + 'FMHZRGHRITAMHVAOLBANWMNYPBYDCFCAVXAEEKXUTYXLUCYQVYPWZFSKYI' \ + 'LWKCHZSXLFCMZIEFHXKVUXVNBIQVSNYPTFVIZHSVTDCKWUSZHEHUPLZSUY' \ + 'BTPSMEISGTYDWCXVUCPGHHKXDOUHGCVDTDUQVKFDRDCHFIKTNFGDFODCQS' \ + 'RIWGZBLPTCSXLMPQODYLHQNGWNSCTHNDOZEUXWCEQAZMRGZIQUTUYSKOGW' \ + 'YGYPEDNUZDWBEPUHLWWBHLGADYTCNGEUFKPLQSAMMXCTXWQEOYBNKQAHBV' \ + 'PDKTMQSHDFCFBCVKXVTRSXRTBHAGKNLFASNVNENHHRNGCKXALMIBNZKOTL' \ + 'BZMWGSNMTLDNPXKVQZZUAZCSPWFNRKAMTNMCPHWGLGBVSNFDXSKVUFBZBW' \ + 'WICBUPCWZDUXCZTXHKQBMQNTBHAAGMBPKUIDTHITUEWEPPMACPXERXRYND' \ + 'RWQSBQQMSQRVWZMAQQKUGHLELKFLKHGNFFDHIWUZPDIMIOELSIHMUIZQCW' \ + 'KLPZQIFAXNDRRIIIHOYBGSCIYBIVSRTECEOZSBLYTNCXGQXEWMGIXHKBIL' \ + 'TOKWHGABPMXNAHEWFKIMOQPHVHSYZABWLMLSSDEHQIBNEMEMHCWOUBIIZS' \ + 'XPFWXMQNOTEANYGQBVMZAXNPAMWSUOGFAFFYUTKDZYHFTPYGOMDHESKOZI' \ + 'IPDXAMSAQOTGAQKYPYWWUYGHSBVODAHDSKDVPMLWRCXQXGAXQZBFNKXFKU' \ + 'WUDRWNRKECIQIUEEMQNCAZDDDOQOEGUVFVCTKLTISNOQILEKUYXCYZNHHZ' \ + 'OFHCSLOPPQAFLHHDZANGTXNQSDZSAFLLSGWULVHIDERMTZZHTDHTZPHEFN' \ + 'ORDEIYOVTTAWVTOOXSLZBOPMRCCKSMHQOVFERCOVUMWXFSNEMFALNWGRQC' \ + 'NLWELLXNCHZGXQEUGEVNCWMHRNGUTBKTVEXGASBRCEBSFITOEFSNIBTLME' \ + 'MERMOFVBNTFCTTWEGAIGRZQBBFOKOPXNCWXFBK' - def test_get_randint_on_nonexistent_randomness_file_fails_ioerror(self): - OT._DEV_RANDOM['fp'] = None - self.assertRaises(IOError, OT._get_randint, - randomness_file='/foo/bar/busted/borken', - fallback_to_fake=False) + TEST_PAD = """\ + XXLNWDZOAYXBEGFCGADYHHKQUYOAULZQMRZHQVTRLDUARODERVGBWVVVKECHNQNWKUCLZBWU - def test_get_randint_on_nonexistent_randomness_file_uses_fake(self): - OT._DEV_RANDOM['fp'] = None - random_int = OT._get_randint(randomness_file='/broke/as/joke') - self.assertTrue(random_int in range(0, 255)) - def test_is_padline(self): - for lineno in PADLINES: - self.assertTrue(OT._is_padline(lineno), - 'line {0} is padline'.format(lineno)) - for lineno in TEXTLINES: - self.assertFalse(OT._is_padline(lineno), - 'line {0} is not padline'.format(lineno)) - for lineno in CIPHERLINES: - self.assertFalse(OT._is_padline(lineno), - 'line {0} is not padline'.format(lineno)) + KUDWNGFTSEGFFSKSVEKZUMLTOSLIZZTFXODBZZMACDFUTESFRBAPYYVTTOCKQBSSTDGPILKE - def test_is_textline(self): - for lineno in TEXTLINES: - self.assertTrue(OT._is_textline(lineno), - 'line {0} is textline'.format(lineno)) - for lineno in PADLINES: - self.assertFalse(OT._is_textline(lineno), - 'line {0} is not textline'.format(lineno)) - for lineno in CIPHERLINES: - self.assertFalse(OT._is_textline(lineno), - 'line {0} is not textline'.format(lineno)) - def test_is_cipherline(self): - for lineno in CIPHERLINES: - self.assertTrue(OT._is_cipherline(lineno), - 'line {0} is cipherline'.format(lineno)) - for lineno in PADLINES: - self.assertFalse(OT._is_cipherline(lineno), - 'line {0} is not cipherline'.format(lineno)) - for lineno in TEXTLINES: - self.assertFalse(OT._is_cipherline(lineno), - 'line {0} is not cipherline'.format(lineno)) + YHSTKXTTGVBAIIZXCUPHICYICDGOBOIUZUEYCMLHPNHXUEKCBWTMKYBKHZLNKTETDXIHYBHF - def test_make_cipherline_from_padline_and_textline(self): - actual = OT._cipherline_from_padline_and_textline(TEST_PADLINE, - TEST_TEXTLINE) - self.assertEqual(TEST_CIPHERLINE, actual) - def test_make_textline_from_cipherline_and_padline(self): - actual = OT._textline_from_cipherline_and_padline(TEST_CIPHERLINE, - TEST_PADLINE) - self.assertEqual(TEST_TEXTLINE, actual) + WBOSEXMRLZVHNZSSQSBCKAERRITRBAZWGVZNTGDSIVIZEMBKWDOOBDWIDCZIUHVTKVTSGFDT - def test_encode_equals_expected(self): - ciphertext = OT.encode(TEST_MSG, TEST_PAD) - self.assertEqual(TEST_CIPHER, ciphertext) - def test_decode_equals_expected(self): - text = OT.decode(TEST_CIPHER, TEST_PAD) - self.assertEqual(TEST_MSG, text) + NRGPNGRNKEVLZVYOAYTUWCDTOLGEQDBSHVRMMYYQIEZWKWZVCPGIAGKPNZEGTLPKMPEILQAC -PADLINES = (1, 4, 7, 10, 13, 16, 19, 22, 25) -TEXTLINES = (2, 5, 8, 11, 14, 17, 20, 23, 26) -CIPHERLINES = (3, 6, 9, 12, 15, 18, 21, 24, 27) + MIPKMYCFMVZKUTVKIPXBKGLFATSDCMYLPYLSTPBARVELNSOUFPZMUTRIRNZLYMDITNXMKLGE -TEST_PADLINE = 'HUCHUGGHOBUXADDHOHPGQBKXQKAOLRL' -TEST_TEXTLINE = 'THISCAKEISUNSATISFACTORYTOMEYES' -TEST_CIPHERLINE = 'BCMAXHRNXUPLTEXRGOQKKQBWKYNTKWD' -TEST_MSG = \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOODCHUCKCOULDCHUCKWOOD' \ - 'HOWMUCHCHUCKCOULDAWOODCHUCKCHUCKIFAWOO' + QXYRYEAOHGETEQMKTUMARPYMPKONUKYZIHTFGNHIKVOLTLRPMPNFSDHWQGVHBNDCWZCLUHQW -TEST_CIPHER = \ - 'FMHZRGHRITAMHVAOLBANWMNYPBYDCFCAVXAEEKXUTYXLUCYQVYPWZFSKYI' \ - 'LWKCHZSXLFCMZIEFHXKVUXVNBIQVSNYPTFVIZHSVTDCKWUSZHEHUPLZSUY' \ - 'BTPSMEISGTYDWCXVUCPGHHKXDOUHGCVDTDUQVKFDRDCHFIKTNFGDFODCQS' \ - 'RIWGZBLPTCSXLMPQODYLHQNGWNSCTHNDOZEUXWCEQAZMRGZIQUTUYSKOGW' \ - 'YGYPEDNUZDWBEPUHLWWBHLGADYTCNGEUFKPLQSAMMXCTXWQEOYBNKQAHBV' \ - 'PDKTMQSHDFCFBCVKXVTRSXRTBHAGKNLFASNVNENHHRNGCKXALMIBNZKOTL' \ - 'BZMWGSNMTLDNPXKVQZZUAZCSPWFNRKAMTNMCPHWGLGBVSNFDXSKVUFBZBW' \ - 'WICBUPCWZDUXCZTXHKQBMQNTBHAAGMBPKUIDTHITUEWEPPMACPXERXRYND' \ - 'RWQSBQQMSQRVWZMAQQKUGHLELKFLKHGNFFDHIWUZPDIMIOELSIHMUIZQCW' \ - 'KLPZQIFAXNDRRIIIHOYBGSCIYBIVSRTECEOZSBLYTNCXGQXEWMGIXHKBIL' \ - 'TOKWHGABPMXNAHEWFKIMOQPHVHSYZABWLMLSSDEHQIBNEMEMHCWOUBIIZS' \ - 'XPFWXMQNOTEANYGQBVMZAXNPAMWSUOGFAFFYUTKDZYHFTPYGOMDHESKOZI' \ - 'IPDXAMSAQOTGAQKYPYWWUYGHSBVODAHDSKDVPMLWRCXQXGAXQZBFNKXFKU' \ - 'WUDRWNRKECIQIUEEMQNCAZDDDOQOEGUVFVCTKLTISNOQILEKUYXCYZNHHZ' \ - 'OFHCSLOPPQAFLHHDZANGTXNQSDZSAFLLSGWULVHIDERMTZZHTDHTZPHEFN' \ - 'ORDEIYOVTTAWVTOOXSLZBOPMRCCKSMHQOVFERCOVUMWXFSNEMFALNWGRQC' \ - 'NLWELLXNCHZGXQEUGEVNCWMHRNGUTBKTVEXGASBRCEBSFITOEFSNIBTLME' \ - 'MERMOFVBNTFCTTWEGAIGRZQBBFOKOPXNCWXFBK' -TEST_PAD = """\ -XXLNWDZOAYXBEGFCGADYHHKQUYOAULZQMRZHQVTRLDUARODERVGBWVVVKECHNQNWKUCLZBWU + GIFBFZKZOFZRRYCBOSBWSNVFXXPSAGOUOXDNBNSOZADYYSKWQUTYNCDNGVLSZNDBCTSIYOUX -KUDWNGFTSEGFFSKSVEKZUMLTOSLIZZTFXODBZZMACDFUTESFRBAPYYVTTOCKQBSSTDGPILKE + NNUGLZNKNDSYGRUCXTKLBIMXZMMZAEHVRFYMBFKVDPAEHOYCBXKADZOTRRMULOPAIKCIHKFY -YHSTKXTTGVBAIIZXCUPHICYICDGOBOIUZUEYCMLHPNHXUEKCBWTMKYBKHZLNKTETDXIHYBHF + BQKKMEXUPLMTKGFIMPMTDVRZEBFEFARDEVQADVKIVNBHNZLEAAGLFIKXHTQWXBPNLXZHFUDZ -WBOSEXMRLZVHNZSSQSBCKAERRITRBAZWGVZNTGDSIVIZEMBKWDOOBDWIDCZIUHVTKVTSGFDT + XYLLVFETIDCYAXGTIHUFUSFMMWTLKZARVQOFGEBKIGWHFFFFKTHPQOMVAVILFLWMRLWPBWVW -NRGPNGRNKEVLZVYOAYTUWCDTOLGEQDBSHVRMMYYQIEZWKWZVCPGIAGKPNZEGTLPKMPEILQAC + PFKSLFFWKYQVPHVPQTTVFRLQXHFMGVXVHTNSVZQSETKHXAPPSLHAIAVXWGDFVKOBQLEWPAZY -MIPKMYCFMVZKUTVKIPXBKGLFATSDCMYLPYLSTPBARVELNSOUFPZMUTRIRNZLYMDITNXMKLGE + WELQRIFCGSOVFSLYXMSETCTROLNKMDWZSGKREFEPEHCOKSMRTNCDSTHCSQKMKCBYEWZSQHPK -QXYRYEAOHGETEQMKTUMARPYMPKONUKYZIHTFGNHIKVOLTLRPMPNFSDHWQGVHBNDCWZCLUHQW + UNWWUBKICDYYOSIZWKWRLGGIMYWKMDXOUKYHCUYCACKSFRWWXADQUZTCNYEYCSQEBTCZMSCV -GIFBFZKZOFZRRYCBOSBWSNVFXXPSAGOUOXDNBNSOZADYYSKWQUTYNCDNGVLSZNDBCTSIYOUX + NTGCDGFUUCTQWINV........................................................ + """ -NNUGLZNKNDSYGRUCXTKLBIMXZMMZAEHVRFYMBFKVDPAEHOYCBXKADZOTRRMULOPAIKCIHKFY + def setUp(self): + self.m25 = OT.Mod25() + def test_mk_as_alpha_excludes_j(self): + self.assertTrue('J' not in self.m25._as_alpha.values()) -BQKKMEXUPLMTKGFIMPMTDVRZEBFEFARDEVQADVKIVNBHNZLEAAGLFIKXHTQWXBPNLXZHFUDZ + def test_mk_as_alpha_dict_has_25_members(self): + self.assertEqual(25, len(self.m25._as_alpha.items())) + + def test_creates_pad_of_desired_length(self): + for width in (72, 33, 99, 111): + pad = self.m25.create_pad(self._padsize, width=width) + lines = [line.strip('.') for line in pad.split()] + actual = len(''.join(lines)) + self.assertEqual(self._padsize, len(''.join(lines)), + 'pad of {0} chars created at width ' + '{1}, actual={2}'.format(self._padsize, + width, actual)) + def test_is_padline(self): + for lineno in self.PADLINES: + self.assertTrue(self.m25._is_padline(lineno), + 'line {0} is padline'.format(lineno)) + for lineno in self.TEXTLINES: + self.assertFalse(self.m25._is_padline(lineno), + 'line {0} is not padline'.format(lineno)) + for lineno in self.CIPHERLINES: + self.assertFalse(self.m25._is_padline(lineno), + 'line {0} is not padline'.format(lineno)) -XYLLVFETIDCYAXGTIHUFUSFMMWTLKZARVQOFGEBKIGWHFFFFKTHPQOMVAVILFLWMRLWPBWVW + def test_is_textline(self): + for lineno in self.TEXTLINES: + self.assertTrue(self.m25._is_textline(lineno), + 'line {0} is textline'.format(lineno)) + for lineno in self.PADLINES: + self.assertFalse(self.m25._is_textline(lineno), + 'line {0} is not textline'.format(lineno)) + for lineno in self.CIPHERLINES: + self.assertFalse(self.m25._is_textline(lineno), + 'line {0} is not textline'.format(lineno)) + def test_is_cipherline(self): + for lineno in self.CIPHERLINES: + self.assertTrue(self.m25._is_cipherline(lineno), + 'line {0} is cipherline'.format(lineno)) + for lineno in self.PADLINES: + self.assertFalse(self.m25._is_cipherline(lineno), + 'line {0} is not cipherline'.format(lineno)) + for lineno in self.TEXTLINES: + self.assertFalse(self.m25._is_cipherline(lineno), + 'line {0} is not cipherline'.format(lineno)) -PFKSLFFWKYQVPHVPQTTVFRLQXHFMGVXVHTNSVZQSETKHXAPPSLHAIAVXWGDFVKOBQLEWPAZY + def test_make_cipherline_from_padline_and_textline(self): + actual = \ + self.m25._cipherline_from_padline_and_textline(self.TEST_PADLINE, + self.TEST_TEXTLINE) + self.assertEqual(self.TEST_CIPHERLINE, actual) + def test_make_textline_from_cipherline_and_padline(self): + actual = self.m25._textline_from_cipherline_and_padline( + self.TEST_CIPHERLINE, self.TEST_PADLINE) + self.assertEqual(self.TEST_TEXTLINE, actual) -WELQRIFCGSOVFSLYXMSETCTROLNKMDWZSGKREFEPEHCOKSMRTNCDSTHCSQKMKCBYEWZSQHPK + def test_encode_equals_expected(self): + ciphertext = OT.mod25encode(self.TEST_MSG, self.TEST_PAD) + self.assertEqual(self.TEST_CIPHER, ciphertext) + def test_decode_equals_expected(self): + text = OT.mod25decode(self.TEST_CIPHER, self.TEST_PAD) + self.assertEqual(self.TEST_MSG, text) -UNWWUBKICDYYOSIZWKWRLGGIMYWKMDXOUKYHCUYCACKSFRWWXADQUZTCNYEYCSQEBTCZMSCV +class TestOneTimePad(unittest.TestCase): -NTGCDGFUUCTQWINV........................................................ + def test_get_randint_on_nonexistent_randomness_file_fails_ioerror(self): + OT._DEV_URANDOM['fp'] = None + self.assertRaises(IOError, OT._get_randint, + 25, randomness_file='/foo/bar/busted/borken', + fallback_to_fake=False) -""" + def test_get_randint_on_nonexistent_randomness_file_uses_fake(self): + OT._DEV_URANDOM['fp'] = None + random_int = OT._get_randint(32, randomness_file='/broke/as/joke') + self.assertTrue(random_int in range(0, 255)) if __name__ == '__main__': diff --git a/onetimepad.py b/onetimepad.py index 92febdf..1ea151f 100644 --- a/onetimepad.py +++ b/onetimepad.py @@ -1,62 +1,177 @@ from itertools import izip -PAD_MODULO = 3 -PADLINE_OFFSET = -2 -TEXTLINE_OFFSET = -1 DEFAULT_PAD_WIDTH = 72 -CIPHER_FLOOR = 1 -CIPHER_CEIL = 25 -NULLCHAR = '.' -def encode(msg, pad): - ret = [] - filled = list(_text_padfill(msg, pad)) - for i, line in enumerate(_text_cipherfill(filled)): - lineno = i + 1 - if _is_cipherline(lineno): - ret.append(line) - return ''.join(ret).strip(NULLCHAR) - - -def decode(ciphertext, pad): - ret = [] - filled = list(_cipher_padfill(ciphertext, pad)) - for i, line in enumerate(_cipher_textfill(filled)): - lineno = i + 1 - if _is_textline(lineno): - ret.append(line) - return ''.join(ret).strip(NULLCHAR) - - -def create_pad(length, width=DEFAULT_PAD_WIDTH): - return '\n'.join(create_pad_lines(length, width=width)) - - -def create_pad_lines(length, width=DEFAULT_PAD_WIDTH): - chars = _create_chars_for_pad(length) - lines = _chunk_chars_into_lines(chars, width=width) - for line in lines: - yield line - yield '' - yield '' - - -def _create_chars_for_pad(length): - chars = [] - for char in range(length): - chars.append(_AS_ALPHA[_get_randint()]) - return ''.join(chars) - - -def _chunk_chars_into_lines(chars, width=DEFAULT_PAD_WIDTH): +def mod25encode(msg, pad): + mod25 = Mod25(pad=pad) + return mod25.encode(msg) + + +def mod25decode(msg, pad): + mod25 = Mod25(pad=pad) + return mod25.decode(msg) + + +class Mod25(object): + _pad_modulo = 3 + _padline_offset = -2 + _textline_offset = -1 + _cipher_floor = 1 + _cipher_ceil = 25 + _nullchar = '.' + + def __init__(self, pad=''): + self.pad = pad + self._as_alpha = {} + self._as_nums = {} + self._mk_as_alpha_as_nums() + + def _mk_as_alpha_as_nums(self): + as_alpha = {} + as_nums = {} + a_chr = ord('A') + past_j = False + + for char in range(self._cipher_ceil + 1): + letter = chr(a_chr + char) + if letter != 'J': + key = char + (1 if not past_j else 0) + as_alpha[key] = letter + else: + past_j = True + + for key, val in as_alpha.iteritems(): + as_nums[val] = key + + self._as_alpha.update(as_alpha) + self._as_nums.update(as_nums) + + def encode(self, msg): + ret = [] + filled = list(self._text_padfill(msg)) + for i, line in enumerate(self._text_cipherfill(filled)): + lineno = i + 1 + if self._is_cipherline(lineno): + ret.append(line) + return ''.join(ret).strip(self._nullchar) + + def decode(self, ciphertext): + ret = [] + filled = list(self._cipher_padfill(ciphertext)) + for i, line in enumerate(self._cipher_textfill(filled)): + lineno = i + 1 + if self._is_textline(lineno): + ret.append(line) + return ''.join(ret).strip(self._nullchar) + + def create_pad(self, length, width=DEFAULT_PAD_WIDTH): + return '\n'.join(self.create_pad_lines(length, width=width)) + + def create_pad_lines(self, length, width=DEFAULT_PAD_WIDTH): + chars = self._create_chars_for_pad(length) + lines = _chunk_chars_into_lines(chars, self._nullchar, width=width) + for line in lines: + yield line + yield '' + yield '' + + def _create_chars_for_pad(self, length): + chars = [] + for char in range(length): + chars.append(self._as_alpha[_get_randint(self._cipher_ceil)]) + return ''.join(chars) + + def _ensure_pad_is_lines(self): + if isinstance(self.pad, basestring): + self.pad = self.pad.splitlines() + + def _text_padfill(self, text): + self._ensure_pad_is_lines() + padlines = [line.strip() for line in self.pad if line.strip()] + textlines = _chunk_chars_into_lines(text, self._nullchar, + _get_textwidth(padlines)) + for textline, padline in izip(textlines, padlines): + yield padline + yield textline + yield '' + + def _cipher_padfill(self, ciphertext): + self._ensure_pad_is_lines() + padlines = [line.strip() for line in self.pad if line.strip()] + cipherlines = _chunk_chars_into_lines(ciphertext, self._nullchar, + _get_textwidth(padlines)) + for cipherline, padline in izip(cipherlines, padlines): + yield padline + yield '' + yield cipherline + + def _text_cipherfill(self, padfilled_text_lines): + for i, line in enumerate(padfilled_text_lines): + lineno = i + 1 + if self._is_cipherline(lineno): + padline = padfilled_text_lines[i - abs(self._padline_offset)] + textline = padfilled_text_lines[i - abs(self._textline_offset)] + yield padline + yield textline + yield self._cipherline_from_padline_and_textline(padline, + textline) + + def _cipher_textfill(self, padfilled_cipher_lines): + for i, line in enumerate(padfilled_cipher_lines): + lineno = i + 1 + if self._is_cipherline(lineno): + padline = padfilled_cipher_lines[i - abs(self._padline_offset)] + textline = \ + padfilled_cipher_lines[i - abs(self._textline_offset)] + yield padline + yield self._textline_from_cipherline_and_padline(line, padline) + yield line + + def _cipherline_from_padline_and_textline(self, padline, textline): + ret = [] + for padchar, textchar in izip(padline, textline): + if textchar == self._nullchar: + ret.append(self._nullchar) + continue + charnum = self._as_nums[padchar] + self._as_nums[textchar] + idx = charnum if charnum <= self._cipher_ceil else \ + charnum % self._cipher_ceil + ret.append(self._as_alpha[idx]) + return ''.join(ret) + + def _textline_from_cipherline_and_padline(self, cipherline, padline): + ret = [] + for ciphercar, padchar in izip(cipherline, padline): + if ciphercar == self._nullchar: + ret.append(self._nullchar) + continue + charnum = self._as_nums[ciphercar] - self._as_nums[padchar] + idx = charnum if charnum <= self._cipher_ceil else \ + charnum % self._cipher_ceil + if idx < 0: + idx = self._cipher_ceil + idx + ret.append(self._as_alpha[idx]) + return ''.join(ret) + + def _is_padline(self, lineno): + return self._is_cipherline(lineno + abs(self._padline_offset)) + + def _is_textline(self, lineno): + return self._is_cipherline(lineno + abs(self._textline_offset)) + + def _is_cipherline(self, lineno): + return not lineno % self._pad_modulo + + +def _chunk_chars_into_lines(chars, nullchar, width=DEFAULT_PAD_WIDTH): lines = [] - for chunk in _as_line_chunks(chars, width=width): + for chunk in _as_line_chunks(chars, nullchar, width=width): lines.append(chunk) return lines -def _as_line_chunks(chars, width=DEFAULT_PAD_WIDTH): +def _as_line_chunks(chars, nullchar, width=DEFAULT_PAD_WIDTH): chunk = [] for char in chars.replace('\n', ''): chunk.append(char) @@ -64,142 +179,42 @@ def _as_line_chunks(chars, width=DEFAULT_PAD_WIDTH): yield ''.join(chunk) chunk = [] if len(chunk) < width: - chunk += ([NULLCHAR] * (width - len(chunk))) + chunk += ([nullchar] * (width - len(chunk))) yield ''.join(chunk) -def _text_padfill(text, padlines): - if isinstance(padlines, basestring): - padlines = padlines.splitlines() - padlines = [line.strip() for line in padlines if line.strip()] - textlines = _chunk_chars_into_lines(text, _get_textwidth(padlines)) - for textline, padline in izip(textlines, padlines): - yield padline - yield textline - yield '' - - -def _cipher_padfill(ciphertext, padlines): - if isinstance(padlines, basestring): - padlines = padlines.splitlines() - padlines = [line.strip() for line in padlines if line.strip()] - cipherlines = _chunk_chars_into_lines(ciphertext, _get_textwidth(padlines)) - for cipherline, padline in izip(cipherlines, padlines): - yield padline - yield '' - yield cipherline - - -def _text_cipherfill(padfilled_text_lines): - for i, line in enumerate(padfilled_text_lines): - lineno = i + 1 - if _is_cipherline(lineno): - padline = padfilled_text_lines[i - abs(PADLINE_OFFSET)] - textline = padfilled_text_lines[i - abs(TEXTLINE_OFFSET)] - yield padline - yield textline - yield _cipherline_from_padline_and_textline(padline, textline) - - -def _cipher_textfill(padfilled_cipher_lines): - for i, line in enumerate(padfilled_cipher_lines): - lineno = i + 1 - if _is_cipherline(lineno): - padline = padfilled_cipher_lines[i - abs(PADLINE_OFFSET)] - textline = padfilled_cipher_lines[i - abs(TEXTLINE_OFFSET)] - yield padline - yield _textline_from_cipherline_and_padline(line, padline) - yield line - - -def _cipherline_from_padline_and_textline(padline, textline): - ret = [] - for padchar, textchar in izip(padline, textline): - if textchar == NULLCHAR: - ret.append(NULLCHAR) - continue - charnum = _AS_NUMS[padchar] + _AS_NUMS[textchar] - idx = charnum if charnum <= CIPHER_CEIL else charnum % CIPHER_CEIL - ret.append(_AS_ALPHA[idx]) - return ''.join(ret) - - -def _textline_from_cipherline_and_padline(cipherline, padline): - ret = [] - for ciphercar, padchar in izip(cipherline, padline): - if ciphercar == NULLCHAR: - ret.append(NULLCHAR) - continue - charnum = _AS_NUMS[ciphercar] - _AS_NUMS[padchar] - idx = charnum if charnum <= CIPHER_CEIL else charnum % CIPHER_CEIL - if idx < 0: - idx = CIPHER_CEIL + idx - ret.append(_AS_ALPHA[idx]) - return ''.join(ret) - - def _get_textwidth(textlines): return max([len(line) for line in textlines]) -def _is_padline(lineno): - return _is_cipherline(lineno + abs(PADLINE_OFFSET)) - - -def _is_textline(lineno): - return _is_cipherline(lineno + abs(TEXTLINE_OFFSET)) - - -def _is_cipherline(lineno): - return not lineno % PAD_MODULO - - -def _get_randint(randomness_file='/dev/urandom', fallback_to_fake=True): - if not _DEV_RANDOM.get('fp'): +def _get_randint(modulo, randomness_file='/dev/urandom', + fallback_to_fake=True): + if not _DEV_URANDOM.get('fp'): _get_urandom_fp(randomness_file=randomness_file, fallback_to_fake=fallback_to_fake) - ret = ord(_DEV_RANDOM['fp'].read(1)) % CIPHER_CEIL - return ret if ret != 0 else _get_randint() + ret = ord(_DEV_URANDOM['fp'].read(1)) % modulo + return ret if ret != 0 else _get_randint(modulo) class _FakeDevUrandom(object): - def read(self, nchars): + @staticmethod + def read(nchars): import random - return ''.join([chr(random.randint(0, 255)) for i in range(0, nchars)]) + ret = [] + for i in range(0, nchars): + ret.append(chr(random.randint(0, 255))) + return ''.join(ret) def _get_urandom_fp(randomness_file='/dev/urandom', fallback_to_fake=True): try: - _DEV_RANDOM['fp'] = open(randomness_file) + _DEV_URANDOM['fp'] = open(randomness_file) except (OSError, IOError): if fallback_to_fake: - _DEV_RANDOM['fp'] = _FakeDevUrandom() + _DEV_URANDOM['fp'] = _FakeDevUrandom() else: raise -_DEV_RANDOM = {} - - -def _mk_as_alpha_as_nums(): - as_alpha = {} - as_nums = {} - a_chr = ord('A') - past_j = False - - for char in range(CIPHER_CEIL + 1): - letter = chr(a_chr + char) - if letter != 'J': - key = char + (1 if not past_j else 0) - as_alpha[key] = letter - else: - past_j = True - - for key, val in as_alpha.iteritems(): - as_nums[val] = key - - return as_alpha, as_nums - - -_AS_ALPHA, _AS_NUMS = _mk_as_alpha_as_nums() +_DEV_URANDOM = {}