Skip to content

Instantly share code, notes, and snippets.

@romuloceccon
Last active September 24, 2016 20:16
Show Gist options
  • Select an option

  • Save romuloceccon/d540dd9260ef5e2934e57394a2a2b60d to your computer and use it in GitHub Desktop.

Select an option

Save romuloceccon/d540dd9260ef5e2934e57394a2a2b60d to your computer and use it in GitHub Desktop.
A tool to convert small binary files to text suitable to be printed on paper. It uses Base32 encoding so that the result is easily parseable with OCR software and every line includes a CRC16 for error verification. Efficiency is about 3000 bytes per page using a 10-pt font!
#! /usr/bin/env python
# Examples:
#
## encoding
#
# $ head -c 135 /dev/urandom | b32backup.py
# ZLBD4SI6YCWDF63FYMQ4X7ZOORL5UYBOXZ4Y2WU54OKEQ4N5LBAFA7YHMYTC3W33KHCENWPK 13CB
# TUK5QT55AXF5PZL6Q6A5RJ7CJ24YZML3WMRQWAI6TCO5XGMOHJWAATO7TUCEM7BCL6FGTC6G 03AF
# 6SZVCA4CTBGIHZXC5DEGMCIOLRJPKGVIQCTWAQZAX4VMNLPULEJLWJMV6AAPIOPUYCVKEEIT 228A
#
## encoding + decoding
#
# $ echo A secret message. | b32backup.py | b32backup.py -d
# A secret message.
import sys
import argparse
import base64
import binascii
from builtins import bytes
POLYNOMIAL = 0x1021
def crc_tab_entry(c):
result = 0
c = c << 8
for j in range(8):
p = (result ^ c) & 0x8000
result = result << 1
result ^= POLYNOMIAL if p != 0 else 0
c = c << 1
return result & 0xffff
CRC_TAB = [crc_tab_entry(i) for i in range(256)]
def crc(data):
result = 0
for c in bytes(data):
result = ((result & 0xff) << 8) ^ CRC_TAB[((result >> 8) ^ c) & 0xff]
return result
def buffer_handle(s):
if hasattr(s, 'buffer'):
# Python 3
return s.buffer
return s
def fail_decode(s, line):
sys.stderr.write('Error: {} (at line {})\n'.format(s, line))
sys.exit(1)
def decode(input_s, output_s):
output_s = buffer_handle(output_s)
line = 0
while True:
s = input_s.readline()
if not s:
break
line += 1
s1, s2 = (x for x in s.strip().split(' ') if x)
try:
data = base64.b32decode(s1)
except TypeError as e:
fail_decode(str(e), line)
except binascii.Error as e:
fail_decode(str(e), line)
try:
line_crc = int(s2, 16)
except ValueError as e:
fail_decode(str(e), line)
if crc(data) != line_crc:
fail_decode('CRC does not match', line)
output_s.write(data)
def encode(input_s, output_s, width):
input_s = buffer_handle(input_s)
width = width // 8 * 8
cnt = width // 8 * 5
while True:
s = input_s.read(cnt)
if not s:
break
b32 = base64.b32encode(s).decode('ascii').ljust(width, ' ')
output_s.write("%s %04X\n" % (b32, crc(s)))
ENCODE = 1
DECODE = 2
def width(val):
result = int(val)
if result < 8:
raise argparse.ArgumentTypeError('Width cannot be less than 8')
return result
def parse_args():
parser = argparse.ArgumentParser(description='Encode binary data as text '\
'suitable to be stored on (and restored from) paper.')
parser.add_argument('-e', '--encode', action='store_const', const=ENCODE,
dest='mode', help='Encode standard input (default)')
parser.add_argument('-d', '--decode', action='store_const', const=DECODE,
dest='mode', help='Decode standard input')
parser.add_argument('-w', '--width', type=width, default=72, help=
'Character count per line when encoding (should be a multiple of 8) '\
'(default: 72)')
return parser.parse_args()
if __name__ == '__main__':
args = parse_args()
if args.mode == DECODE:
decode(sys.stdin, sys.stdout)
else:
encode(sys.stdin, sys.stdout, args.width)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment