Last active
September 24, 2016 20:16
-
-
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!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /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