#!/usr/bin/env python3
"""andotp-decrypt.py

Usage:
andotp-decrypt.py [-o|--old] [--debug] [-h|--help] [--version] INPUT_FILE

Options:
-o --old      Use old encryption (andOTP <= 0.6.2)
--debug       Print debug info
-h --help     Show this screen.
--version     Show version.

"""

import os
import sys
import hashlib
import struct
from os.path import basename
from getpass import getpass

from Crypto.Cipher import AES
from Crypto.Hash import SHA256

from docopt import docopt

def bytes2Hex(bytes2encode):
    return '(%s) 0x%s' % (len(bytes2encode), ''.join('{:02x}'.format(x) for x in bytes2encode))

def decode(key, data, debug=False):
    """Decode function used for both the old and new style encryption"""
    # Raw data structure is IV[:12] + crypttext[12:-16] + auth_tag[-16:]
    iv = data[:12]
    crypttext = data[12:-16]
    tag = data[-16:]
    if debug:
        print("Input bytes: %", bytes2Hex(data))
        print("IV: %s" % bytes2Hex(iv))
        print("Crypttext: %s" % bytes2Hex(crypttext))
        print("Auth tag: %s" % bytes2Hex(tag))
    try:
        aes = AES.new(key, AES.MODE_GCM, nonce=iv)
    except Exception as e:
        print(e)
        return None

    try:
        dec = aes.decrypt_and_verify(crypttext, tag)
        if debug:
            print("Decrypted data: %s" % bytes2Hex(dec))
        return dec.decode('UTF-8')
    except ValueError as e:
        print(e)
        print("The passphrase was probably wrong")
        return None

def decrypt_aes_new_format(password, input_file, debug=False):
    input_bytes = None
    with open(input_file, 'rb') as f:
        input_bytes = f.read()

    try:
        # Raw data structure is iterations[:4] + salt[4:16] + data[16:]
        iterations = struct.unpack(">I", input_bytes[:4])[0]
        salt       = input_bytes[4:16]
        data       = input_bytes[16:]
        pbkdf2_key = hashlib.pbkdf2_hmac('sha1', password, salt, iterations, 32)
        if debug:
            print("Iterations: %s" % iterations)
            print("Salt: %s" % bytes2Hex(salt))
            print("Pbkdf2 key: %s" % bytes2Hex(pbkdf2_key))
        return decode(pbkdf2_key, data, debug)
    except struct.error:
        print("The input file is probably empty")
        return None

def decrypt_aes(password, input_file, debug=False):
    hash = SHA256.new(password)
    symmetric_key = hash.digest()
    if debug:
        print("Symmetric key: %s" % bytes2Hex(symmetric_key))
    input_bytes = None
    with open(input_file, 'rb') as f:
        input_bytes = f.read()

    return decode(symmetric_key, input_bytes, debug)

def get_password():
    # Read the backup password manually via a prompt if stdin is a tty,
    # otherwise read from stdin directly.
    if sys.stdin.isatty():
        pw = getpass('andOTP AES passphrase:')
    else:
        pw = sys.stdin.readline()
    return pw.strip().encode('UTF-8')

def main():
    arguments = docopt(__doc__, version='andotp-decrypt 0.1')
    input_file = arguments['INPUT_FILE']
    debug = arguments['--debug']
    old_encryption = arguments['--old']
    if not os.path.exists(input_file):
        print("Could not find input file: %s" % input_file)
        return None
    password = get_password()
    if old_encryption:
        print(decrypt_aes(password, input_file, debug))
    else:
        print(decrypt_aes_new_format(password, input_file, debug))
    
if __name__ == '__main__':
    main()

#!/usr/bin/env python3
"""andotp-decrypt.py

Usage:
andotp-decrypt.py [-o|--old] [--debug] [-h|--help] [--version] INPUT_FILE

Options:
-o --old      Use old encryption (andOTP <= 0.6.2)
--debug       Print debug info
-h --help     Show this screen.
--version     Show version.

"""

import os
import sys
import hashlib
import struct
from os.path import basename
from getpass import getpass

from Crypto.Cipher import AES
from Crypto.Hash import SHA256

from docopt import docopt

def bytes2Hex(bytes2encode):
    return '(%s) 0x%s' % (len(bytes2encode), ''.join('{:02x}'.format(x) for x in bytes2encode))

def decode(key, data, debug=False):
    """Decode function used for both the old and new style encryption"""
    # Raw data structure is IV[:12] + crypttext[12:-16] + auth_tag[-16:]
    iv = data[:12]
    crypttext = data[12:-16]
    tag = data[-16:]
    if debug:
        print("Input bytes: %", bytes2Hex(data))
        print("IV: %s" % bytes2Hex(iv))
        print("Crypttext: %s" % bytes2Hex(crypttext))
        print("Auth tag: %s" % bytes2Hex(tag))
    try:
        aes = AES.new(key, AES.MODE_GCM, nonce=iv)
    except Exception as e:
        print(e)
        return None

    try:
        dec = aes.decrypt_and_verify(crypttext, tag)
        if debug:
            print("Decrypted data: %s" % bytes2Hex(dec))
        return dec.decode('UTF-8')
    except ValueError as e:
        print(e)
        print("The passphrase was probably wrong")
        return None

def decrypt_aes_new_format(password, input_file, debug=False):
    input_bytes = None
    with open(input_file, 'rb') as f:
        input_bytes = f.read()

    try:
        # Raw data structure is iterations[:4] + salt[4:16] + data[16:]
        iterations = struct.unpack(">I", input_bytes[:4])[0]
        salt       = input_bytes[4:16]
        data       = input_bytes[16:]
        pbkdf2_key = hashlib.pbkdf2_hmac('sha1', password, salt, iterations, 32)
        if debug:
            print("Iterations: %s" % iterations)
            print("Salt: %s" % bytes2Hex(salt))
            print("Pbkdf2 key: %s" % bytes2Hex(pbkdf2_key))
        return decode(pbkdf2_key, data, debug)
    except struct.error:
        print("The input file is probably empty")
        return None

def decrypt_aes(password, input_file, debug=False):
    hash = SHA256.new(password)
    symmetric_key = hash.digest()
    if debug:
        print("Symmetric key: %s" % bytes2Hex(symmetric_key))
    input_bytes = None
    with open(input_file, 'rb') as f:
        input_bytes = f.read()

    return decode(symmetric_key, input_bytes, debug)

def get_password():
    # Read the backup password manually via a prompt if stdin is a tty,
    # otherwise read from stdin directly.
    if sys.stdin.isatty():
        pw = getpass('andOTP AES passphrase:')
    else:
        pw = sys.stdin.readline()
    return pw.strip().encode('UTF-8')

def main():
    arguments = docopt(__doc__, version='andotp-decrypt 0.1')
    input_file = arguments['INPUT_FILE']
    debug = arguments['--debug']
    old_encryption = arguments['--old']
    if not os.path.exists(input_file):
        print("Could not find input file: %s" % input_file)
        return None
    password = get_password()
    if old_encryption:
        print(decrypt_aes(password, input_file, debug))
    else:
        print(decrypt_aes_new_format(password, input_file, debug))
    
if __name__ == '__main__':
    main()

#!/usr/bin/env python3 andotp-decrypt.py  Usage: andotp-decrypt.py [-o|--old] [--debug] [-h|--help] [--version] INPUT_FILE  Options: -o --old      Use old encryption (andOTP <= 0.6.2) --debug       Print debug info -h --help     Show this screen. --version     Show version. Decode function used for both the old and new style encryption # Raw data structure is IV[:12] + crypttext[12:-16] + auth_tag[-16:] # Raw data structure is iterations[:4] + salt[4:16] + data[16:] # Read the backup password manually via a prompt if stdin is a tty, # otherwise read from stdin directly.