You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
box-o-sand/onetimepad.py

145 lines
3.6 KiB

import random
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):
return msg
def decode(msg, pad):
return msg
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[random.randint(CIPHER_FLOOR, CIPHER_CEIL)])
return ''.join(chars)
def _chunk_chars_into_lines(chars, width=DEFAULT_PAD_WIDTH):
lines = []
for chunk in _as_line_chunks(chars, width=width):
lines.append(chunk)
return lines
def _as_line_chunks(chars, width=DEFAULT_PAD_WIDTH):
chunk = []
for char in chars:
chunk.append(char)
if len(chunk) == width:
yield ''.join(chunk)
chunk = []
if len(chunk) < width:
chunk += ([NULLCHAR] * (width - len(chunk)))
yield ''.join(chunk)
def _padfill(text, padlines):
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 _cipherfill(padfilled_lines):
for i, line in enumerate(padfilled_lines):
lineno = i + 1
if _is_cipherline(lineno):
padline = padfilled_lines[i - abs(PADLINE_OFFSET)]
textline = padfilled_lines[i - abs(TEXTLINE_OFFSET)]
yield padline
yield textline
yield _cipherline_from_padline_and_textline(padline, textline)
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(text):
if isinstance(text, basestring):
text = text.splitlines()
return max([len(line) for line in text])
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 _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()