#!/usr/bin/python
# Evan Jones
# http://evanjones.ca/

import os
import struct

alphabet = (
    "23456789!@#%+=:?"
    "abcdefghijkmnopq"
    "rstuvwxyzABCDEFG"
    "HJKLMNPRSTUVWXYZ")

def toAlphabet(s):
    assert len(s) % 3 == 0

    out = ''
    # Three input bytes = 4 output bytes
    while len(s) > 0:
        # Build an integer out of the next 3 bytes
        value = ord(s[0]) | (ord(s[1]) << 8) | (ord(s[2]) << 16)
        s = s[3:]
        
        
        for i in xrange(4):
            # Take bottom 6 bits
            v = value & 0x3f
            value >>= 6

            out += alphabet[v]
    return out

def longTo128bits(v):
    # Take the lower 64 bits:
    lower = v & 0xffffffffffffffffL
    v >>= 64
    upper = v & 0xffffffffffffffffL
    v >>= 64
    assert(v == 0)

    out = struct.pack("<QQ", lower, upper)
    assert len(out) == 16
    return out

def tohex(s):
    out = ''
    for c in s:
        out += "%02x" % ord(c)
    return out

def paperPasswords(sequence_key, start, count):
    """Returns count passwords starting at password number start, using sequence_key."""

    # 3 consecutive counter values = 16 passwords.
    # Round start DOWN to the point we need to start feeding in AES blocks
    real_start_code = start / 16 * 16
    counter_start = start / 16 * 3
    # We want to generate passwords past start+count, round that UP
    end_code = start + count
    real_end_code = end_code / 16
    if real_end_code * 16 < end_code:
        real_end_code += 1
    counter_end = real_end_code * 3
    real_end_code *= 16

    # Build input bytes to encrypt
    input = ''
    for counter in xrange(counter_start, counter_end):
        input += longTo128bits(counter)
    assert len(input) % (16*3) == 0

    # We use AES with a 256-bit key. We must specify -nopad in order to disable
    # OpenSSL padding it out to 256 bits of output.
    child_in, child_out = os.popen2("openssl enc -nopad -aes-256-ecb -iv 0 -K %s" % (tohex(sequence_key)), "rw")
    child_in.write(input)
    child_in.close()
    output = child_out.read()
    child_out.close()
    assert len(output) == len(input)

    # Split output into groups of 4 characters
    out = toAlphabet(output)
    codes = []
    i = 0
    while i*4 < len(out):
        code = out[i*4:(i+1)*4]
        assert len(code) == 4
        codes.append(code)
        i += 1

    # Trim the extra initial and trailing passwords
    extra_initial = start - real_start_code
    extra_trailing = real_end_code - end_code
    if extra_trailing == 0:
        codes = codes[extra_initial:]
    else:
        codes = codes[extra_initial:-extra_trailing]
    return codes

# Example PPP sequence key: SHA256 hash of "zombie"
sequence_key = (
    "\x49\x46\x0b\x7b\xbb\xd3\xaa\xd3\xf2\xcb\xa0\x98\x64\xf5\xe8\xb0"
    "\x1a\x22\x0e\xa8\xc0\x77\xe9\xfa\x99\x6d\xe3\x67\xe7\x98\x4a\xf0")

print paperPasswords(sequence_key, 0, 70)
print paperPasswords(sequence_key, 70, 70)
print paperPasswords(sequence_key, 140, 70)

