#!/usr/bin/env python3
# denettalk - bruteforce Nettalk's "NTCTC001" encrypted messages
# (ported from Haskell version at <http://lpaste.net/85305>)
import argparse
import re
import sys

# NTCTC001 algorithm

codeChr = "0123456789abcdefghijklmnopqrstuvwxyz!?#%-+"
multPl = 71
num_keys = 1764

def decrypt(tpass, msg):
    def go(i, lastCVal, rest):
        if len(rest) < 2:
            return ""
        c1, c2, *rest = rest
        i1 = codeChr.find(c1)
        i2 = codeChr.find(c2)
        charVal = (i2 * 42 + i1 - tpass + multPl * i - lastCVal * (13 + i * 7)) % 1764
        return chr(charVal % 256) + go(i+1, i2+1, rest)
    return go(1, 0, msg[10:])

# optimized bruteforce

all_keys = [0, 256, 996, 1252, 1508]    # working for all but 2 samples
all_keys += [1153, 1409, 1665]          # working for the remaining 2 samples
all_keys += [k for k in range(num_keys)
                if k not in all_keys]

def try_all(enc):
    for key in all_keys:
        msg = decrypt(key, enc)
        if msg.endswith("<>"):
            yield msg[:-2]

def hax0r(enc):
    from collections import defaultdict

    # Sometimes up to 5 different keys may work – most of them will decrypt to
    # the correct plaintext, others will decrypt ~80% correctly but have
    # garbage in some places; so try all keys but return only the version that
    # occurs most often.
    #
    # However, these 5 keys are the same in all but two samples I've seen in
    # the wild (i.e. pastebinned), therefore try_all() will test them first,
    # and the below loop will stop searching after 3 hits.
    #
    # Therefore, optimize the script for most commonly seen keys, in case
    # another idiot decides to try out Nettalk again.

    variants = defaultdict(int)
    for msg in try_all(enc):
        variants[msg] += 1
        if variants[msg] >= 3:
            break

    variants = list(variants.items())
    variants.sort(key=lambda x: x[1])

    yield variants[-1][0]
    #return ["%s (%d)" % x for x in variants]

def is_printable(s):
    return all(32 <= ord(c) <= 127 or c in '\t' for c in s)

def escaped(s):
    return repr(s)[1:-1]

rx = re.compile(r"\[NTCTC001\|(.+?)\]")

parser = argparse.ArgumentParser()
parser.add_argument("line", nargs="*")
args = parser.parse_args()

if args.line:
    for line in args.line:
        m = rx.search(line)
        if m:
            for msg in hax0r(m.group(0)):
                print(line[:m.start(0)] + escaped(msg) + line[m.end(0):])
else:
    for line in sys.stdin:
        line = line.strip()
        m = rx.search(line)
        if m:
            print("-", line)
            for msg in hax0r(m.group(0)):
                print("+", line[:m.start(0)] + escaped(msg) + line[m.end(0):])
        else:
            print(" ", line)
